《Python编程实战》7.3 版本演进与向后兼容
接口一旦发布就是契约,改它要付代价。本节讲清哪些字段变更是破坏性的、哪些是安全的,用 deprecated 与 Sunset 头做渐进弃用,给出 URL/Header 两种版本化策略,并写一个能自动检测破坏性变更的 OpenAPI 契约 diff 脚本。
category
接口一旦发布就是契约,改它要付代价。本节讲清哪些字段变更是破坏性的、哪些是安全的,用 deprecated 与 Sunset 头做渐进弃用,给出 URL/Header 两种版本化策略,并写一个能自动检测破坏性变更的 OpenAPI 契约 diff 脚本。
上一节设计好的 Pydantic 模型,本身就是一份机器可读的契约。本节让 FastAPI 导出真实的 /openapi.json,逐段解读 schema 里字段约束如何映射,再讲客户端代码生成的三条工具路径与命令,并手写一个基于 httpx + ASGITransport 的最小客户端跑通端到端调用,最后把 schema 断言写进 CI。
上一章我们用 SQLAlchemy 打通了数据库,这一节回到 HTTP 边界:用 Pydantic V2(2.13.5)设计请求/响应模型。覆盖 Field 约束与 Annotated 复用、field_validator 与 model_validator、ConfigDict 关键开关、别名、computed_field,以及 model_dump 的取舍与 from_attributes。
Alembic 1.20.0 实战:从 init 到 env.py 配置、autogenerate 生成迁移、upgrade/downgrade/history/current 全套命令,讲清加列、数据回填与不可逆操作等演进策略,代码在 SQLite 上真跑过。
从 session 生命周期讲起,用实测数据讲清事务边界、连接池参数(pool_size/max_overflow/pool_pre_ping)与 N+1 识别治理;用事件钩子统计 SQL 条数,对比惰性、selectinload、joinedload 的真实数字。
SQLAlchemy 2.1.4 实战:用 DeclarativeBase 与 Mapped[] 定义类型化 ORM 模型,改用 select() 2.x 风格查询,讲清关系映射、加载策略与 ORM/Pydantic 模型分层的工程取舍,代码在 SQLite 上真跑过。
用 lifespan 管理应用级资源(连接池、后台任务),在 SIGTERM 下实现优雅关闭让在途请求跑完,并按 Kubernetes 语义设计 liveness 与 readiness 两类健康探针;全部用 uvicorn 0.54.0 真实进程实测。
用中间件把日志、请求 ID、耗时统计等横切关注点从业务里抽出来:对比 BaseHTTPMiddleware 与纯 ASGI 两种写法,用 request.state 与 contextvars 传递请求上下文,并实测中间件的洋葱顺序与并发下的上下文隔离。
用 FastAPI 0.143.0 搭一个分层后端:把 api / service / repository / schema 四层拆开,用 APIRouter 分文件装配,用 Depends 区分请求级与进程级依赖,用 lru_cache 做单例,并用 dependency_overrides 做测试隔离;每一步都给出真实运行结果。
给测试体系补上三块短板:用 hypothesis 的性质测试突破手写用例的输入盲区并靠反例缩小定位真实缺陷,用 pytest-benchmark 把性能退化成一道可对比的门禁,用 pytest-cov 的覆盖率与 --cov-fail-under 卡住底线。
单元测试之外的第二层防线:先划清单元与集成的边界,再用 SQLite + fixture 在本机搭出可跑的等价数据层,讲透外层事务加 SAVEPOINT 的用例隔离,最后给出 testcontainers 真实容器环境的配置并明确标注未实测。
把 pytest 从「能跑」推到「可维护」:conftest.py 向上合并与就近覆盖、fixture 作用域与依赖注入、工厂型 fixture 避免共享可变状态,再把约定写进 pyproject.toml,最后用一个本地 conftest 插件和实测插件清单收尾。
日志记录离散事件,指标记录系统整体健康度。本节用 prometheus-client 0.26.0 暴露 Counter/Gauge/Histogram,用中间件埋点并规避标签基数陷阱,设计 liveness/readiness 健康检查,再接入 Prometheus 告警规则。
文本日志难以被机器解析。本节用 structlog 26.1.0 把日志输出成结构化 JSON,用 contextvars 绑定请求级上下文,通过 ProcessorFormatter 桥接标准库 logging,并让 trace_id/request_id 贯穿全链路,最后与 OpenTelemetry 打通日志与追踪。
硬编码配置撑不过多环境。本节用 pydantic-settings 2.15.0 讲清配置来源优先级(环境变量 > .env > 默认值)、类型转换与 field_validator 校验、SecretStr 脱敏、嵌套配置与多 .env 合并,以及 lru_cache 缓存配合 FastAPI 依赖注入的落地方式。
一份代码要在多个 Python 版本上跑通。本节实测 uv python list 与 --python-version 的多版本解析,对比 tox 与 nox 的组织思路,给出 GitHub Actions 矩阵配置与缓存键设计,让 CI 既不重复下载也不误用陈旧缓存。
内网与 CI 常常无法直连 PyPI。本节实测 [[tool.uv.index]] 镜像配置与 UV_INDEX_URL 环境变量,讲清私有源凭证与 uv 缓存机制,并用 uv sync --offline 与 wheelhouse 演示一次完全离线的依赖安装与校验。
锁定文件是可复现构建的地基。本节先拆解 pip freeze 为何不可复现,再对比 uv.lock 与 pip-tools 的解析与锁定机制,实测约束文件、平台标记与 PEP 751 pylock.toml,最后讲哈希校验如何把依赖内容彻底锁死。
写在文档里的规范没人执行,变成门禁才会拦人。本节先讲 ruff 规则集怎么选、format 与 --fix 的边界、per-file-ignores 怎么开合理的口子,再用原生 git hook 实测一个「提交前跑 ruff + mypy,不合格就拒绝提交」的门禁,并给出 pre-commit 框架的等价配置。
pyproject.toml 是现代 Python 项目唯一的配置中心。本节逐段拆解它的四个区段——build-system、project、dependency-groups、tool.*,再用实测代码演示 mypy 的三档分层:核心模块严格、适配层宽松、遗留模块豁免,让类型检查既能守住关键逻辑,又不被历史包袱拖垮。