Flask Application Factories

I was running some tests on a Flask application recently and ran into some weird errors.

I was using Flask-WTF to create a form with a select box that was populated from the database.

I was using Flask-Script to run my tests but because I had included app in my manage.py file the app was being initalised before any of my test scripts did anything like set configuration values for my tests. So either the database wasn't there to populate the select box, or if it was, I'd get a database error when the test scripts tried to repopulate that table that contained unique database constraints.

After much head-scratching and a post on StackOverflow I realised the solution was to use Application Factories.

Where I was once creating the app in the application's __init__.py like so:

from flask import Flask
app = Flask(__name__)

I moved the creation of the app into a function that can be called from elsewhere and pass in the necessary config details like so (taken from the Flask website):

def create_app(config_filename):
    app = Flask(__name__)
    app.config.from_pyfile(config_filename)

    from yourapplication.model import db
    db.init_app(app)

    from yourapplication.views.admin import admin
    from yourapplication.views.frontend import frontend
    app.register_blueprint(admin)
    app.register_blueprint(frontend)

    return app

However I think it's a little more helpful if I flesh this out in a little more detail.

Here's roughly what my __init__.py file ended up looking like:

import os

from flask import Flask, request
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.bcrypt import Bcrypt
from flask.ext.login import LoginManager
from .errors import ErrorHandler


db = SQLAlchemy()
bcrypt = Bcrypt()
login_manager = LoginManager()


def create_app(config=None):
    """Create and return app."""
    app = Flask(__name__)
    if config is None and 'PROJECT_SETTINGS' in os.environ:
        app.config.from_object(os.environ['PROJECT_SETTINGS'])
    else:
        app.config.from_object(config)
    db.app = app
    db.init_app(app)

    # populated regions table required for adverts form. Must be imported
    # before blueprints
    from data.generator import Generator
    generator = Generator()
    generator.create_regions()

    bcrypt.init_app(app)
    login_manager.init_app(app)

    if not app.debug:
        ErrorHandler(app)

    load_blueprints(app)

    return app


def load_blueprints(app):
    """Load blueprints."""
    from .users.views import users_blueprint
    from .oauth.views import oauth_blueprint
    from .pages.views import pages_blueprint
    
    app.register_blueprint(users_blueprint, url_prefix='/users')
    app.register_blueprint(oauth_blueprint, url_prefix='/oauth')
    app.register_blueprint(pages_blueprint)

I'll be merging these changes into my Flask Template when I get the chance.

So, firstly we have our imports as you would expect. Then I create global variables for db, bcrypt and login_manager. Notice that I don't pass app to any of these. I'm able to do this because these extensions have all been kind enough to follow the Flask Extension guidelines and therefore have an init_app method which allows for the module to be initialised but waits for the app to be loaded in.

I then create the app leaving open the possibility of a system variable defining the configuration.

Next, I need to get the database up and running. I'm not 100% sure why, but Flask-SQLAlchemy requires you to set the app attribute before running init_app. I then populate the regions table so I don't get an error when the form is created (don't worry about the Generator business, it's what I use to generate test data).

I then initialise the bcrypt, login_manager and ErrorHandler and finally load in my blueprints (which is in a separate function for clarity, and return the app.

Now we should be good to go.

I've done away with Flask-Script and instead just created a script directory with various scripts in it. For example, my test.py script looks like this:

import unittest


def test():
    """Run unit tests."""
    tests = unittest.TestLoader().discover('tests', pattern='*.py')
    unittest.TextTestRunner(verbosity=1).run(tests)

if __name__ == "__main__":
    test()

And my unittest create_app method looks like this:

def create_app(self):
    """Create app for tests."""
    app = create_app('config.Test')
    return app

Similarly my runserver.py script looks like this:

from project import create_app

app = create_app()

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

It took me a while to piece all that together so hopefully it helps someone.