The application factory

Flask · Practical · 12 min

Factories let tests and different environments build fresh, differently configured apps without import-time side effects.

Instead of a global app = Flask(__name__), larger projects build the app in a function:

def create_app(test_config=None):
    app = Flask(__name__, instance_relative_config=True)
    ...
    return app

Benefits:

  • Testing: each test can create an app with its own config, such as a temporary database.
  • Multiple configurations: dev, staging and production differ only in what you pass in or load.
  • No circular imports: modules never import a global app. Blueprints are registered inside the factory, and code that needs the app uses current_app.

Extensions follow the init_app pattern: create the extension object at module level without an app (db = SQLAlchemy()), then bind it inside the factory with db.init_app(app). The extension stores its state on the app (in app.extensions), so one extension object can serve several apps.

The Flask CLI finds a factory named create_app or make_app automatically: flask --app myapp run works if myapp/__init__.py defines one. You can also be explicit: flask --app "myapp:create_app(debug=True)" run. Gunicorn accepts the same syntax: gunicorn "myapp:create_app()".

Keep the factory boring and deterministic: load config, init extensions, register blueprints, error handlers and CLI commands, return the app. Avoid doing I/O like connecting to the database there; connect lazily per request.

Example

from flask import Blueprint, Flask, current_app

main = Blueprint("main", __name__)

@main.get("/")
def index():
    return {"app": current_app.config["APP_NAME"], "testing": current_app.testing}

class FakeExtension:                       # the init_app pattern
    def init_app(self, app):
        app.extensions["fake"] = self
fake = FakeExtension()

def create_app(test_config=None):
    app = Flask(__name__)
    app.config.from_mapping(APP_NAME="Notes", SECRET_KEY="dev")
    if test_config:
        app.config.update(test_config)
    fake.init_app(app)
    app.register_blueprint(main)
    return app
prod, test = create_app(), create_app({"TESTING": True, "APP_NAME": "Notes-test"})
print(prod.test_client().get("/").json)   # {'app': 'Notes', 'testing': False}
print(test.test_client().get("/").json)   # {'app': 'Notes-test', 'testing': True}
print("fake" in test.extensions)          # True

Remember

  • create_app() builds a configured app on demand
  • Extensions: create unbound, then ext.init_app(app) in the factory
  • Use current_app instead of importing a global app

Watch out for

  • Importing app from the package inside blueprints, which brings back circular imports.

Check yourself

  1. Why do extensions provide init_app(app) instead of only Extension(app)?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: flask.palletsprojects.com/en/stable/patterns/appfactories/