之前的FastAPI还活在单文件里所有路由挤在一个main.py。200行时没问题500行时没人敢动——今天把它升级成“分模块的工程”用早报站的API实战三件套APIRouter拆路由、Settings管配置、Depends依赖注入。本篇产出早报站FastAPI应用骨架——app/分层、两个路由模块、统一会话注入、配置文件外置。含代码约150行。 太长不看版给想快速上手的你项目信息一句话说明本篇目标FastAPI从单文件升级为分模块工程代码行数~150行含注释依赖fastapipydantic-settings新增核心功能APIRouter拆分 Settings配置 Depends依赖注入跑起来的命令uvicorn app.main:app --reload核心知识点分层结构、配置外置、依赖注入做完你能得到一套能维护、可扩展的FastAPI工程骨架⚠️工程声明本篇先把FastAPI的“骨架与结构”立起来响应模型response_model的完整规范留到后面联调篇统一设计——今天解决“结构”明天解决“规范”。一、为什么需要“工程化”单文件的三宗罪单文件API是这个画风# 所有路由、所有逻辑、所有配置挤在一个文件app.get(/todos)...app.post(/todos)...app.put(/todos/{id})...三个问题会随着代码长大依次爆发#问题表现①路由一多文件失控找接口靠CtrlF②配置散落连接串、密钥写在文件各处③重复代码每个接口都写一遍“开会话、关会话”工程化就是把这三件事制度化——这正是早报站从“玩具”走向“产品”必须跨过的一步。二、新结构app/层的标准布局python_daily/ ├── core/ # 数据层第03、04篇已就位 │ ├── db.py # 引擎与会话 │ └── models.py # ORM模型 ├── app/ # API层本篇新建 │ ├── __init__.py │ ├── main.py # 应用入口创建app、挂载路由 │ ├── config.py # Settings配置集中管理 │ ├── deps.py # 共享依赖get_session等 │ └── routers/ # 路由按资源分文件 │ ├── __init__.py │ ├── articles.py │ └── sources.py └── .env # 本地配置进.gitignore分层逻辑一句话core是“数据怎么存”app是“接口怎么暴露”routers是“一个资源一个文件”。三、第1步Settings——配置只写一处app/config.pyapp/config.py —— 全局配置frompydantic_settingsimportBaseSettings,SettingsConfigDictclassSettings(BaseSettings):配置项集中声明环境变量与.env文件自动注入database_url:strpostgresqlpsycopg://postgres:你的密码localhost:5432/dailysecret_key:strdev-onlydebug:boolFalsemodel_configSettingsConfigDict(env_file.env,extraignore)settingsSettings()# 全局唯一实例其他地方import它 三个细节#细节说明①env_file.env自动读项目根的.env文件——.env必须进.gitignore密钥不在代码里、不在仓库里只在环境里②环境变量自动匹配字段database_url自动匹配环境变量DATABASE_URL大小写不敏感.env里写DATABASE_URLxxx即生效③默认值本地开发兜底生产环境用环境变量覆盖代码零改动 以后任何接口要用配置一行from app.config import settings。.env示例记得进.gitignoreDATABASE_URLpostgresqlpsycopg://postgres:你的密码localhost:5432/daily SECRET_KEY你的随机密钥 DEBUGfalse四、第2步共享依赖——session注入每个接口都要数据库会话但“开一个、用、关一个”不该重复写。FastAPI的依赖注入Depends把这件事制度化app/deps.pyapp/deps.py —— 共享依赖fromcore.dbimportSessiondefget_session():每个请求一个独立会话请求结束自动关闭with保证withSession()assession:yieldsessionapp/routers/articles.pyapp/routers/articles.py —— 文章接口fromfastapiimportAPIRouter,Dependsfromsqlalchemyimportselectfromsqlalchemy.ormimportSessionfromapp.depsimportget_sessionfromcore.modelsimportArticle routerAPIRouter(prefix/articles,tags[articles])router.get()deflist_articles(session:SessionDepends(get_session),# 依赖注入FastAPI自动调用get_sessionlimit:int10,):文章列表最新在前stmtselect(Article).order_by(Article.id.desc()).limit(limit)returnsession.scalars(stmt).all() 拆开看发生了什么#关键点说明①Depends(get_session)声明“这个接口需要get_session提供的东西”——FastAPI收到请求时自动调用get_session把yield出的session作为参数传进来请求结束自动执行收尾②好处一接口代码干净函数体里只有业务逻辑没有样板③好处二全局替换将来测试时第26篇把get_session换成“测试库会话”所有接口自动切换一行接口代码不用改依赖注入的第一个字面收益是整洁深层收益是可替换。 再加一个业务型依赖练手——分页参数app/routers/articles.py补充defget_page(page:int1,size:int10):分页依赖page从1开始返回(offset, limit)return(page-1)*size,sizerouter.get(/top)deftop_articles(session:SessionDepends(get_session),page:tuple[int,int]Depends(get_page),# 依赖还能组合依赖):offset,limitpage stmtselect(Article).order_by(Article.id.desc()).offset(offset).limit(limit)returnsession.scalars(stmt).all() 注意page的类型注解是tuple[int, int]和get_page返回值对齐——类型注解要和实际返回一致第七节④会讲这个坑。五、第3步应用入口——把路由挂上去app/main.pyapp/main.py —— FastAPI应用入口fromfastapiimportFastAPIfromapp.routersimportarticles,sources appFastAPI(title早报站 API,version0.2.0)app.include_router(articles.router)app.include_router(sources.router)app/routers/sources.py订阅源接口模式同articlesfromfastapiimportAPIRouter,Dependsfromsqlalchemyimportselectfromsqlalchemy.ormimportSessionfromapp.depsimportget_sessionfromcore.modelsimportSource routerAPIRouter(prefix/sources,tags[sources])router.get()deflist_sources(session:SessionDepends(get_session)):returnsession.scalars(select(Source)).all()✅ 运行在项目根目录执行uvicorn app.main:app--reload打开/docs——见证结构化的第一个红利Swagger里接口按articles / sources分组显示tags自动归类接口多了也不乱。六、验收清单1. uvicorn启动无报错/docs里articles / sources两组接口2. 浏览器访问/articles?limit3→ 返回3条文章JSON3. 访问/articles/top?page2→ 翻页生效offset正确4. 改.env的DATABASE_URL指向不存在的库 → 接口报可读错误 → 改回5.gitstatus确认.env不在仓库里6. 全程无报错后提交Gitgitadd.gitcommit-mFastAPI工程化路由拆分 Settings 依赖注入七、常见报错这6个工程化改造的标配重点①ModuleNotFoundError: No module named pydantic_settings 原因pydantic-settings需要单独安装FastAPI自带pydantic不带settings。✅ 解法pipinstallpydantic-settings pip freezerequirements.txt②ModuleNotFoundError: No module named app 原因启动目录不对——不在项目根目录执行uvicorn。✅ 解法cd python_daily再跑cwd在sys.path里才有app包一季第5篇的老规矩。③/docs里接口地址变成/articles//或访问报307 原因prefix/articles加router.get(/)拼出了双斜杠。✅ 解法带prefix时路由写空串router.get()不带prefix时写/。prefix与路径的拼接规则要记牢。④ 访问接口报missing required argument: session 原因函数签名写了session: Session但忘了 Depends(get_session)——FastAPI把它当成普通必填参数。✅ 解法依赖注入的声明是Depends(...)不是类型注解本身。类型注解只是文档Depends才是接线。⑤ 返回的JSON里datetime报cannot encode datetime 原因直接返回ORM对象时FastAPI默认序列化不了datetime等复杂类型。✅ 解法现在表里全是基础类型所以没炸加了时间列就会遇到——响应模型response_model与序列化规范在后续统一处理。先记住裸ORM对象返回是过渡态。⑥ 改了.env配置程序没反应 原因Settings在import时读一次.env——改完要重启uvicorn--reload会自动重启但改了.env有时不触发。✅ 解法手动重启或确认.env在项目根、名字正确无后缀就叫.env。八、课后练习#练习难度提示1补齐sources接口给订阅源加“按url模糊搜索”⭐⭐Source.url.contains(keyword)2分页依赖升级给get_page加max_size50上限size传1000时自动截断⭐⭐依赖里的防御逻辑一次生效全接口受益3密钥搬家把遗留的明文配置全部迁入Settings .env⭐⭐密钥军规正式落地4选做新旧对照用新结构重写待办API⭐⭐⭐感受“单文件”与“工程”的差距 配套代码完整app/结构已上传Gitpython_daily/【gitee仓库地址】