Pydantic Settings:多环境配置文件(开发/测试/生产)管理

📂 所属阶段:第五阶段 — 工程化与部署(实战篇)
🔗 相关章节:依赖注入系统 · FastAPI 进阶


1. 为什么需要环境配置管理?

1.1 问题场景

# ❌ 硬编码配置(开发/测试/生产混在一起)
DATABASE_URL = "postgresql://localhost:5432/mydb"
SECRET_KEY = "dev-secret-key"
DEBUG = True

# 上线时手动改?容易出错!

1.2 正确方案

.env(本地)           .env.test(测试)       .env.production(生产)
─────────────────     ─────────────────      ─────────────────
DATABASE_URL=...      DATABASE_URL=...       DATABASE_URL=...
SECRET_KEY=dev-...    SECRET_KEY=test-...    SECRET_KEY=生产密钥
DEBUG=true            DEBUG=false            DEBUG=false
LOG_LEVEL=debug      LOG_LEVEL=info         LOG_LEVEL=warning

2. Pydantic Settings 基础

2.1 安装

pip install pydantic-settings
# Pydantic v2 已内置,只需安装

2.2 基础配置

# config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
from functools import lru_cache

class Settings(BaseSettings):
    """应用配置"""
    model_config = SettingsConfigDict(
        env_file=".env",               # 读取 .env 文件
        env_file_encoding="utf-8",
        case_sensitive=False,          # 环境变量大小写不敏感
        extra="ignore",                # 忽略额外字段
    )

    # 应用信息
    app_name: str = "DaomanAPI"
    version: str = "1.0.0"
    debug: bool = False

    # 数据库
    database_url: str = "sqlite+aiosqlite:///./app.db"

    # JWT
    jwt_secret: str = "change-me-in-production"
    jwt_algorithm: str = "HS256"
    jwt_expire_minutes: int = 30
    jwt_refresh_expire_days: int = 30

    # Redis
    redis_url: str = "redis://localhost:6379/0"

    # CORS
    cors_origins: list[str] = ["http://localhost:3000"]

    # 日志
    log_level: str = "INFO"

    @property
    def is_production(self) -> bool:
        return self.environment == "production"


@lru_cache()
def get_settings() -> Settings:
    """单例模式,全局复用同一份配置"""
    return Settings()

3. 环境隔离

3.1 按环境加载不同 .env

# config.py
import os

class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=f".env.{os.getenv('ENV', 'development')}",  # 关键!
    )

    # 根据 ENV 变量自动加载 .env.development / .env.production
# .env.development
ENV=development
DATABASE_URL=sqlite+aiosqlite:///./dev.db
DEBUG=true
LOG_LEVEL=debug

# .env.production
ENV=production
DATABASE_URL=postgresql+asyncpg://user:pass@db.example.com/myapp
DEBUG=false
LOG_LEVEL=warning
JWT_SECRET=你的超长随机密钥(至少32字符)

# .env.test
ENV=test
DATABASE_URL=sqlite+aiosqlite:///./test.db

3.2 启动命令隔离

# 开发环境
uvicorn main:app --reload

# 测试环境
ENV=test pytest tests/

# 生产环境
ENV=production uvicorn main:app --host 0.0.0.0 --port 8000

4. 敏感信息管理

4.1 生产环境:环境变量优先

# 生产环境:环境变量(K8s / Docker Secret / 云平台管理)
# 导出到系统环境变量,优先级高于 .env 文件
# os.environ["JWT_SECRET"] = "从云平台读取"

class Settings(BaseSettings):
    # 如果系统环境变量存在,覆盖 .env 中的值
    jwt_secret: str = "default-fallback"

4.2 Docker 环境变量

# docker run -e JWT_SECRET="生产密钥" -e DATABASE_URL="postgresql://..."
docker run -d \
  -e ENV=production \
  -e JWT_SECRET="$(openssl rand -base64 32)" \
  -p 8000:8000 \
  my-fastapi-app

5. 在 FastAPI 中使用

5.1 主应用配置

# main.py
from fastapi import FastAPI
from config import get_settings

settings = get_settings()

app = FastAPI(
    title=settings.app_name,
    version=settings.version,
    debug=settings.debug,
)

# 日志配置
import logging
logging.basicConfig(level=settings.log_level)

# 路由注册、数据库初始化...

5.2 依赖注入配置

# dependencies.py
from functools import lru_cache

@lru_cache
def get_settings() -> Settings:
    return Settings()

# 路由中使用
@app.get("/config")
async def show_config(settings: Settings = Depends(get_settings)):
    return {
        "app_name": settings.app_name,
        "debug": settings.debug,
        # 不返回敏感字段!
    }

6. 完整配置结构

# .env.development 示例
ENV=development
APP_NAME=DaomanAPI
VERSION=1.0.0
DEBUG=true

# 数据库
DATABASE_URL=sqlite+aiosqlite:///./data/dev.db

# Redis
REDIS_URL=redis://localhost:6379/0

# JWT(仅开发用简单密钥)
JWT_SECRET=dev-only-secret-key-not-for-production
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=60

# CORS
CORS_ORIGINS=["http://localhost:3000","http://localhost:8080"]

# 外部服务
GITHUB_CLIENT_ID=dev_client_id
GITHUB_CLIENT_SECRET=dev_client_secret

# 日志
LOG_LEVEL=debug

7. 小结

# 三分钟上手 Pydantic Settings

# 1. 安装
pip install pydantic-settings

# 2. 定义配置类
class Settings(BaseSettings):
    app_name: str = "MyApp"
    jwt_secret: str = "change-me"

# 3. 读取环境变量和 .env 文件
settings = Settings()

# 4. 在 FastAPI 中使用
@app.get("/")
async def root(settings: Settings = Depends(get_settings)):
    return {"name": settings.app_name}

💡 最佳实践:所有配置项只定义在 Settings 类中一处,不要在代码里写死任何可变值(包括 DEBUG、数据库 URL 等)。这样测试时切换环境只需改环境变量。


🔗 扩展阅读