1. 告别手写os.getenv的时代
在Python项目中,配置管理一直是个让人头疼的问题。记得我刚入行时,项目里到处都是这样的代码:
import os DB_HOST = os.getenv('DB_HOST', 'localhost') DB_PORT = int(os.getenv('DB_PORT', '5432')) DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'这种写法至少有三大痛点:
- 类型转换需要手动处理(比如上面的int转换)
- 默认值分散在各处,难以统一管理
- 配置项多了之后,代码会变得冗长且难以维护
2. pydantic-settings核心功能解析
2.1 基础环境变量加载
pydantic-settings最基础的用法是通过继承BaseSettings类:
from pydantic import BaseModel from pydantic_settings import BaseSettings class DatabaseConfig(BaseModel): host: str = 'localhost' port: int = 5432 class Settings(BaseSettings): db: DatabaseConfig debug: bool = False这样就能自动从环境变量加载配置:
- DB_HOST → settings.db.host
- DB_PORT → settings.db.port
- DEBUG → settings.debug
2.2 嵌套配置与自动类型转换
pydantic-settings的强大之处在于对复杂嵌套配置的支持:
class AuthConfig(BaseModel): secret_key: str algorithm: str = 'HS256' expire_minutes: int = 30 class Settings(BaseSettings): database: DatabaseConfig auth: AuthConfig logging_level: str = 'INFO'环境变量会自动映射为:
- DATABASE_HOST
- DATABASE_PORT
- AUTH_SECRET_KEY
- AUTH_ALGORITHM
- AUTH_EXPIRE_MINUTES
- LOGGING_LEVEL
3. 高级配置技巧
3.1 多环境配置管理
实际项目中我们通常需要区分不同环境:
class Settings(BaseSettings): env_name: str = 'dev' class Config: env_file = '.env' env_file_encoding = 'utf-8' env_nested_delimiter = '__' @property def is_prod(self) -> bool: return self.env_name == 'prod'通过.env文件管理不同环境的配置:
# .env.dev DATABASE_HOST=localhost DATABASE_PORT=5432 # .env.prod DATABASE_HOST=db.prod.com DATABASE_PORT=54323.2 敏感信息处理
对于密码等敏感信息,pydantic-settings提供了SecretStr类型:
from pydantic import SecretStr class Settings(BaseSettings): db_password: SecretStr class Config: secrets_dir = '/run/secrets'这样密码可以存储在单独的文件中:
# /run/secrets/db_password my_super_secret_password4. 实战案例:Web应用配置
4.1 完整配置示例
from pydantic import BaseModel, SecretStr from pydantic_settings import BaseSettings class DatabaseConfig(BaseModel): host: str = 'localhost' port: int = 5432 user: str = 'postgres' password: SecretStr name: str = 'app_db' class RedisConfig(BaseModel): host: str = 'localhost' port: int = 6379 db: int = 0 class AuthConfig(BaseModel): secret_key: SecretStr algorithm: str = 'HS256' access_token_expire: int = 30 # minutes class Settings(BaseSettings): debug: bool = False database: DatabaseConfig redis: RedisConfig auth: AuthConfig class Config: env_file = '.env' env_nested_delimiter = '__' secrets_dir = '/run/secrets'4.2 配置使用示例
from fastapi import FastAPI from .config import Settings settings = Settings() app = FastAPI(debug=settings.debug) @app.get("/info") async def info(): return { "db_host": settings.database.host, "redis_port": settings.redis.port, "token_algo": settings.auth.algorithm }5. 常见问题解决方案
5.1 环境变量命名冲突
当多个配置项可能重名时,可以通过前缀解决:
class Settings(BaseSettings): class Config: env_prefix = 'APP_'这样环境变量需要以APP_开头:
- APP_DATABASE_HOST
- APP_REDIS_HOST
5.2 自定义环境变量名
对于某些特殊环境变量名,可以使用Field的alias:
from pydantic import Field class Settings(BaseSettings): db_host: str = Field(..., alias='DATABASE_SERVER')5.3 配置验证
pydantic的验证器同样适用:
from pydantic import validator class Settings(BaseSettings): port: int @validator('port') def validate_port(cls, v): if not 1024 <= v <= 65535: raise ValueError('Port must be between 1024 and 65535') return v6. 性能优化建议
6.1 避免重复加载
在Web应用中,通常只需要加载一次配置:
# config.py from pydantic_settings import BaseSettings class Settings(BaseSettings): ... settings = Settings() # app.py from .config import settings6.2 延迟加载
对于测试等场景,可以延迟加载配置:
class LazySettings: _instance = None def __init__(self): if not self._instance: self._instance = Settings() def __getattr__(self, name): return getattr(self._instance, name) settings = LazySettings()7. 测试策略
7.1 单元测试配置
import os from unittest import TestCase class TestConfig(TestCase): def setUp(self): os.environ['DATABASE_HOST'] = 'test.db' os.environ['DATABASE_PORT'] = '5432' def test_config(self): settings = Settings() self.assertEqual(settings.database.host, 'test.db') self.assertEqual(settings.database.port, 5432)7.2 使用pytest fixture
import pytest from pydantic_settings import BaseSettings @pytest.fixture def test_settings(): class TestSettings(BaseSettings): db_host: str = 'localhost' return TestSettings() def test_db_host(test_settings): assert test_settings.db_host == 'localhost'8. 与传统方案的对比
8.1 与python-dotenv对比
| 特性 | python-dotenv | pydantic-settings |
|---|---|---|
| 类型转换 | 需要手动处理 | 自动类型转换 |
| 嵌套配置 | 不支持 | 完善支持 |
| 环境变量优先级 | 无 | 可自定义 |
| 敏感信息处理 | 无特殊支持 | 内置SecretStr |
| 配置验证 | 无 | 内置验证器 |
8.2 与Django配置对比
Django的配置系统虽然强大,但存在以下问题:
- 全局单例模式,难以测试
- 缺乏类型提示
- 配置分散在settings.py和环境变量中
pydantic-settings在这些方面都有明显优势。
9. 迁移指南
9.1 从os.getenv迁移
- 识别项目中所有的os.getenv调用
- 创建对应的pydantic模型
- 逐步替换,保持向后兼容:
# 旧代码 DB_HOST = os.getenv('DB_HOST', 'localhost') # 过渡方案 class Settings(BaseSettings): db_host: str = 'localhost' settings = Settings() DB_HOST = settings.db_host # 兼容旧代码9.2 从ini/json配置迁移
- 将现有配置转换为环境变量或.env文件
- 定义对应的pydantic模型
- 使用pydantic的解析方法加载旧配置:
import json from pydantic import BaseModel class OldConfig(BaseModel): database_host: str @classmethod def from_json(cls, path): with open(path) as f: return cls(**json.load(f))10. 最佳实践总结
- 环境区分:使用不同.env文件管理各环境配置
- 敏感信息:使用SecretStr和secrets_dir管理
- 配置验证:充分利用pydantic的验证器
- 性能优化:避免重复加载配置
- 文档生成:利用Field的description自动生成配置文档
- 版本控制:.env.example加入版本控制,.env加入.gitignore
- 监控配置:对关键配置项添加监控和告警
一个完整的生产级配置示例:
from pydantic import BaseModel, Field, SecretStr, validator from pydantic_settings import BaseSettings from typing import Literal class DatabaseConfig(BaseModel): host: str = Field(..., description="Database server host") port: int = Field(5432, description="Database server port") user: str = Field('postgres', description="Database user") password: SecretStr = Field(..., description="Database password") pool_size: int = Field(10, description="Connection pool size") @validator('pool_size') def validate_pool_size(cls, v): if v < 1 or v > 100: raise ValueError('Pool size must be between 1 and 100') return v class Settings(BaseSettings): env: Literal['dev', 'test', 'prod'] = Field('dev', description="Runtime environment") database: DatabaseConfig = Field(..., description="Database configuration") class Config: env_file = '.env' env_nested_delimiter = '__' secrets_dir = '/run/secrets' @property def is_prod(self) -> bool: return self.env == 'prod'通过pydantic-settings,我们实现了:
- 类型安全的配置管理
- 自动化的环境变量加载
- 完善的文档和验证
- 优雅的敏感信息处理
- 跨环境的统一配置接口