进阶 3 · 配置与 .env¶
应用在不同环境(开发/测试/生产)需要不同配置:数据库地址、密钥、日志级别、特性开关。硬编码进代码是大忌(12-factor 原则:配置存在环境变量,代码不区分环境)。本章深入 pydantic-settings 的配置管理——对照 Spring 的 @ConfigurationProperties / @Value / @Profile。
pydantic-settings 的基础用法(
BaseSettings、环境变量映射)见 进阶 · pydantic 第 1.10 节。本章不重复基础,聚焦多环境、密钥安全、嵌套配置。
3.1 配置与代码分离¶
-
Spring(application.yml + 注入)
-
Python(pydantic-settings)
关键区别:Spring 用 YAML 文件 + 注解注入;Python 推崇环境变量优先(12-factor),pydantic-settings 把"环境变量 → 强类型配置对象"自动化。
3.2 多环境配置(dev / staging / prod)¶
不同环境用不同的 .env 文件,靠环境变量选择加载哪个:
import os
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=(os.getenv("ENV_FILE", ".env"),),
extra="ignore",
)
db_url: str
debug: bool = False
secret_key: str
settings = Settings()
运行时用环境变量切换:
优先级
真实环境变量 > .env 文件 > 代码默认值。生产环境通常不传 .env 文件,而是 CI/容器平台直接注入环境变量(更安全,见 3.5)。
3.3 嵌套配置¶
配置项很多时,分组更清晰(对照 Spring 的嵌套 @ConfigurationProperties)。用 env_nested_delimiter:
把相关配置分组到子模型,环境变量用分隔符表达层级。最干净的方式是独立子模型 + 字段:
class DbConfig(BaseSettings):
url: str
pool_size: int = 5
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_nested_delimiter="__", env_file=".env")
db: DbConfig = DbConfig() # 读 DB__URL, DB__POOL_SIZE
.env 写法:
3.4 .env 文件管理¶
-
.env.example(提交到 git) -
.env(绝不提交)
铁律:
.env加入.gitignore(含真实密钥,绝不入库)。- 提交
.env.example作为模板(只有键名 + 示例值,新人复制成.env)。 .env只用于本地开发;生产用环境变量注入(下节)。
本仓库 bookmarks-api/.env.example 正是这个模式。
⚠️ 致命错误
把含真实密钥的 .env 提交到 git(尤其公开仓库)= 密钥泄露。即使后来删除,git 历史里仍有。一旦发生:立即吊销/轮换该密钥,再清历史。预防 > 补救。
3.5 密钥与敏感信息¶
| 环境 | 密钥来源 | 说明 |
|---|---|---|
| 本地开发 | .env 文件 |
个人填,不入库 |
| CI | CI 平台的 secrets | GitHub Actions secrets、GitLab CI variables |
| 容器/生产 | 平台环境变量注入 | Docker -e、k8s Secret、云平台环境变量 |
生产绝不把密钥打进镜像或 .env 文件——通过环境变量在运行时注入(镜像不含密钥,可安全分发)。
对照 Spring:同样是"密钥不入代码/镜像,环境变量注入",Python 这边靠 pydantic-settings 统一读取。
3.6 校验配置(fail fast)¶
pydantic-settings 的最大价值之一:启动时校验配置完整性和类型。缺必填项或类型错,应用启动就报错,而不是跑到一半才崩。
class Settings(BaseSettings):
secret_key: str # 必填,缺失 → 启动即 ValidationError
port: int = 8000 # 类型错(PORT=abc)→ 启动报错
settings = Settings() # 在应用启动早期实例化
对照 Spring:Bean Validation 也能校验 @ConfigurationProperties,但 Python 这边 pydantic 的校验更直接、错误信息更清晰。
Pythonic 实践
在应用入口(main.py / app = FastAPI(...) 之前)尽早实例化 settings = Settings()。配置错在启动秒级暴露,胜过运行时才炸。
3.7 与 Spring 对照速查¶
| 需求 | Spring | Python (pydantic-settings) |
|---|---|---|
| 强类型配置对象 | @ConfigurationProperties |
BaseSettings 子类 |
| 单值注入 | @Value("${x}") |
settings.x |
| 环境变量读取 | @Value / Environment |
自动(X_Y → x_y) |
| 多环境 profile | @Profile / application-{env}.yml |
env_file + ENV_FILE 变量 |
| 配置文件 | application.yml |
.env |
| 校验 | Bean Validation | pydantic(启动 fail fast) |
| 密钥 | Spring Cloud Secrets / @Value |
环境变量 / CI secrets |
本章练习¶
练习 3.1
给 bookmarks-api 设计多环境配置:开发用 SQLite、测试用内存、生产用 Postgres。写出三个 .env 文件和 Settings 加载逻辑。
参考答案
# .env
DATABASE_URL=sqlite+aiosqlite:///./dev.db
# .env.test
DATABASE_URL=sqlite+aiosqlite://
# .env.production
DATABASE_URL=postgresql+asyncpg://user:pwd@db/app
import os
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=os.getenv("ENV_FILE", ".env"))
database_url: str = "sqlite+aiosqlite:///./dev.db"
ENV_FILE=.env.production uv run uvicorn ...
练习 3.2
为什么 .env 要加进 .gitignore 而 .env.example 要提交?泄露了已提交的密钥该怎么办?
参考答案
.env 含真实密钥,提交 = 泄露;.env.example 只有键名和占位值,作为模板共享。泄露后:立即在密钥签发平台吊销/轮换该密钥(最关键),再考虑清理 git 历史(git filter-repo)——但吊销优先于清历史,因为历史可能已被克隆。
练习 3.3
用嵌套配置(env_nested_delimiter="__")表达 db.url、db.pool_size、cache.host,写出 .env 和 Settings。
上一章:进阶 2 · 依赖与包管理 | ← 回首页 | 下一章:进阶 4 · 打包发布与部署