Solving Circular Imports in Flask with Application Factories and Blueprints
Stop fighting circular imports in Flask. Learn how to use the Application Factory pattern and Blueprints to build a modular, testable, and scalable project structure.
04 Oct 2026, 10:23 UTC

The 'Circular Import' Wall
As a Flask project grows, you typically move routes into separate files and models into a models.py. Very quickly, you hit a wall: your routes need the app object to define decorators, but your app.py needs to import the routes to register them. This creates a circular dependency that crashes the Python interpreter before the server even starts.
The solution is to stop treating the Flask application as a global object. By using the Application Factory pattern combined with Blueprints, you decouple the definition of your routes from the creation of the application instance.
The Application Factory Pattern
An Application Factory is a function that encapsulates the creation of the Flask instance. Instead of creating app = Flask(__name__) at the top level of a module, you wrap it in a function. This allows you to create multiple instances of the app with different configurations—one for production, one for testing, and one for development—without restarting the Python process.
To make this work with extensions (like SQLAlchemy or Flask-Login), you must use the init_app() pattern. You define the extension object globally but do not bind it to a specific app until the factory function calls its initialization method.
Modularizing with Blueprints
Blueprints are essentially "mini-applications." They allow you to define routes, templates, and static files in isolation. A Blueprint does not have an application object of its own; it records operations to be performed on the application once it is registered.
To access application settings (like a database URI or API key) inside a Blueprint without importing the app object, Flask provides the current_app proxy. This is a thread-safe pointer that always refers to the application handling the current request.
Implementation Example
Below is a structured implementation. This assumes Flask 3.0+ and a standard Python environment.
1. The Blueprint (auth.py)
from flask import Blueprint, current_app
auth_bp = Blueprint('auth', __name__)
@auth_bp.route('/login')
def login():
# Access config via current_app instead of importing 'app'
app_name = current_app.config.get('APP_NAME', 'Default App')
return f"Logging into {app_name}"
2. The Factory (app.py)
from flask import Flask
from .auth import auth_bp
def create_app(config_class):
app = Flask(__name__)
app.config.from_object(config_class)
# Register Blueprints
app.register_blueprint(auth_bp, url_prefix='/auth')
return app
3. The Entry Point (main.py)
from app import create_app
class DevelopmentConfig:
APP_NAME = "Dev-Environment"
# Run this as: python main.py
if __name__ == "__main__":
app = create_app(DevelopmentConfig)
app.run(debug=True)
Execution and Verification
To verify this setup, run the main.py script. You can check the result by navigating to /auth/login. If the page displays "Logging into Dev-Environment", the current_app proxy is correctly resolving the configuration passed through the factory.
Risk: If you attempt to access current_app outside of a request context (e.g., in a standalone script), Flask will raise a RuntimeError. Always ensure the app context is pushed when running maintenance scripts.
Trade-offs and Limitations
While this pattern solves scaling issues, it introduces boilerplate. For a single-file prototype or a micro-service with only three endpoints, a factory is overkill. It adds layers of indirection that can make the initial codebase harder to navigate for newcomers.
Additionally, you must be disciplined about extension initialization. If you call db.create_all() outside of the factory or without an app_context(), the extension will not know which database to connect to, leading to initialization errors.
Actionable Summary
- Use a Factory when you need different configurations for testing and production.
- Use Blueprints to split your application into logical components (e.g.,
/api,/auth,/admin). - Use
current_appinside Blueprints to avoid circular imports of the main app object. - Use
init_app()for all Flask extensions to ensure they are bound to the factory-created instance.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.