Introduction to the Application Factory Pattern
As Flask applications grow in complexity, structuring your code becomes increasingly important. One of the most powerful patterns for organizing Flask applications is the "Application Factory Pattern." This approach allows you to create Flask application instances on demand, rather than having a single, globally defined application.
Think of the application factory pattern like a car manufacturing plant. Instead of having just one pre-built car (a global Flask app), you have a factory that can produce many cars (Flask app instances) with different specifications (configurations) as needed. This flexibility is particularly valuable for testing, where you might need multiple application instances with different configurations.
Problems with the Global Flask Instance
Before we dive into the application factory pattern, let's understand why the simple approach of creating a global Flask instance can become problematic as applications grow.
The Simple Approach
from flask import Flask
# Global Flask instance
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
@app.route('/')
def home():
return 'Hello, World!'
if __name__ == '__main__':
app.run(debug=True)
This approach works fine for small applications but presents several challenges:
- Limited Configuration Flexibility: Configuration is set at import time, making it difficult to use different settings for development, testing, and production.
- Testing Difficulties: Each test might need a different configuration, but with a global instance, changing settings in one test affects others.
- Circular Import Issues: When your application is spread across multiple modules, it's easy to create circular import dependencies, where modules depend on each other.
- Extension Initialization: Extensions like Flask-SQLAlchemy and Flask-Login are typically bound to the application instance, making them harder to reuse across different app instances.
These challenges become more pronounced as your application grows. The application factory pattern addresses these issues by providing a more flexible structure.
Basic Application Factory Pattern
The core idea of the application factory pattern is to create a function (the "factory") that constructs and returns a Flask application instance. This function can accept parameters to customize the application's configuration.
A Simple Application Factory
# app.py or __init__.py
from flask import Flask
def create_app(config=None):
# Create the Flask instance
app = Flask(__name__)
# Set default configuration
app.config.from_mapping(
SECRET_KEY='dev',
SQLALCHEMY_DATABASE_URI='sqlite:///dev.db',
SQLALCHEMY_TRACK_MODIFICATIONS=False,
)
# Apply custom configuration if provided
if config:
app.config.from_mapping(config)
# Register a simple route
@app.route('/')
def home():
return 'Hello from the Application Factory!'
return app
# This allows running the app directly with "python app.py"
if __name__ == '__main__':
app = create_app()
app.run(debug=True)
Now you can create application instances with different configurations as needed:
# Create a development app
dev_app = create_app({
'DEBUG': True,
'SQLALCHEMY_DATABASE_URI': 'sqlite:///dev.db'
})
# Create a testing app
test_app = create_app({
'TESTING': True,
'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.db'
})
# Create a production app
prod_app = create_app({
'DEBUG': False,
'SECRET_KEY': 'secure-production-key',
'SQLALCHEMY_DATABASE_URI': 'postgresql://user:pass@localhost/prod_db'
})
This approach gives you the flexibility to create different application instances with appropriate configurations for different scenarios.
Configuration Management
Effective configuration management is essential for a clean and maintainable application factory. Flask provides several methods for loading configuration, which can be used together.
Configuration Options
- Default Configuration: Set in the factory function
- Configuration Classes: Python classes with configuration variables
- Environment Variables: Loading settings from the environment
- Configuration Files: Loading settings from files (JSON, TOML, etc.)
Using Configuration Classes
A common approach is to define a base configuration class with default settings, then create subclasses for different environments:
# config.py
import os
class Config:
"""Base configuration."""
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-please-change')
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
"""Development configuration."""
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URI',
'sqlite:///dev.db')
class TestingConfig(Config):
"""Testing configuration."""
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URI',
'sqlite:///test.db')
class ProductionConfig(Config):
"""Production configuration."""
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URI',
'sqlite:///prod.db')
# Dictionary with configuration objects
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
Then update your factory function to use these configuration classes:
# app.py or __init__.py
from flask import Flask
from config import config
def create_app(config_name='default'):
app = Flask(__name__)
# Load the configuration
app.config.from_object(config[config_name])
# Optionally, allow config class to initialize the app
if hasattr(config[config_name], 'init_app'):
config[config_name].init_app(app)
# ... rest of your factory function ...
return app
Now you can easily create applications with different configurations:
# Create a development app
app = create_app('development')
# Create a testing app
app = create_app('testing')
# Create a production app
app = create_app('production')
This approach makes it easy to manage different configurations while keeping your code clean and organized.
Environment-based Configuration
For production applications, it's common to determine the configuration based on environment variables:
# Set the environment variable in your deployment environment
# export FLASK_ENV=production
# app.py or __init__.py
import os
from flask import Flask
from config import config
def create_app():
# Get configuration from environment or use default
config_name = os.environ.get('FLASK_ENV', 'default')
app = Flask(__name__)
app.config.from_object(config[config_name])
# ... rest of your factory function ...
return app
This approach allows you to deploy the same code to different environments and have it automatically use the appropriate configuration.
Integrating Extensions
Most Flask applications use extensions for functionality like database access, form handling, user authentication, etc. With the application factory pattern, extensions are typically initialized in two steps:
- Create extension instances outside the factory function
- Initialize them with the application inside the factory function
# extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
# Create extension instances
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
# app.py or __init__.py
from flask import Flask
from config import config
from extensions import db, migrate, login_manager
def create_app(config_name='default'):
app = Flask(__name__)
app.config.from_object(config[config_name])
# Initialize extensions with this app
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
# ... rest of your factory function ...
return app
This approach allows the extensions to be used anywhere in your application without creating circular imports, while still being properly bound to the application instance.
For extensions that need to know about an application context (e.g., to create tables), you can use the application context:
# manual_setup.py
from app import create_app
from extensions import db
app = create_app('development')
with app.app_context():
# Create all tables
db.create_all()
Registering Blueprints
Blueprints are an essential component for structuring large Flask applications. They allow you to organize related routes and functionality into separate modules. With the application factory pattern, blueprints are registered inside the factory function.
Creating a Blueprint
# blueprints/main.py
from flask import Blueprint, render_template
main = Blueprint('main', __name__)
@main.route('/')
def index():
return render_template('index.html')
@main.route('/about')
def about():
return render_template('about.html')
# blueprints/auth.py
from flask import Blueprint, render_template, redirect, url_for
auth = Blueprint('auth', __name__, url_prefix='/auth')
@auth.route('/login')
def login():
return render_template('login.html')
@auth.route('/register')
def register():
return render_template('register.html')
Registering Blueprints in the Factory
# app.py or __init__.py
from flask import Flask
from config import config
from extensions import db, migrate, login_manager
from blueprints.main import main
from blueprints.auth import auth
def create_app(config_name='default'):
app = Flask(__name__)
app.config.from_object(config[config_name])
# Initialize extensions
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
# Register blueprints
app.register_blueprint(main)
app.register_blueprint(auth)
return app
This approach keeps your application modular and organized. We'll explore blueprints in more detail in the next lecture.
Error Handling in the Factory
Centralized error handling is another benefit of the application factory pattern. You can register error handlers within your factory function:
def create_app(config_name='default'):
app = Flask(__name__)
# ... configuration and extensions setup ...
# Register error handlers
@app.errorhandler(404)
def page_not_found(e):
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('errors/500.html'), 500
# ... blueprint registration ...
return app
For larger applications, you might want to create a dedicated errors blueprint:
# blueprints/errors.py
from flask import Blueprint, render_template
errors = Blueprint('errors', __name__)
@errors.app_errorhandler(404)
def page_not_found(e):
return render_template('errors/404.html'), 404
@errors.app_errorhandler(500)
def internal_server_error(e):
return render_template('errors/500.html'), 500
Then register this blueprint in your factory:
# In create_app()
from blueprints.errors import errors
app.register_blueprint(errors)
Note the use of app_errorhandler instead of errorhandler in the blueprint.
This ensures the error handler applies to the entire application, not just routes within the blueprint.
Application-specific Commands
Flask's CLI functionality allows you to create custom commands for your application. With the application factory pattern, you can define these commands in your factory function:
import click
from flask.cli import with_appcontext
def create_app(config_name='default'):
app = Flask(__name__)
# ... configuration and extensions setup ...
# Register custom commands
@app.cli.command('init-db')
@click.option('--drop', is_flag=True, help='Drop tables before creating')
def init_db_command(drop):
"""Initialize the database."""
if drop:
click.echo('Dropping tables...')
db.drop_all()
click.echo('Initializing database...')
db.create_all()
click.echo('Database initialized.')
# ... blueprint registration ...
return app
For more complex applications, you might want to organize commands in separate modules:
# commands.py
import click
from flask.cli import with_appcontext
from extensions import db
@click.command('init-db')
@click.option('--drop', is_flag=True, help='Drop tables before creating')
@with_appcontext
def init_db_command(drop):
"""Initialize the database."""
if drop:
click.echo('Dropping tables...')
db.drop_all()
click.echo('Initializing database...')
db.create_all()
click.echo('Database initialized.')
Then register the command in your factory:
# In create_app()
from commands import init_db_command
app.cli.add_command(init_db_command)
These custom commands can be run from the command line:
$ flask init-db
Initializing database...
Database initialized.
$ flask init-db --drop
Dropping tables...
Initializing database...
Database initialized.
Testing with the Application Factory
One of the key benefits of the application factory pattern is improved testability. You can create a test-specific application instance with appropriate configuration.
# tests/conftest.py
import pytest
from app import create_app
from extensions import db
@pytest.fixture
def app():
"""Create and configure a Flask app for testing."""
app = create_app('testing')
# Create tables for testing
with app.app_context():
db.create_all()
yield app
# Clean up after test
with app.app_context():
db.drop_all()
@pytest.fixture
def client(app):
"""A test client for the app."""
return app.test_client()
@pytest.fixture
def runner(app):
"""A test CLI runner for the app."""
return app.test_cli_runner()
Then you can use these fixtures in your tests:
# tests/test_main.py
def test_index(client):
response = client.get('/')
assert response.status_code == 200
assert b'Hello, World!' in response.data
This approach allows each test to run with a fresh application instance and isolated database, preventing tests from interfering with each other.
Real-world Application Factory Example
Let's put it all together with a complete example of an application factory pattern for a blog application:
Application Factory] --> B[config.py
Configuration Classes] A --> C[extensions.py
Flask Extensions] A --> D[blueprints/] D --> E[main.py
Main Routes] D --> F[auth.py
Authentication] D --> G[blog.py
Blog Functionality] D --> H[admin.py
Admin Panel] D --> I[errors.py
Error Handlers] A --> J[models.py
Database Models] A --> K[utils.py
Utility Functions] A --> L[commands.py
CLI Commands]
Directory Structure
myapp/
├── __init__.py # Application factory
├── config.py # Configuration classes
├── extensions.py # Flask extensions
├── models.py # Database models
├── utils.py # Utility functions
├── commands.py # CLI commands
├── blueprints/ # Blueprint modules
│ ├── __init__.py
│ ├── main.py
│ ├── auth.py
│ ├── blog.py
│ ├── admin.py
│ └── errors.py
├── templates/ # Jinja2 templates
│ ├── base.html
│ ├── index.html
│ ├── auth/
│ ├── blog/
│ ├── admin/
│ └── errors/
├── static/ # Static files
│ ├── css/
│ ├── js/
│ └── images/
└── tests/ # Test modules
├── conftest.py
├── test_auth.py
├── test_blog.py
└── test_admin.py
Application Factory (__init__.py)
"""Flask application factory."""
from flask import Flask
def create_app(config_name='default'):
"""Create and configure the Flask application."""
app = Flask(__name__)
# Load configuration
from myapp.config import config
app.config.from_object(config[config_name])
# Initialize extensions
from myapp.extensions import db, migrate, login_manager
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
# Register blueprints
from myapp.blueprints.main import main
from myapp.blueprints.auth import auth
from myapp.blueprints.blog import blog
from myapp.blueprints.admin import admin
from myapp.blueprints.errors import errors
app.register_blueprint(main)
app.register_blueprint(auth)
app.register_blueprint(blog)
app.register_blueprint(admin)
app.register_blueprint(errors)
# Register CLI commands
from myapp.commands import init_db_command
app.cli.add_command(init_db_command)
# Configure logging (if needed)
if not app.debug and not app.testing:
# Setup production logging
pass
return app
Extensions (extensions.py)
"""Flask extensions."""
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
# Create extension instances
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
@login_manager.user_loader
def load_user(user_id):
from myapp.models import User
return User.query.get(int(user_id))
Main Blueprint (blueprints/main.py)
"""Main routes."""
from flask import Blueprint, render_template
main = Blueprint('main', __name__)
@main.route('/')
def index():
return render_template('index.html')
@main.route('/about')
def about():
return render_template('about.html')
Running the Application
# wsgi.py or run.py
from myapp import create_app
app = create_app()
if __name__ == '__main__':
app.run()
This structure provides a clean, modular foundation that can scale well as your application grows.
Best Practices for Application Factories
- Keep the factory function clean and focused: It should just create the app, load configuration, initialize extensions, and register blueprints/commands.
- Use separate modules for different concerns: Configuration, extensions, models, and blueprints should all be in separate modules.
- Initialize extensions outside the factory: Create extension instances in a separate module and initialize them in the factory.
- Use environment-based configuration: Determine the configuration from environment variables to ease deployment across different environments.
- Create fixtures for testing: Provide pytest fixtures that create test-specific application instances.
- Document your factory function: Make it clear what the factory does and what options it accepts.
Application Context vs. Request Context
With the application factory pattern, it's important to understand Flask's application context and request context, especially when working with extensions like Flask-SQLAlchemy outside of request handlers.
Application Context
The application context keeps track of the application-level data during a request or CLI command.
It's available through the current_app proxy.
Request Context
The request context keeps track of request-level data during a request. It's available through the
request proxy.
When using the application factory pattern, you might need to manually push an application context in scripts or the shell:
# Using the app context in a script
from myapp import create_app
from myapp.extensions import db
from myapp.models import User
app = create_app()
with app.app_context():
# Now you can use current_app and extensions that require the app context
users = User.query.all()
for user in users:
print(user.username)
This is because Flask-SQLAlchemy's query attribute requires an application context to know which database to connect to.
Practical Activity: Creating Your First Application Factory
Let's apply what we've learned by creating a simple Flask application using the application factory pattern. This application will have:
- A development and testing configuration
- A database with SQLAlchemy
- A simple blueprint for the main routes
- Basic error handling
Follow these steps:
-
Create a basic directory structure:
myapp/ ├── __init__.py ├── config.py ├── extensions.py ├── models.py ├── blueprints/ │ ├── __init__.py │ └── main.py ├── templates/ │ ├── base.html │ ├── index.html │ └── errors/ │ ├── 404.html │ └── 500.html └── run.py -
Implement the configuration module (config.py):
"""Configuration settings.""" import os class Config: """Base configuration.""" SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-please-change') SQLALCHEMY_TRACK_MODIFICATIONS = False class DevelopmentConfig(Config): """Development configuration.""" DEBUG = True SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URI', 'sqlite:///dev.db') class TestingConfig(Config): """Testing configuration.""" TESTING = True SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URI', 'sqlite:///test.db') # Dictionary with configuration objects config = { 'development': DevelopmentConfig, 'testing': TestingConfig, 'default': DevelopmentConfig } -
Create the extensions module (extensions.py):
"""Flask extensions.""" from flask_sqlalchemy import SQLAlchemy # Create extension instances db = SQLAlchemy() -
Define a simple model (models.py):
"""Database models.""" from datetime import datetime from extensions import db class Task(db.Model): """Task model for a simple todo application.""" id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(100), nullable=False) description = db.Column(db.Text, nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) completed = db.Column(db.Boolean, default=False) def __repr__(self): return f'' -
Create a blueprint (blueprints/main.py):
"""Main routes.""" from flask import Blueprint, render_template, request, redirect, url_for from extensions import db from models import Task main = Blueprint('main', __name__) @main.route('/') def index(): tasks = Task.query.order_by(Task.created_at.desc()).all() return render_template('index.html', tasks=tasks) @main.route('/add', methods=['POST']) def add_task(): title = request.form.get('title') description = request.form.get('description') if title: task = Task(title=title, description=description) db.session.add(task) db.session.commit() return redirect(url_for('main.index')) -
Implement the application factory (__init__.py):
"""Flask application factory.""" from flask import Flask, render_template def create_app(config_name='default'): """Create and configure the Flask application.""" app = Flask(__name__) # Load configuration from config import config app.config.from_object(config[config_name]) # Initialize extensions from extensions import db db.init_app(app) # Register blueprints from blueprints.main import main app.register_blueprint(main) # Register error handlers @app.errorhandler(404) def page_not_found(e): return render_template('errors/404.html'), 404 @app.errorhandler(500) def internal_server_error(e): return render_template('errors/500.html'), 500 # Shell context processor @app.shell_context_processor def make_shell_context(): return {'db': db, 'Task': Task} return app -
Create a run script (run.py):
"""Run the application.""" import os from app import create_app app = create_app(os.getenv('FLASK_CONFIG', 'default')) if __name__ == '__main__': app.run() - Create templates for the application (base.html, index.html, error pages)
This basic setup demonstrates the key components of the application factory pattern. You can expand it by adding more blueprints, extensions, and functionality as needed.
Key Takeaways
- The application factory pattern allows you to create Flask application instances on demand, with different configurations.
- This pattern solves problems with global Flask instances, including testing difficulties, circular imports, and limited configuration flexibility.
- Configuration can be managed through classes, environment variables, or configuration files.
- Extensions are typically initialized in two steps: creating instances outside the factory and initializing them inside the factory.
- Blueprints are registered within the factory function to keep your application modular.
- Error handlers and CLI commands can be defined inside the factory function.
- The application factory pattern improves testability by allowing you to create test-specific application instances.
- Understanding application context and request context is important when working with the application factory pattern.