Flask应用工厂函数
什么是应用工厂函数
应用工厂函数是Flask中一种重要的设计模式,它允许你创建一个函数来构建和配置Flask应用实例。这种模式提供了更好的代码组织、配置管理和测试支持。
为什么使用工厂函数
传统方式的局限性
# 传统方式 - 直接创建app
from flask import Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
# 注册蓝图
from myapp.views import main
app.register_blueprint(main)
# 初始化扩展
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
if __name__ == '__main__':
app.run()
问题:
- 应用对象在模块级别创建,难以配置不同环境
- 扩展在导入时初始化,可能导致循环导入
- 测试时需要创建新的应用实例
工厂函数的优势
# 工厂函数方式
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() # 先创建扩展实例,但不绑定app
def create_app(config_name='default'):
"""创建Flask应用实例的工厂函数"""
app = Flask(__name__)
# 加载配置
if config_name == 'development':
app.config.from_object('config.DevelopmentConfig')
elif config_name == 'testing':
app.config.from_object('config.TestingConfig')
elif config_name == 'production':
app.config.from_object('config.ProductionConfig')
else:
app.config.from_object('config.DefaultConfig')
# 初始化扩展(延迟绑定)
db.init_app(app)
# 注册蓝图
from .views import main
app.register_blueprint(main)
# 注册错误处理器
from .errors import not_found_error, internal_error
app.register_error_handler(404, not_found_error)
app.register_error_handler(500, internal_error)
return app
优势:
- 支持多环境配置
- 避免循环导入
- 便于测试
- 支持多个应用实例
基本工厂函数实现
最简单的工厂函数
# app/__init__.py
from flask import Flask
def create_app():
app = Flask(__name__)
# 基本配置
app.config['SECRET_KEY'] = 'dev-secret-key'
app.config['DEBUG'] = True
# 简单的路由
@app.route('/')
def hello():
return 'Hello, World!'
return app
使用工厂函数
# run.py
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)
配置管理
配置类
# config.py
import os
class Config:
"""基础配置类"""
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard-to-guess-string'
SQLALCHEMY_TRACK_MODIFICATIONS = False
@staticmethod
def init_app(app):
"""初始化应用"""
pass
class DevelopmentConfig(Config):
"""开发环境配置"""
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
'sqlite:///dev.db'
class TestingConfig(Config):
"""测试环境配置"""
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
'sqlite:///test.db'
WTF_CSRF_ENABLED = False # 测试时禁用CSRF保护
class ProductionConfig(Config):
"""生产环境配置"""
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///prod.db'
@classmethod
def init_app(cls, app):
Config.init_app(app)
# 生产环境日志配置
import logging
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler(
'flask.log', maxBytes=10240, backupCount=10
)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s '
'[in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Flask application startup')
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
在工厂函数中使用配置类
def create_app(config_name='default'):
app = Flask(__name__)
# 加载配置
app.config.from_object(config[config_name])
# 调用配置类的初始化方法
config[config_name].init_app(app)
return app
扩展初始化
延迟初始化模式
# extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_mail import Mail
from flask_bootstrap import Bootstrap
db = SQLAlchemy()
migrate = Migrate()
login = LoginManager()
mail = Mail()
bootstrap = Bootstrap()
# 工厂函数中初始化
def create_app(config_name='default'):
app = Flask(__name__)
# 加载配置
app.config.from_object(config[config_name])
# 初始化扩展
db.init_app(app)
migrate.init_app(app, db)
login.init_app(app)
mail.init_app(app)
bootstrap.init_app(app)
# 配置登录管理器
login.login_view = 'auth.login'
login.login_message = '请先登录以访问此页面'
return app
蓝图注册
组织项目结构
myapp/
├── __init__.py # 工厂函数
├── config.py # 配置
├── extensions.py # 扩展
├── models.py # 数据模型
├── auth/
│ ├── __init__.py
│ ├── forms.py
│ └── views.py
├── main/
│ ├── __init__.py
│ └── views.py
└── api/
├── __init__.py
└── v1/
├── __init__.py
└── views.py
蓝图示例
# auth/__init__.py
from flask import Blueprint
auth = Blueprint('auth', __name__)
from . import views
# auth/views.py
from flask import render_template, redirect, url_for, flash, request
from . import auth
from .forms import LoginForm, RegistrationForm
@auth.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# 处理登录逻辑
flash('登录成功!', 'success')
return redirect(url_for('main.index'))
return render_template('auth/login.html', form=form)
@auth.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
# 处理注册逻辑
flash('注册成功!请登录。', 'success')
return redirect(url_for('auth.login'))
return render_template('auth/register.html', form=form)
在工厂函数中注册蓝图
def create_app(config_name='default'):
app = Flask(__name__)
# 加载配置
app.config.from_object(config[config_name])
# 初始化扩展
from .extensions import db, login, mail
db.init_app(app)
login.init_app(app)
mail.init_app(app)
# 注册蓝图
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/auth')
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
from .api.v1 import api as api_blueprint
app.register_blueprint(api_blueprint, url_prefix='/api/v1')
return app