Flask应用工厂模式创建应用时如何正确读取配置项
问题根因
报错本质是配置读取的位置放错了:models.py里模型类的字段定义是模块导入时就立刻执行的,这时候你还没调用create_app()生成Flask实例,current_app根本没绑定任何活跃的应用上下文,不管你在别的地方怎么加app.app_context()都没用——导入from app.models import db的时候,那行读配置的代码早就跑完了,比应用初始化的流程早了一大截。
实现方法
两种方案都能完美适配多环境配置,按需选择即可:
方案1:数据库层面生成过期时间(适合需要数据库强制约束默认值的场景)
不要在类定义的时候硬写server_default,等应用加载完配置之后再给字段绑定默认值就行。
先修改app/models.py,把定义阶段读配置的逻辑删掉:
from flask_sqlalchemy import SQLAlchemy from sqlalchemy.sql import func db = SQLAlchemy() class LoginLink(db.Model): id = db.Column(db.Integer, primary_key=True) # 先留空,初始化应用时再绑定server_default expiration_date = db.Column(db.DateTime(timezone=True), nullable=False)
再修改app/__init__.py,在配置加载完成、db初始化之后绑定字段默认值,再创建表:
from flask import Flask from config import CONFIG_OBJECT def create_app(config_name): app = Flask(__name__) app.config.from_object(CONFIG_OBJECT[config_name]) from app.models import db, LoginLink db.init_app(app) # 此时已经能安全读取配置 exp_seconds = app.config["LOGIN_LINK_EXP_TIME"].total_seconds() # 绑定服务端默认值:当前时间 + 配置的过期时长 # MySQL/PostgreSQL用interval语法即可,SQLite可以替换为对应时间计算语法 LoginLink.expiration_date.server_default = db.text( f"NOW() + INTERVAL {exp_seconds} SECOND" ) with app.app_context(): db.create_all() return app
方案2:ORM层面生成默认值(最简单,无数据库语法兼容问题)
如果不需要数据库层面维护默认值,直接用SQLAlchemy的default参数(注意不是server_default),把读配置的逻辑放到单独的函数里,只有实际创建模型实例的时候才会调用这个函数,这时候应用上下文肯定已经激活了。
只需要修改app/models.py即可,其他文件不用动:
from flask import current_app from flask_sqlalchemy import SQLAlchemy from datetime import datetime db = SQLAlchemy() def _calc_expire(): # 新建LoginLink实例时才会执行,此时上下文一定是正常的 return datetime.utcnow() + current_app.config["LOGIN_LINK_EXP_TIME"] class LoginLink(db.Model): id = db.Column(db.Integer, primary_key=True) expiration_date = db.Column( db.DateTime(timezone=True), nullable=False, default=_calc_expire # 传函数本身,不要加括号 )
避坑提醒
- 别在模块顶层、类定义的位置直接调用
current_app读配置,这些代码导入就跑,那时候应用还没初始化完,必然报上下文错误 - 原代码里
str(app.config["LOGIN_LINK_EXP_TIME"])直接拼SQL的写法有兼容问题,不同数据库对时间间隔的语法要求不一样,优先选方案2,或者用SQLAlchemy内置的func构造表达式,避免SQL语法报错 - 两个方案都会自动读取对应环境的配置:开发环境取30秒、生产环境取30分钟,不需要额外写环境判断逻辑
内容的提问来源于stack exchange,提问作者Tedpac
相关产品推荐
相关产品推荐

