Flask that soup 🥥

Python Web

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.

The basic structure of the app is something like

# __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

# 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.

# 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:

FLASK_APP=postit
FLASK_ENV=development

while in the soupboat we will add the url_prefix property:

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)

# 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.

# 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

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