FastAPI项目中db.py代码先于startup事件执行的问题解决
解决FastAPI启动时数据库引擎早于密钥加载的问题
方案一:懒加载单例引擎(推荐)
修改db.py,将引擎初始化逻辑封装为懒加载函数,确保只创建一次引擎,且首次调用时环境变量已完成加载:
import os from sqlmodel import create_engine, Session from sqlalchemy.engine import URL # 初始化引擎变量为None _engine = None def get_engine(): global _engine if _engine is None: connection_string = 'DRIVER={};SERVER={};DATABASE={};UID={};PWD={}'.format( os.getenv('DRIVER'), os.getenv('SERVER'), os.getenv('DB'), os.getenv('USERNAME'), os.getenv('PASSWORD') ) connection_url = URL.create("mssql+pyodbc", query={"odbc_connect": connection_string}) _engine = create_engine(connection_url) return _engine # 生成数据库会话的复用函数 def get_session(): with Session(get_engine()) as session: yield session
在路由或其他需要数据库操作的代码中,通过get_session()获取会话即可:
from db import get_session from fastapi import Depends @router.get("/items") def read_items(session: Session = Depends(get_session)): # 此处使用session执行数据库操作 pass
该方案无需修改main.py的启动逻辑,引擎会在首次被调用时创建,此时startup事件已执行完毕,密钥已加载到环境变量中。
方案二:在Startup事件中初始化引擎并绑定到应用状态
如果希望引擎在启动阶段就完成初始化,可按以下步骤修改:
- 修改
db.py,预留引擎变量及初始化函数:
import os from sqlmodel import create_engine, Session from sqlalchemy.engine import URL engine = None def init_engine(): global engine connection_string = 'DRIVER={};SERVER={};DATABASE={};UID={};PWD={}'.format( os.getenv('DRIVER'), os.getenv('SERVER'), os.getenv('DB'), os.getenv('USERNAME'), os.getenv('PASSWORD') ) connection_url = URL.create("mssql+pyodbc", query={"odbc_connect": connection_string}) engine = create_engine(connection_url) def get_session(): with Session(engine) as session: yield session
- 修改
main.py的启动事件,加载密钥后立即初始化引擎:
# ... 其他导入代码 ... from db import init_engine @app.on_event("startup") def startup_event(): load_secrets() init_engine() # 密钥加载完成后初始化数据库引擎 # ... 其他代码 ...
这样引擎会在startup阶段完成初始化,后续所有使用db.engine或get_session()的地方都会复用同一个实例。
问题根源
原代码中db.py的引擎创建逻辑在模块被导入时(比如main.py或路由文件引用db模块)就会执行,而FastAPI的@app.on_event("startup")事件是在应用启动完成后才触发的,此时模块导入已完成,但密钥还未加载,导致os.getenv()返回None,最终引发错误。
内容的提问来源于stack exchange,提问作者configure.ng
相关产品推荐
相关产品推荐

