跳转至

进阶 3 · 配置与 .env

应用在不同环境(开发/测试/生产)需要不同配置:数据库地址、密钥、日志级别、特性开关。硬编码进代码是大忌(12-factor 原则:配置存在环境变量,代码不区分环境)。本章深入 pydantic-settings 的配置管理——对照 Spring 的 @ConfigurationProperties / @Value / @Profile

pydantic-settings 的基础用法BaseSettings、环境变量映射)见 进阶 · pydantic 第 1.10 节。本章不重复基础,聚焦多环境、密钥安全、嵌套配置


3.1 配置与代码分离

  • Spring(application.yml + 注入)

    # application.yml
    db:
      url: jdbc:postgresql://localhost/app
    
    @Value("${db.url}") String url;
    

  • Python(pydantic-settings)

    class Settings(BaseSettings):
        db_url: str = "sqlite:///./app.db"
    # 读环境变量 DB_URL / .env
    

关键区别:Spring 用 YAML 文件 + 注解注入;Python 推崇环境变量优先(12-factor),pydantic-settings 把"环境变量 → 强类型配置对象"自动化。


3.2 多环境配置(dev / staging / prod)

不同环境用不同的 .env 文件,靠环境变量选择加载哪个

.env                # 默认/开发
.env.test           # 测试
.env.production     # 生产
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_FILE=.env.production uv run uvicorn ...   # 生产配置
ENV_FILE=.env.test pytest                      # 测试配置

优先级

真实环境变量 > .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 写法:

DB__URL=postgresql://localhost/app
DB__POOL_SIZE=10

3.4 .env 文件管理

  • .env.example(提交到 git)

    # 复制为 .env 后填真实值
    DB_URL=sqlite:///./dev.db
    SECRET_KEY=replace-me
    DEBUG=true
    
  • .env(绝不提交)

    DB_URL=postgresql://prod...
    SECRET_KEY=<真实密钥>
    

铁律:

  1. .env 加入 .gitignore(含真实密钥,绝不入库)。
  2. 提交 .env.example 作为模板(只有键名 + 示例值,新人复制成 .env)。
  3. .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 文件——通过环境变量在运行时注入(镜像不含密钥,可安全分发)。

# 容器运行时注入(密钥不进镜像)
docker run -e SECRET_KEY=$SECRET_KEY myapp:latest

对照 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_Yx_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.urldb.pool_sizecache.host,写出 .envSettings

参考答案

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_nested_delimiter="__")
    db: "DbConfig"
    cache: "CacheConfig" = CacheConfig()
DB__URL=postgresql://localhost/app
DB__POOL_SIZE=10
CACHE__HOST=redis.local


上一章:进阶 2 · 依赖与包管理← 回首页 | 下一章:进阶 4 · 打包发布与部署