Use a Flask application factory for isolated config and testable blueprints
Move Flask app creation into a create_app factory to isolate configuration per environment, register blueprints at creation time, and enable clean test instances without import side effects.
12 Mar 2026, 05:01 UTC

Problem: a global app leaks state between environments
Creating a Flask app at import time with app = Flask(__name__) makes configuration, extensions and blueprint registration happen as soon as the module is imported. That couples development, testing and production settings to import order, pollutes test runs with module-level state, and forces blueprints to register before you know which app they belong to.
The practical takeaway is to move creation into a factory function, typically create_app, that builds and returns a fresh Flask instance on demand. The factory accepts configuration and registers blueprints only when the app is created, giving per-environment isolation and clean test instances.
Desired outcome
- A callable
create_app(config)that returns a new Flask app. - Configuration applied inside the factory, not at import time.
- Blueprints registered inside the factory so routes exist only after app creation.
- Independent app instances for development, testing and production without cross-contamination.
Prerequisites
Python 3.10+ and Flask 2.x or 3.x installed in the project environment. The project layout should allow imports from an app package without triggering side effects.
Assumption: extension initialization signatures are stable in your Flask minor version. Verify the init_app pattern you use matches the version you run, because behavior can differ across Flask minor releases.
Build a minimal factory
Create app/__init__.py with a factory that returns a Flask instance. Run commands from the project root with write permission to the project directory.
from flask import Flask
def create_app(config=None):
app = Flask(__name__)
if config:
app.config.update(config)
return app
Expected check: calling create_app() returns an object with route and config attributes. Do not rely on import-time registration; the app is inert until the factory runs.
Isolate configuration per environment
Pass a config name or dict to the factory and apply it before returning the app. This avoids a single global config dict.
def create_app(config_name='development'):
app = Flask(__name__)
configs = {
'development': {'DEBUG': True},
'testing': {'TESTING': True, 'DEBUG': False},
'production': {'DEBUG': False}
}
app.config.update(configs.get(config_name, {}))
return app
Risk: mutating a shared dict will leak between instances. Use app.config.update with a fresh mapping per call.
Verification step: instantiate twice with different names and compare config values. The two instances should hold different DEBUG values.
Register blueprints inside the factory
Blueprints are modular route groups. Register them inside the factory so they attach only to the app you create.
from flask import Blueprint
api = Blueprint('api', __name__)
@api.get('/ping')
def ping():
return {'ok': True}
def create_app(config=None):
app = Flask(__name__)
if config:
app.config.update(config)
app.register_blueprint(api, url_prefix='/api')
return app
Because registration occurs in the factory, importing api does not add routes to any app. Routes appear only after create_app returns.
Verify isolation and registration
Practical checks you can perform in an interactive session or a test file:
- Create a minimal factory and confirm the returned object has route registration methods.
- Instantiate the factory with different config values and assert the values differ between instances, confirming isolation.
- Register a simple blueprint inside the factory and verify its routes appear only after app creation, not at import time. Inspect
app.url_mapafter creation.
These checks confirm no module-level state leakage and that blueprints are bound at creation time.
Context management and recovery
Operations that need an application context, such as database initialization or reading config outside a request, require an explicit context.
app = create_app('testing')
with app.app_context():
# init extensions here
pass
Forgetting to push or pop a context can cause runtime errors about a missing application context. If you hit those errors, ensure the code that touches current_app or extensions runs inside app.app_context() or a request context. This is the main recovery path when migrating an existing import-time app to a factory.
Limitations to track
The factory pattern requires explicit app context management for initialization. Extension init_app signatures can change across Flask minor versions, so verify usage against the version you deploy.
Factories add indirection. Keep the factory small and move heavy setup into functions the factory calls, which preserves readability and keeps each piece testable on its own.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.