在项目从本地开发走向线上部署的过程中,「配置」往往是第一个让人头疼的问题:数据库连接串、Redis 地址、JWT 密钥、第三方 API Key……如果把这些值硬编码在代码里,每次切换环境都要改代码,既不安全也容易出错。FastAPI 本身不强制配置方案,但官方推荐使用 pydantic-settings(Pydantic v2 时代的 BaseSettings)来统一管理。本文系统讲解如何在 FastAPI 项目中用 pydantic-settings 构建清晰、可测试、环境隔离的配置体系。
为什么选择 pydantic-settings
传统做法是用 os.getenv("KEY") 散落在各处读取环境变量,存在几个明显问题:
- 类型缺失:
os.getenv 返回的永远是 str 或 None,端口号、布尔开关需要手动转换,稍不注意就报错。
- 校验滞后:缺少某个关键变量时,往往要等到运行到对应代码才发现,启动阶段没有任何提示。
- 难以测试:测试用例里要反复
os.environ["KEY"] = ... 设置和清理,容易污染全局状态。
pydantic-settings 借助 Pydantic 的类型系统,在应用启动时就完成类型转换、默认值填充和必填校验,缺了关键配置直接启动失败,把问题暴露在最早阶段。
安装与最简示例
pydantic-settings 是独立包,需要单独安装:
1
| pip install pydantic-settings
|
定义一个继承 BaseSettings 的配置类,把每个配置项声明为带类型注解的字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): app_name: str = "My FastAPI App" debug: bool = False
database_url: str
redis_url: str = "redis://localhost:6379/0"
jwt_secret: str jwt_algorithm: str = "HS256" jwt_expire_minutes: int = 60
model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=False, )
settings = Settings()
|
对应的 .env 文件:
1 2 3 4
| DEBUG=true DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/mydb JWT_SECRET=super-secret-key-change-in-production JWT_EXPIRE_MINUTES=120
|
Settings() 实例化时会按以下优先级读取:环境变量 > .env 文件 > 类中默认值。DEBUG 会被自动转换为布尔值,JWT_EXPIRE_MINUTES 会被转为 int,类型转换由 Pydantic 自动完成。
在 FastAPI 中使用配置
方式一:模块级单例(最简单)
在 config.py 中实例化后,直接在各处导入使用:
1 2 3 4 5 6 7 8 9 10 11 12 13
| from pydantic_settings import BaseSettings
class Settings(BaseSettings): database_url: str jwt_secret: str debug: bool = False
model_config = SettingsConfigDict(env_file=".env")
settings = Settings()
|
1 2 3 4 5 6 7 8 9 10
| from fastapi import FastAPI from .config import settings
app = FastAPI(title=settings.app_name, debug=settings.debug)
@app.get("/info") async def info(): return {"app": settings.app_name, "debug": settings.debug}
|
这种方式适合中小型项目,缺点是配置在测试时不好替换(需要 mock 或改环境变量)。
方式二:通过依赖注入(推荐)
更灵活的做法是用 lru_cache 把配置作为依赖注入,便于测试时覆盖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| from functools import lru_cache from fastapi import Depends, FastAPI from pydantic_settings import BaseSettings
class Settings(BaseSettings): app_name: str = "My API" database_url: str jwt_secret: str
model_config = SettingsConfigDict(env_file=".env")
@lru_cache def get_settings() -> Settings: """用 lru_cache 缓存,整个应用生命周期内只实例化一次""" return Settings()
app = FastAPI()
@app.get("/info") async def info(settings: Settings = Depends(get_settings)): return {"app": settings.app_name}
|
lru_cache 保证 Settings() 只在第一次请求时构造一次,后续直接返回缓存。测试时可以调用 get_settings.cache_clear() 重置,或用 app.dependency_overrides 覆盖:
1 2 3 4 5 6 7 8 9 10
| from fastapi.testclient import TestClient from .config import Settings, get_settings
def get_test_settings(): return Settings(database_url="sqlite:///:memory:", jwt_secret="test-secret")
app.dependency_overrides[get_settings] = get_test_settings client = TestClient(app)
|
这样测试用例完全不依赖真实环境变量,干净且可控。
嵌套配置
当配置项很多时,把它们拍平在一个类里会很难维护。pydantic-settings 支持嵌套模型,配合环境变量前缀可以让结构更清晰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| from pydantic import BaseModel from pydantic_settings import BaseSettings, SettingsConfigDict
class DatabaseSettings(BaseModel): url: str pool_size: int = 10 max_overflow: int = 20 echo: bool = False
class RedisSettings(BaseModel): url: str = "redis://localhost:6379/0" max_connections: int = 50
class Settings(BaseSettings): database: DatabaseSettings redis: RedisSettings = RedisSettings() jwt_secret: str
model_config = SettingsConfigDict( env_file=".env", env_nested_delimiter="__", )
|
对应的 .env:
1 2 3 4
| DATABASE__URL=postgresql://user:pass@localhost:5432/mydb DATABASE__POOL_SIZE=20 DATABASE__ECHO=true JWT_SECRET=my-secret
|
env_nested_delimiter="__" 告诉 pydantic-settings 把 DATABASE__URL 解析为 database.url。这样不同模块的配置互不干扰,结构一目了然。
多环境切换
实际项目通常有 dev / test / prod 多套环境。常见做法是用多个 .env 文件,通过 ENV 环境变量决定加载哪一个:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import os from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): app_name: str database_url: str debug: bool = False
model_config = SettingsConfigDict( env_file=(f".env.{os.getenv('ENV', 'dev')}", ".env"), env_file_encoding="utf-8", )
|
env_file 接受一个元组,后面的文件优先级更低,作为兜底默认值。这样你可以:
.env.dev 放开发环境的配置(debug=true、本地数据库)
.env.prod 放生产配置(debug=false、线上数据库)
.env 放所有环境共享的默认值
启动时通过 ENV=prod uvicorn app.main:app 切换。注意 .env.prod 这类文件不应提交到 Git,应在 .gitignore 中排除,敏感信息只存在于服务器环境变量中。
校验与默认值进阶
必填校验
没有默认值的字段是必填的。如果启动时没有提供,实例化会直接抛出 ValidationError:
1 2 3 4 5 6 7
| class Settings(BaseSettings): jwt_secret: str
|
这种「快速失败」行为非常有价值——避免应用带着不完整配置跑到线上。
枚举与约束
可以结合 Pydantic 的 Field 和 Enum 进一步约束取值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from enum import Enum from pydantic import Field from pydantic_settings import BaseSettings
class Env(str, Enum): dev = "dev" test = "test" prod = "prod"
class Settings(BaseSettings): env: Env = Env.dev log_level: str = Field("INFO", pattern="^(DEBUG|INFO|WARNING|ERROR)$") port: int = Field(8000, ge=1, le=65535)
|
传入非法值时启动就会失败,等于在配置层就建立了一道校验防线。
敏感信息保护
1. 永远不要把密钥写进代码
.env 文件应加入 .gitignore,绝不提交。可以提交一个 .env.example 作为模板:
1 2 3 4
| # .env.example —— 提交到仓库作为模板 DEBUG=false DATABASE_URL=postgresql://user:pass@localhost:5432/mydb JWT_SECRET=change-me-in-production
|
2. 使用 SecretStr 防止日志泄漏
对于 JWT 密钥、数据库密码等,可以用 SecretStr 类型,避免在日志或调试输出中明文打印:
1 2 3 4 5 6 7 8 9 10 11 12 13
| from pydantic import SecretStr from pydantic_settings import BaseSettings
class Settings(BaseSettings): jwt_secret: SecretStr db_password: SecretStr
settings = Settings(jwt_secret="my-secret", db_password="p@ssw0rd")
print(settings.jwt_secret) print(settings.jwt_secret.get_secret_value())
|
repr 和 str 输出的都是 **********,只有显式调用 get_secret_value() 才能拿到真实值,能有效防止日志中误打印密钥。
3. 生产环境用真正的环境变量
在 Docker、Kubernetes 或云平台上,不要依赖 .env 文件,而是直接通过平台机制注入环境变量。pydantic-settings 会自动读取进程环境变量,无需额外配置:
1 2 3
| ENV DATABASE_URL=postgresql://prod-db:5432/app CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]
|
或在 docker run 时传入:
1
| docker run -e JWT_SECRET=$JWT_SECRET -e DATABASE_URL=$DATABASE_URL my-app
|
小结
pydantic-settings 让 FastAPI 的配置管理变得类型安全、可测试、环境隔离:
- 继承
BaseSettings 声明配置项,自动完成类型转换与校验。
model_config 控制 .env 文件加载、嵌套分隔符等行为。
- 依赖注入 +
lru_cache 是推荐的集成方式,便于单元测试时覆盖。
- 嵌套模型 + 前缀 适合大型项目,让配置结构清晰可维护。
- 多
.env 文件 + ENV 变量 实现开发/测试/生产环境隔离。
SecretStr 保护敏感信息,避免日志泄漏。
- 生产环境直接用平台环境变量,
.env 仅用于本地开发。
掌握这套配置体系后,你可以从容应对从本地开发到容器化部署的全流程,让配置不再成为项目的隐患。