P09第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

依赖与配置管理

依赖与配置管理 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

这一课解决服务长大后的两个黏腻问题:每个接口都自己开数据库连接、配置散落在代码各处。目标是学会用 Depends 注入依赖、用 Settings 收拢配置,并让「缺一项可选能力」不再炸掉整个服务。

问题场景

P08 的 NOTES 还是内存列表,P11 会换成 SQLite。最直接的写法是每个路由函数第一行 sqlite3.connect(...)——十个接口十份连接代码,测试时想换成内存库要改十个函数。前端 React 里你用 Context 解决「组件树到处传依赖」,FastAPI 的答案是 Depends。

核心解释

依赖注入的本质:函数声明「我需要什么」,框架负责「造出来递给你」。

import os
import sqlite3
from functools import lru_cache
from fastapi import Depends, FastAPI

app = FastAPI()

def get_db():
    conn = sqlite3.connect(os.environ.get("DB_PATH", "reactlab.db"))
    try:
        yield conn
    finally:
        conn.close()                      # yield 后的代码在请求结束时执行

@lru_cache
def get_settings() -> dict:
    return {
        "db_path": os.environ.get("DB_PATH", "reactlab.db"),
        "ai_key": os.environ.get("AI_API_KEY", ""),   # 可选能力
    }

@app.get("/notes")
def list_notes(conn: sqlite3.Connection = Depends(get_db)):
    rows = conn.execute("SELECT id, title FROM note").fetchall()
    return {"total": len(rows), "items": [dict(zip(("id", "title"), r)) for r in rows]}

四条设计纪律:

  1. 依赖可以被替换。测试时用 app.dependency_overrides[get_db] = lambda: memory_conn,整个应用的数据库换成内存库,路由代码零改动。这是注入比「函数里直接 connect」值钱的地方。
  2. 配置集中 + 环境变量注入。get_settings 用 lru_cache 保证全进程只读一次;数据库路径、密钥这类随部署变化的东西全部走环境变量,代码里只留默认值。本站的做法一致:AI 密钥只存在于服务端 .env.local。
  3. 区分启动必需与可选能力。数据库路径缺失应该启动时明确报错;AI 密钥缺失不该阻止启动——对应本站 /api/ai/status:没配 AI 时服务照常跑,只是 AI 功能返回 not_configured。启动崩溃是给「必需项」的待遇。
  4. 不要在模块导入时连接服务。import 时连数据库,测试导入模块就连上真库,P03 讲的「导入无副作用」在服务端的翻版。所有连接都发生在请求期(Depends)或显式启动事件里。

示例拆解

验证「注入可替换」这个核心收益:

# test_note.py(先感受,P17 系统讲)
from fastapi.testclient import TestClient
import sqlite3
from main import app, get_db

app.dependency_overrides[get_db] = lambda: iter([sqlite3.connect(":memory:")])  # 示意
client = TestClient(app)
# 请求 /notes 时拿到的不再是文件库,而是注入的内存库

预期效果:同一个路由函数,生产用文件库、测试用内存库,切换只发生在 override 一处。对照 P17 的临时数据库测试和 P19 的契约测试,这条链路贯穿整个 track 的工程化路线。

动手任务

  1. 把 P08 的笔记接口改造成 Depends(get_db) 从 SQLite 读,删除内存列表。
  2. 新增 GET /ai/status:读 settings 里的 ai_key,为空返回 {"configured": false, "category": "not_configured"},非空返回 {"configured": true}——注意服务本体不因缺 key 崩溃。
  3. 把 DB 路径挪进 get_settings,用环境变量 DB_PATH=test.db 启动一次,确认新库文件被创建。

常见错误

  • 模块顶层 connect:导入即连接,测试和生产互相污染;连接一律走依赖或启动事件。
  • 每个接口读一遍 os.environ:配置散落,改名要全局搜;收进 Settings 对象。
  • 依赖里忘了 close:yield 依赖的 finally 是释放资源的唯一保险,漏写连接数只涨不降。
  • 可选能力设成硬依赖:没配 AI 密钥连笔记接口都起不来;必需项启动校验,可选项运行时降级。

检查与复盘

自查清单:

  • 路由函数里找不到 connect/environ 的直接调用
  • 能演示用 dependency_overrides 换掉一个依赖
  • 拔掉可选配置(AI key)服务照常启动,必需配置缺失则明确报错

参考资料

检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1读代码中等

阅读代码(FastAPI,Python 3.11+)。用请求头 Authorization: Bearer abc123 访问 GET /me,返回什么?如果不用 Depends、改在 read_me 里手动调用 get_current_user,会失去什么?

from fastapi import FastAPI, Depends, Header, HTTPException

app = FastAPI()

def get_current_user(authorization: str = Header()):
    if authorization != 'Bearer abc123':
        raise HTTPException(status_code=401, detail='未登录')
    return {'name': '阿明'}

@app.get('/me')
def read_me(user: dict = Depends(get_current_user)):
    return user
Q2多选中等(多选,全部选对才算正确)

关于 FastAPI 项目的配置管理(Python 3.11+),下列做法正确的有哪些?(多选)

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...