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 appBenefits:
- 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) # TrueRemember
- 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.