Thoughts on Modularizing Flask Applications

I've been playing around with the Flask web framework for a while now. It's the basis for YAWT, the CMS/blogging system I'm currently developing (mostly as an exercise in familiarizing myself with Python)

With Flask, it's dead simple to get a very basic web application up and running:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

@app.route("/blah")
def blah():
    return "Hello Blah!"

if __name__ == "__main__":
    app.run()

Put this in a python file called blah.py, mark it as executable, and run it. You should be able to access the web application on locahost:5000. It does not, admittedly, do anything even remotely interesting, but it's enough to get the basic idea.

As your application gets bigger, Flask best practices suggest that you

Ideally you'd like to do both. As it turns out, this is trickier than I had anticipated. This article documents what I've learned on the subject.

Dividing the Application into Modules

First we can try breaking up the application into modules. In this vein, we'd like to put the view functions (the ones with the @app.route decorators) into a separate python file/module. How do we do this?

The docs actually have a recipe for this. If you adapt the instructions to the above case, you would do the following:

  • create a folder called blah
  • copy blah.py to blah/__init__.py
  • move the view functions into views.py, existing alongside your __init__.py file.
  • create a runserver.py alongside the new blah folder.

So your blah/__init__.py file would look like this:

from flask import Flask
app = Flask(__name__)

import blah.views

And your views.py would look like this:

from blah import app

@app.route("/")
def hello():
    return "Hello World!"

@app.route("/blah")
def blah():
    return "Hello Blah!"

And your runserver.py would look like this:

from blah import app
app.run(debug=True)

You'll note that we've introduced a circular dependency (__init__.py imports views.py, and views.py imports app from __init__.py). Most python programmers would be uncomfortable with that, myself included, but the docs acknowledge the weirdness and move on.

One possibility that the docs seem to gloss over, but which could be explored, is to eschew decorators completely and use the equivalent method calls on the Flask object. That would allow you to do something like this:

__init__.py:

from flask import Flask
app = Flask(__name__)
init_views(app)

views.py:

def init_views(app):
    app.add_url_rule('/', 'hello', hello)
    app.add_url_rule('/blah', 'blah', blah)

def hello():
    return "Hello World!"

def blah():
    return "Hello Blah!"

This works, and avoids circular dependencies, but it does mean that you miss out on one of Flasks nicest features, which are the decorators. I don't think it's something that a lot of people are willing to give up.

Modules with Blueprints

Yet another way to do it is to define the URL rules in a Blueprint. Blueprints are Flask's way of breaking up an application into independent pieces. Blueprints can have URL rules, filters, static files, etc. in basically any combination (it's possible, for example, to define a Blueprint for the sole purpose of providing a set of static files).

We can define a single Blueprint for our application in a views file:

views.py:

blah_bp = Blueprint(__name__)

@blah_bp.route("/")
def hello():
    return "Hello World!"

@blah_bp.route("/blah")
def blah():
    return "Hello Blah!"

And then the main module file looks like this:

__init__.py:

from flask import Flask
app = Flask(__name__)

from views import blah_bp
app.register_blueprint(blah_bp)

No circular imports, and we get to use decorators - seems like a win all around. Of course, if you wanted to structure your Blueprint into multiple files, you can do that too, but you'd have to resort to circular imports again.

Modules and Application Factories

The value of Blueprints starts to become clear when we start talking about application factories.

The simple way of instantiating a Flask application is by doing so directly in a python file somewhere. That being said, the Flask docs actually recommend creating the application object via an application factory function. So instead of this:

app = Flask(__name__)

You'd have something like this:

def create_app():
    app = Flask(__name__)
    return app

By doing so, you make unit testing a bit more convenient, and you allow for the possibility of running more than one Flask application in the same python process.

Using application factories complicates the process of setting up URL rules, or using Flask based decorators in general. In general, using the decorators requires access to the app object, and you don't have it until the create_app() call returns, which is too late. One obvious solution is to define all your rules inside the create_app method, like so:

def create_app():
    app = Flask(__name__)

    @app.route("/")
    def hello():
        return "Hello World!"

    @app.route("/blah")
    def blah():
       return "Hello Blah!"

    return app

This works, and it's a method I've adopted in the past, but it always made me a bit uncomfortable. It's just weird to me that I have to nest my routing functions inside another function. And what if you want to define the URL rules in another module?

Blueprints help solve the problem. You define your Blueprint in another file and import it after the application object is created, so you can register it on the application, in much the same fashion as the non-factory scenario:

__init__.py:

def create_app():
    app = Flask(__name__)
    from views import blah_bp
    app.register_blueprint(blah_bp)

views.py:

blah_bp = Blueprint(__name__)

@blah_bp.route("/")
def hello():
    return "Hello World!"

@blah_bp.route("/blah")
def blah():
    return "Hello Blah!"

Application Factories and Configuration

As noted in the docs, one downside to the above approach is that the application object - and hence the configuration - is not available for use in the Blueprint during import time. You can't use current_app to access the application, because you don't have an application context at this point (more on that later). And you can't use the app object itself since it's tucked away in the create_app method and you can't see it in the views.py module.

But what if you need it? What if, for example, you want to use a piece of configuration, let's say the static_url_path, during the creation of the Blueprint?

I suspect this is a scenario that does not come up very often, since I found very little information on how to do this. I eventually came up with the following, but it only works on Flask 0.9 and above:

__init__.py:

def create_app():
    app = Flask(__name__)
    with app.app_context():
        from views import blah_bp
        app.register_blueprint(blah_bp)

views.py:

static_url_path = current_app.config['STATIC_URL_PATH']
blah_bp = Blueprint(__name__, static_url_path = static_url_path)

@blah_bp.route("/")
def hello():
    return "Hello World!"

@blah_bp.route("/blah")
def blah():
    return "Hello Blah!"

The app.app_context() call creates what's called an application context. Application contexts are a somewhat mystifying concept in Flask and what follows is my best attempt at an explanation (to be taken with a very large grain of salt).

Basically, a Flask application can exist in one of two states:

  • the setup state, where we create the application object and we haven't started processing any requests yet.
  • the request handling state, where we're processing a request.

When we're processing a request, certain global variables become available, like request and current_app. These are the standard mechanisms by which request processing code gets access to the request or the application object. These variables become available because the processing of a request is implicitly associated with the creation of a request context, a kind of scratch pad that isn't shared between threads and where you can stash items that are only valid for the duration of the request.

Notably, these global variables are not implicitly available during the setup phase because we don't have a request context. It makes sense - we're not actually processing a request so why would there be a request context?

In older versions of Flask, you could make those variables available during the setup phase by manually creating a request context. That still works in newer versions of Flask, but if all you're after is the current_app variable (say, because you want access to the configuration object), then you have a cheaper option - you can manually create an application context instead. It's the existence of the application context that makes the current_app variable accessible.

Note that in the newer versions of Flask, creating a request context implicitly creates an application context as well, so the current_app variable is still available just by creating a request context.

One thing that trips people up is the fact that an application context is not actually scoped to the application - each request gets their own application context. Application contexts act as a kind of extension to the request context, an extension that can stand on its own without the request context, but not the other way around.

Anyway, to make a long story short, you need one of these application contexts around for the current_app special variable to work. You automatically have an application context provided for you when you process a request, but outside of a request (like when you're creating the Blueprint) you don't have one implicitly and it's up to you to create one explicitly if you want one. The app.app_context() call will do that for you.