Solving Circular Dependencies in Flask with the Application Factory Pattern
Stop fighting circular imports in Flask. Learn how to use the Application Factory pattern to manage multiple configurations and decouple your routes using Blueprints.
26 May 2026, 02:51 UTC

The Global App Bottleneck
In small Flask projects, it is common to create the app = Flask(__name__) instance in a main file and import it wherever it is needed. While this works for a few routes, it quickly leads to circular imports. This happens when your app needs your routes to start, but your routes need the app instance to define their decorators.
The solution is the Application Factory. Instead of a global variable, you wrap the application creation inside a function. This allows you to instantiate the app multiple times with different configurations—such as one for production and one for automated testing—without restarting the Python process.
Decoupling with Blueprints and Extensions
To make a factory work, you must stop importing the app object directly into your logic files. Instead, use two key Flask mechanisms:
- Blueprints: These are essentially "mini-apps" that define routes and handlers without needing an active application instance. You register them with the app inside the factory function.
- Deferred Initialization: Most Flask extensions (like SQLAlchemy or PyMongo) provide an
init_app()method. You define the extension object globally, but you don't bind it to a specific app until the factory runs.
Implementation Example
This example demonstrates a factory that switches configurations based on an input argument. This is critical for ensuring that your test suite does not accidentally wipe your production database.
# extensions.py
from flask_sqlalchemy import SQLAlchemy
# Define the extension globally, but don't bind it to an app yet
db = SQLAlchemy()
# factory.py
from flask import Flask
from .extensions import db
from .routes import main_bp
def create_app(config_name='development'):
app = Flask(__name__)
# Load configuration based on the environment
if config_name == 'testing':
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['TESTING'] = True
else:
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///prod.db'
# Bind extensions to the app instance
db.init_app(app)
# Register blueprints
app.register_blueprint(main_bp)
return app
# routes.py
from flask import Blueprint, current_app
from .extensions import db
main_bp = Blueprint('main', __name__)
@main_bp.route('/')
def index():
# Use current_app proxy to access config without importing the app object
db_uri = current_app.config['SQLALCHEMY_DATABASE_URI']
return f"Connected to: {db_uri}"
How to run this
To run the application, you must point Flask to the factory function. In your terminal, set the FLASK_APP environment variable:
# Linux/macOS
export FLASK_APP="factory:create_app"
flask run
Managing the Application Context
When you move away from a global app object, you can no longer call app.config or app.logger from random scripts. Flask solves this with current_app, a proxy that points to the application handling the current request.
If you need to access the app outside of a request (for example, in a database migration script), you must manually push an application context:
app = create_app('development')
with app.app_context():
# Now current_app is available
print(current_app.config['SQLALCHEMY_DATABASE_URI'])
Trade-offs and Limitations
The Application Factory adds boilerplate. For a single-file API with three endpoints, it is overkill. Additionally, it introduces the risk of RuntimeError: Working outside of application context if you attempt to use current_app in a background thread or a standalone script without the with app.app_context(): block.
Verification Checklist
To ensure your factory is implemented correctly, check the following:
- Circular Imports: Ensure no file in your
/routesor/modelsdirectory importsappfrom the factory file. - Instance Isolation: Verify that calling
create_app('testing')andcreate_app('production')creates two distinct objects with differentconfigvalues. - Blueprint Registration: Confirm that routes defined in Blueprints return 404 until
app.register_blueprint()is called within the factory.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.