You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
7.8 KiB
Markdown
278 lines
7.8 KiB
Markdown
3 years ago
|
---
|
||
|
title: Flask that soup 🥥
|
||
|
description: Confy Flask setup on the Soupboat
|
||
|
categories:
|
||
|
- Python
|
||
|
- Web
|
||
|
git: https://git.xpub.nl/kamo/soup_app
|
||
|
date: 15/02/2022
|
||
|
---
|
||
|
|
||
|
This is a template for simple and scalable Flask apps on the Soupboat. One of the main feature of this setup is that it enables a seamless work between local development (namely your computer) and the Jupiter Lab environment in the Soupboat.
|
||
|
|
||
|
This approach proposes these features:
|
||
|
|
||
|
### Url prefix
|
||
|
The Soupboat has some problems with generated URLs, and when we developed the app for SI16 we had to put every link + '/soupboat/si16', that was more a hack than a real solution. This setup fix it with a middleware that intercepts every request and adds the prefix automatically.
|
||
|
|
||
|
### Easy debug
|
||
|
To develop directly in Jupiter means to stop and restart continously the app to see the changes. With this setup we can work in locale and push the results when we are happy with them.
|
||
|
|
||
|
### Modular
|
||
|
Taking advantages of the Flask Blueprint system and Python modules, we can write a legible structure for our app, and then go crazy inside the blueprints of each page or super specific modules. In this way the code remains readable, and the relations between the different components are rendered clearly.
|
||
|
|
||
|
## Guide
|
||
|
|
||
|
Create a folder for your project. This will be the root of the app. Create a virtual environment in the folder. Open the terminal and write:
|
||
|
|
||
|
`python3 -m venv venv`
|
||
|
|
||
|
This will create a venv folder after a while. Then activate the virtual environment.
|
||
|
|
||
|
`. venv/bin/activate`
|
||
|
|
||
|
In this way every package we will install will be in the scope of the virtual environment, and this helps to avoid conflicts between different versions and projects.
|
||
|
|
||
|
Now we can install Flask.
|
||
|
|
||
|
`pip3 install flask`
|
||
|
|
||
|
Once we have installed it, let's create a folder `soup_app` for the Flask application. Here we will put all the modules needed for the flask app. In this folder we can create a `__init__.py` file, in order to make the folder to be recognized as a Python package.
|
||
|
|
||
|
Here we will initialize a factory for our Flask application. Instead of writing the application directly, we will write a function to generate it depending of the different environemnts. This comes handy when we want to develop our project in locale and then push it in the Soupboat. For more about the concept of factory here's a nice series about design pattern:[Factory Method Pattern](https://www.youtube.com/watch?v=EcFVTgRHJLM).
|
||
|
|
||
|
The basic structure of the app is something like
|
||
|
|
||
|
```python
|
||
|
# __init__.py
|
||
|
|
||
|
# import os to create the folder for the app
|
||
|
import os
|
||
|
|
||
|
# import flask to init the app, and send_from_directory for the icon file
|
||
|
from flask import Flask, send_from_directory
|
||
|
|
||
|
# url_prefix module to work either in local and in the soupboat
|
||
|
from . import prefix
|
||
|
|
||
|
|
||
|
def create_app(test_config=None):
|
||
|
# Create and configure the Flask App
|
||
|
app = Flask(__name__, instance_relative_config=True)
|
||
|
app.config.from_mapping(
|
||
|
SECRET_KEY="dev",
|
||
|
)
|
||
|
|
||
|
|
||
|
# load the instance config, if it exists, when not testing
|
||
|
if test_config is None:
|
||
|
app.config.from_pyfile("config.py", silent=True)
|
||
|
else:
|
||
|
# load the test config if passed in
|
||
|
app.config.from_mapping(test_config)
|
||
|
|
||
|
# ensure the instance folder exists
|
||
|
try:
|
||
|
os.makedirs(app.instance_path)
|
||
|
except OSError:
|
||
|
pass
|
||
|
|
||
|
# create an endpoint for the icon
|
||
|
@app.route("/favicon.ico")
|
||
|
def favicon():
|
||
|
return send_from_directory(
|
||
|
os.path.join(app.root_path, "static"),
|
||
|
"favicon.ico",
|
||
|
mimetype="image/vnd.microsoft.icon",
|
||
|
)
|
||
|
|
||
|
|
||
|
# here we can import all our blueprints for each endpoints
|
||
|
# home blueprint
|
||
|
from . import home
|
||
|
app.register_blueprint(home.bp)
|
||
|
|
||
|
|
||
|
|
||
|
# register the prefix middleware
|
||
|
# it takes the url prefix from an .env file!
|
||
|
app.wsgi_app = prefix.PrefixMiddleware(
|
||
|
app.wsgi_app, prefix=os.environ.get("URL_PREFIX", "")
|
||
|
)
|
||
|
|
||
|
# return the app
|
||
|
return app
|
||
|
|
||
|
```
|
||
|
|
||
|
The home blueprint can be something as easy as
|
||
|
|
||
|
```python
|
||
|
|
||
|
# home.py
|
||
|
|
||
|
from flask import Blueprint
|
||
|
|
||
|
bp = Blueprint("home", __name__, url_prefix="/")
|
||
|
|
||
|
|
||
|
@bp.route("/")
|
||
|
def home():
|
||
|
return 'Hello world'
|
||
|
```
|
||
|
|
||
|
While the prefix it's a bit more abstract: it takes every incoming request and prefix it.
|
||
|
|
||
|
```python
|
||
|
|
||
|
# prefix.py
|
||
|
|
||
|
class PrefixMiddleware(object):
|
||
|
def __init__(self, app, prefix=""):
|
||
|
self.app = app
|
||
|
self.prefix = prefix
|
||
|
|
||
|
def __call__(self, environ, start_response):
|
||
|
|
||
|
if environ["PATH_INFO"].startswith(self.prefix):
|
||
|
environ["PATH_INFO"] = environ["PATH_INFO"][len(self.prefix) :]
|
||
|
environ["SCRIPT_NAME"] = self.prefix
|
||
|
return self.app(environ, start_response)
|
||
|
else:
|
||
|
start_response("404", [("Content-Type", "text/plain")])
|
||
|
return ["This url does not belong to the app.".encode()]
|
||
|
|
||
|
```
|
||
|
|
||
|
|
||
|
If you execute `flask run` from the terminal now it will complain that there is no flask app defined in the environment variables. We could fix it setting manually the name of the flask app directly in the terminal, but we should do that every time we start the project.
|
||
|
|
||
|
Hence we will install python-dotenv, a library to use a `.env` file to configure the application. We will use different .env files depending on where we will deploy the application: one for our local development and one for the environment in the soupboat.
|
||
|
|
||
|
`pip3 install python-dotenv`
|
||
|
|
||
|
Now we can create in the root of our application (the same folder with the venv and flask-soup directories) a .env file, that the application will recognize automatically.
|
||
|
|
||
|
The contents for the local development will be something like this:
|
||
|
|
||
|
```env
|
||
|
FLASK_APP=postit
|
||
|
FLASK_ENV=development
|
||
|
```
|
||
|
|
||
|
while in the soupboat we will add the url_prefix property:
|
||
|
|
||
|
```env
|
||
|
FLASK_APP=postit
|
||
|
FLASK_ENV=development
|
||
|
URL_PREFIX=/soupboat/flask-soup/
|
||
|
```
|
||
|
|
||
|
If you now run `flask run` the application will start at the address `localhost:5000`, and you can debug and play around with things.
|
||
|
|
||
|
There are a couple of other files we can add in the root directory:
|
||
|
|
||
|
A `setup.py` module, useful for installing all the dependencies we need for our project (at the moment flask and python-dotenv)
|
||
|
|
||
|
```python
|
||
|
|
||
|
# setup.py
|
||
|
|
||
|
from setuptools import find_packages, setup
|
||
|
|
||
|
setup(
|
||
|
name="soup_app",
|
||
|
version="1.0.0",
|
||
|
packages=find_packages(),
|
||
|
include_package_data=True,
|
||
|
zip_safe=False,
|
||
|
install_requires=["flask", "python-dotenv"],
|
||
|
)
|
||
|
```
|
||
|
|
||
|
and a `config.py`, to fine tuning the different configurations for the application.
|
||
|
|
||
|
```python
|
||
|
|
||
|
# config.py
|
||
|
|
||
|
import os
|
||
|
|
||
|
|
||
|
class Config(object):
|
||
|
DEBUG = False
|
||
|
TESTING = False
|
||
|
URL_PREFIX = ""
|
||
|
|
||
|
|
||
|
class ProductionConfig(Config):
|
||
|
DEBUG = False
|
||
|
URL_PREFIX = os.environ.get("URL_PREFIX")
|
||
|
|
||
|
|
||
|
class DevelopmentConfig(Config):
|
||
|
ENV = "development"
|
||
|
DEVELOPMENT = True
|
||
|
DEBUG = True
|
||
|
|
||
|
```
|
||
|
|
||
|
The last file to create is a `.gitignore` one, in which we will indicate the files that git should ignore when committing our changes.
|
||
|
|
||
|
I'm using this template atm
|
||
|
|
||
|
```.gitignore
|
||
|
|
||
|
# .gitignore
|
||
|
|
||
|
venv/
|
||
|
|
||
|
*.pyc
|
||
|
__pycache__/
|
||
|
.ipynb_checkpoints
|
||
|
|
||
|
instance/
|
||
|
|
||
|
.pytest_cache/
|
||
|
.coverage
|
||
|
htmlcov/
|
||
|
|
||
|
dist/
|
||
|
build/
|
||
|
*.egg-info/
|
||
|
|
||
|
.env
|
||
|
|
||
|
```
|
||
|
|
||
|
Notice that the .env file is ignored from your commits, so you will need to create one manually in each environment you wanna work.
|
||
|
|
||
|
Now you are ready to push the project to a git repo. Create a new one and follow the steps to push your contents on there.
|
||
|
|
||
|
On the xpub git should be something like:
|
||
|
|
||
|
```
|
||
|
git init
|
||
|
git add .
|
||
|
git commit -m 'Init my soup app'
|
||
|
git remote add origin https://git.xpub.nl/your-username/your-repo.git
|
||
|
git push -u origin master
|
||
|
```
|
||
|
|
||
|
And if it doesn't complain nice we are ready to go on the soupboat (or wherever)
|
||
|
|
||
|
TODO: finish this
|
||
|
|
||
|
steps:
|
||
|
|
||
|
1. clone the repo in the soupboat
|
||
|
2. create a virtual environment in the cloned folder
|
||
|
3. activate it
|
||
|
4. pip3 install -e . (aka install the dependencies)
|
||
|
5. create a .env file with the prefix
|
||
|
6. setup nginx
|
||
|
|
||
|
|
||
|
|
||
|
|