Dynamic Flask Application Configuration
When developing a Flask appplication in a single file, the Flask object app is created in the global scope. This meens any configuration changes after the script runs won’t take effect. To resolve this, move app creation to a explicitly callable factory function, delaying instance initialization.
Factory function: app/init.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect
from config import config_options
# Initialize extensions without app context
db = SQLAlchemy()
csrf_protection = CSRFProtect()
def create_app(env_name):
app = Flask(__name__)
app.config.from_object(config_options[env_name])
config_options[env_name].init_app(app)
# Register extensions
db.init_app(app)
csrf_protection.init_app(app)
return app
Call create_app() with a dynamic environment name to apply the correct configuration.
Implementing Blueprints
The factory function solves dynamic configuration, but routing becomes an issue. In single-file apps, @app.route decorators directly attach routes to the global app instance. With delayed app creation, routes can’t be defined until create_app() is called. Flask blueprints address this by allowing routes to be defined independently of the app instance and registered later.
Blueprint definition: app/main/init.py
from flask import Blueprint
# Create blueprint with name and module context
main_bp = Blueprint('main', __name__)
# Import views to register routes with the blueprint
from . import views
Registering blueprints in app/init.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect
from config import config_options
db = SQLAlchemy()
csrf_protection = CSRFProtect()
def create_app(env_name):
app = Flask(__name__)
app.config.from_object(config_options[env_name])
config_options[env_name].init_app(app)
db.init_app(app)
csrf_protection.init_app(app)
# Register blueprints
from .main import main_bp
from .auth import auth_bp
app.register_blueprint(main_bp)
app.register_blueprint(auth_bp, url_prefix='/auth')
return app
Blueprint routes: app/main/views.py
from datetime import datetime
from flask import render_template, session, redirect, url_for
from . import main_bp
@main_bp.route('/dashboard', methods=['GET', 'POST'])
def dashboard():
return render_template('dashboard.html')
Application Startup Script
Use Flask-Script and Flask-Migrate for command-line management. Create manage.py to handle app initialization and database migrations.
manage.py
import os
from flask_script import Manager, Shell
from flask_migrate import Migrate, MigrateCommand
from app import create_app
from app import db
# Initialize app with environment variable or default to development
app = create_app(os.getenv('FLASK_ENV') or 'dev')
manager = Manager(app)
migrate = Migrate(app, db)
# Shell context for interactive debugging
def make_shell_context():
return {'app': app, 'db': db}
manager.add_command('shell', Shell(make_context=make_shell_context))
manager.add_command('db', MigrateCommand)
if __name__ == '__main__':
manager.run()
Install Flask-Script and Flask-Migrate first, then run python manage.py to start the application.
Complete Project Structure
|-- flask_project
|-- config.py
|-- environment.yml
|-- manage.py
|-- app
| |-- models.py
| |-- __init__.py
| |-- auth
| | |-- forms.py
| | |-- views.py
| | |-- __init__.py
| | |-- templates
| |-- main
| | |-- errors.py
| | |-- forms.py
| | |-- views.py
| | |-- __init__.py
| |-- static
| | |-- css
| | |-- img
| | |-- js
| |-- templates
|-- migrations
|-- tests
| |-- __init__.py
|-- venv
Dynamic Configuration Classes
Define environment-specific configuration classes in config.py.
config.py
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class BaseConfig:
CSRF_SECRET = os.environ.get('CSRF_SECRET') or 'secure-csrf-token'
SECRET_KEY = os.environ.get('SECRET_KEY') or 'secure-session-key'
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
SQLALCHEMY_TRACK_MODIFICATIONS = False
@staticmethod
def init_app(app):
app.config['SECRET_KEY'] = BaseConfig.SECRET_KEY
app.config['CSRF_SECRET'] = BaseConfig.CSRF_SECRET
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = BaseConfig.SQLALCHEMY_COMMIT_ON_TEARDOWN
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = BaseConfig.SQLALCHEMY_TRACK_MODIFICATIONS
class DevelopmentConfig(BaseConfig):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DB_URL') or \
'sqlite:///' + os.path.join(basedir, 'dev-db.sqlite')
class TestingConfig(BaseConfig):
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DB_URL') or \
'sqlite:///' + os.path.join(basedir, 'test-db.sqlite')
class ProductionConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI = os.environ.get('PROD_DB_URL') or \
'sqlite:///' + os.path.join(basedir, 'prod-db.sqlite')
config_options = {
'dev': DevelopmentConfig,
'test': TestingConfig,
'prod': ProductionConfig,
'default': DevelopmentConfig
}