Getting bigger with Flask

My last post about creating websites with Flask covered the steps to create a simple application. What happens when it grows bigger?

In this post I will take as example a common use case for a web app:

  • a public section (homepage, tour, signup, login)
  • a member only section (the app, user settings)
  • an api

Each member will have its own subdomain (ie: if my username is maximebf, I get the maximebf.example.com subdomain). This app will need input validation and background processing (eg: resizing the user’s profile picture).

I’ll assume the same file organization as I described in my previous post.

Getting modular with Blueprints

Flask provides a feature called Blueprints which let your organize your app as modules. As always, the documentation about Blueprints is pretty comprehensive.

I like to create a modules folder in my application directory where all my module files will be stored. I create one python file per module.

example/
  modules/
    __init__.py
    public.py
    member.py
    api.py

To create a module, you initializes a Blueprint object which acts in the same way as the Flask object. Most importantly you can register handlers using the same route() decorator.
In the example/modules/public.py file:

from flask import Blueprint

blueprint = Blueprint('public', __name__)

@blueprint.route('/')
def home():
    return render_template('public/home.html')

Each blueprint can have its own templates folder. However, I don’t like to do it that way and prefer to keep a single templates folder. I find it easier to have all my template files in the same directory. Thus, the public/home.html file would be located in example/templates/.

I use the same logic for static files. Anyway, as I use webassets to manage them, using the static file feature from blueprints wouldn’t be practical.

Don’t forget to add the module name when building urls using url_for(). For example, the name of the url for our home() function would be public.home.

Blueprints need to be registered against a Flask object. The great thing about this is that you can specify at which endpoint using url_prefix and subdomain. In the example/init.py file:

from modules import public, member, api
app.register_blueprint(public)
app.register_blueprint(member, subdomain='<subdomain>')
app.register_blueprint(api, subdomain='api')

Note that there shouldn’t be any handlers left in the __init__.py file. I think once you start using modules, all handlers should be located in modules.

Using this scheme, the public module is accessible from example.com/, the member module from *.example.com/ and the api module from api.example.com/.

Wildcard subdomains

As you may have noted, when I registered the member module against the Flask object, I used a dynamic parameter for the subdomain (the same way as with URLs). We can use this parameter to fetch the associated user using a combination of url processors and before request callbacks.

Here is a handy function to add subdomain support for any Flask or Blueprint object.

You can then use a before_request callback to process the subdomain:

add_subdomain_support(blueprint)

@blueprint.before_request
def add_user_to_global():
    g.user = None
    if g.subdomain:
        g.user = User.query.filter_by(username=g.subdomain).first_or_404()

Handling html forms

WTForms is one of the best Python solution to manage html forms. The Flask documentation has a great section about integrating and using WTForms with your app. I like to define my forms at the top of the module files.

If you are using the Bootstrap CSS framework, here is a Jinja2 macro to generate Bootstrap compatible form fields:

I like to have a helpers.html file in my templates/ folder with some useful macros (like the one above). To import the macros use:

{% from "helpers.html" import form_field %}

Background processing with Celery

Any task which needs a bit of processing should be queued and processed as a background job. Celery is a great Python library which makes launching background tasks really easy.

I create a tasks.py file in the application directory where Celery will be initialized and all my delayed functions will be located.

from example import app
from celery import Celery

celery = Celery('example.tasks')
celery.conf.update(app.config)

@celery.task(ignore_result=True)
def resize_uploaded_image(filename, w, h):
    # ...

I find the easiest way to get Celery running is using Redis as the backend. In the settings.py file:

class Config(object):
    # ...
    BROKER_URL = 'redis://localhost:6379/0'

Launching a background job is done as described in the celery doc:

from example.tasks import resize_uploaded_image

# ...

@blueprint.route('/upload', methods=['POST'])
def upload_image():
    # ...
    resize_uploaded_image.delay(filename, 100, 100)

Finally, you’ll need to start the worker using the celery CLI utility:

celery worker -A example.tasks -l info

This is where supervisor comes handy (see previous post) as we can add a new entry to automatically start the celery process:

[program:example_worker]
directory=/path/to/my/app
command=celery worker -A example.tasks -l info
autostart=true
autorestart=true


comments powered by Disqus

01/11/2012