pydantic_settings 使用方法

Pydantic Settings 完整使用指南

pydantic_settings 是 Pydantic V2 中用于管理应用程序配置的强大工具,支持从多种来源加载配置(环境变量、.env 文件、命令行参数等)。


✅ 安装

pip install pydantic-settings

🧩 基础用法

1. 最简单的配置类

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str = "MyApp"
    debug: bool = False
    port: int = 8000

settings = Settings()
print(settings.app_name)  # MyApp
print(settings.debug)     # False
print(settings.port)      # 8000

2. 从环境变量加载

# 设置环境变量
# export APP_NAME="ProductionApp"
# export DEBUG="true"
# export PORT="9000"

class Settings(BaseSettings):
    app_name: str = "MyApp"
    debug: bool = False
    port: int = 8000

settings = Settings()
print(settings.app_name)  # ProductionApp(从环境变量读取)
print(settings.debug)     # True
print(settings.port)      # 9000

📁 从 .env 文件加载

创建 .env 文件

APP_NAME=MyProductionApp
DEBUG=true
PORT=8080
DATABASE_URL=postgresql://localhost/mydb

配置类

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str
    debug: bool
    port: int
    database_url: str

    class Config:
        env_file = ".env"  # 指定 .env 文件
        env_file_encoding = "utf-8"  # 指定编码

settings = Settings()
print(settings.app_name)      # MyProductionApp
print(settings.debug)         # True
print(settings.port)          # 8080
print(settings.database_url)  # postgresql://localhost/mydb

🔐 字段验证与必填

使用 Field 定义字段

from pydantic_settings import BaseSettings
from pydantic import Field, validator

class Settings(BaseSettings):
    app_name: str = Field(default="MyApp", description="应用名称")
    api_key: str = Field(..., description="API密钥(必填)")  # ... 表示必填
    max_connections: int = Field(default=100, ge=1, le=1000)  # 1 <= max_connections <= 1000
    debug: bool = Field(default=False)

    class Config:
        env_file = ".env"

# 如果没有设置 API_KEY 环境变量,会报 ValidationError

自定义字段名

from pydantic_settings import BaseSettings
from pydantic import Field

class Settings(BaseSettings):
    database_url: str = Field(env="DB_URL")  # 从 DB_URL 环境变量读取
    secret_key: str = Field(env="SECRET_KEY")

# .env 文件
# DB_URL=postgresql://localhost/mydb
# SECRET_KEY=very-secret

🛠 配置模型设置

from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    app_name: str = "MyApp"
    debug: bool = False

    # 使用新的 SettingsConfigDict(推荐)
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        case_sensitive=False,  # 环境变量不区分大小写
        extra="forbid",        # 禁止额外字段
    )

🔐 敏感信息处理

from pydantic_settings import BaseSettings
from pydantic import SecretStr, SecretBytes

class Settings(BaseSettings):
    api_key: SecretStr
    password: SecretStr

settings = Settings(api_key="secret123", password="mypassword")

print(settings.api_key)                    # SecretStr('**********')
print(settings.api_key.get_secret_value()) # secret123

🧩 配置来源优先级

Pydantic Settings 按以下顺序加载配置(高 → 低):

  1. 构造函数参数(最高优先级)
  2. 环境变量
  3. .env 文件
  4. 字段默认值(最低优先级)
# 环境变量: APP_NAME="EnvApp", DEBUG="false"
class Settings(BaseSettings):
    app_name: str = "DefaultApp"
    debug: bool = True

    class Config:
        env_file = ".env"

# 构造函数传参覆盖所有其他来源
settings = Settings(app_name="CodeApp")  # app_name="CodeApp", debug=False

🧪 嵌套配置

from pydantic_settings import BaseSettings
from pydantic import BaseModel

class DatabaseConfig(BaseModel):
    host: str = "localhost"
    port: int = 5432
    name: str = "mydb"

class Settings(BaseSettings):
    app_name: str = "MyApp"
    database: DatabaseConfig = DatabaseConfig()

    class Config:
        env_prefix = "APP_"  # 环境变量前缀

# 环境变量: APP_DATABASE_HOST="prod-db", APP_DATABASE_PORT="5433"

📋 配置验证器

from pydantic_settings import BaseSettings
from pydantic import field_validator

class Settings(BaseSettings):
    host: str = "localhost"
    port: int = 8000

    @field_validator("port")
    def validate_port(cls, v):
        if v < 1 or v > 65535:
            raise ValueError("端口必须在 1-65535 之间")
        return v

    @field_validator("host")
    def validate_host(cls, v):
        if not v:
            raise ValueError("主机名不能为空")
        return v

🚀 实际应用示例

from pydantic_settings import BaseSettings
from pydantic import Field, PostgresDsn, validator
from typing import Optional

class Settings(BaseSettings):
    # 应用配置
    app_name: str = "FastAPI App"
    debug: bool = Field(default=False, env="DEBUG")
    version: str = "1.0.0"
    
    # 数据库配置
    database_url: PostgresDsn = Field(
        default="postgresql://localhost/defaultdb",
        env="DATABASE_URL"
    )
    
    # Redis配置
    redis_host: str = Field(default="localhost", env="REDIS_HOST")
    redis_port: int = Field(default=6379, env="REDIS_PORT")
    
    # API配置
    api_key: str = Field(..., env="API_KEY")  # 必填
    allowed_hosts: list[str] = Field(
        default=["localhost"],
        env="ALLOWED_HOSTS"
    )

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"
        case_sensitive = False

# 使用
settings = Settings()

# 验证配置
print(f"App: {settings.app_name}")
print(f"Debug: {settings.debug}")
print(f"DB: {settings.database_url}")
print(f"Redis: {settings.redis_host}:{settings.redis_port}")

🧩 自定义配置源(高级)

from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
from typing import Dict, Any

class CustomConfigSource(PydanticBaseSettingsSource):
    def __call__(self) -> Dict[str, Any]:
        # 从自定义源加载配置
        return {
            "app_name": "CustomApp",
            "custom_field": "custom_value"
        }

class Settings(BaseSettings):
    app_name: str = "DefaultApp"
    custom_field: str = "default"

    def settings_customise_sources(
        cls,
        settings_cls,
        init_settings,
        env_settings,
        dotenv_settings,
        file_secret_settings,
    ):
        return (
            init_settings,
            env_settings,
            dotenv_settings,
            CustomConfigSource(settings_cls),  # 自定义源
            file_secret_settings,
        )

✅ 最佳实践

  1. 使用 .env 文件管理开发环境配置
  2. 敏感信息使用 SecretStr 类型
  3. 必填字段使用 Field(...)
  4. 类型验证使用 Field(ge=1, le=100) 等参数
  5. 配置类实例化一次,全局使用
  6. 使用 SettingsConfigDict 替代旧的 Config
# config.py - 推荐的配置管理方式
from pydantic_settings import BaseSettings
from pydantic import Field

class Settings(BaseSettings):
    app_name: str = "MyApp"
    debug: bool = Field(default=False, env="DEBUG")

    model_config = {
        "env_file": ".env",
        "env_file_encoding": "utf-8",
        "case_sensitive": False,
    }

settings = Settings()

# main.py
from config import settings
print(settings.app_name)

这样就完成了 Pydantic Settings 的完整使用指南!

settings_customise_sourcespydantic_settings.BaseSettings 中一个非常强大的方法,允许你自定义配置源的加载顺序和方式。通过重写这个方法,你可以添加自定义的配置源(如 YAML 文件、JSON 文件、数据库、远程配置中心等)。


✅ 基本语法

from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic_settings.sources import (
    PydanticBaseSettingsSource,
    InitSettingsSource,
    EnvSettingsSource,
    DotEnvSettingsSource,
)

class CustomSource(PydanticBaseSettingsSource):
    def get_field_value(self, field, field_name):
        # 实现获取字段值的逻辑
        pass

class MySettings(BaseSettings):
    some_field: str = "default"

    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: EnvSettingsSource,
        dotenv_settings: DotEnvSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        # 返回配置源的元组,顺序即为优先级(从高到低)
        return (
            init_settings,      # 构造函数传入的参数(最高优先级)
            env_settings,       # 环境变量
            dotenv_settings,    # .env 文件
            # 在这里添加自定义配置源
            CustomSource(settings_cls),
            file_secret_settings,  # 文件密钥(最低优先级)
        )

🧩 示例 1:从 YAML 文件加载配置

import yaml
from pathlib import Path
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
from typing import Dict, Any

class YamlConfigSettingsSource(PydanticBaseSettingsSource):
    def __init__(self, settings_cls: type[BaseSettings], yaml_file: str = "config.yaml"):
        self.yaml_file = Path(yaml_file)
        super().__init__(settings_cls)

    def get_field_value(self, field: Any, field_name: str) -> tuple[Any, str, bool]:
        """
        返回: (value, key, is_complex)
        """
        if self.yaml_file.exists():
            with open(self.yaml_file, 'r', encoding='utf-8') as f:
                data = yaml.safe_load(f) or {}
            field_value = data.get(field_name)
            return field_value, field_name, False  # is_complex=False
        return None, field_name, False

    def __call__(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {}
        if self.yaml_file.exists():
            with open(self.yaml_file, 'r', encoding='utf-8') as f:
                data = yaml.safe_load(f) or {}
            d = {k: v for k, v in data.items() if v is not None}
        return d

class Settings(BaseSettings):
    app_name: str = "DefaultApp"
    debug: bool = False
    database_url: str = "sqlite:///:memory:"

    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ):
        return (
            init_settings,                    # 构造函数参数
            env_settings,                     # 环境变量
            dotenv_settings,                  # .env 文件
            YamlConfigSettingsSource(settings_cls, yaml_file="config.yaml"),  # 自定义 YAML 源
            file_secret_settings,             # 文件密钥
        )

# 使用
settings = Settings()
print(settings.model_dump())

config.yaml

app_name: "MyAwesomeApp"
debug: true
database_url: "postgresql://localhost/mydb"

🧩 示例 2:从 JSON 文件加载配置

import json
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
from typing import Dict, Any

class JsonConfigSettingsSource(PydanticBaseSettingsSource):
    def __init__(self, settings_cls: type[BaseSettings], json_file: str = "config.json"):
        self.json_file = json_file
        super().__init__(settings_cls)

    def get_field_value(self, field: Any, field_name: str) -> tuple[Any, str, bool]:
        try:
            with open(self.json_file, 'r', encoding='utf-8') as f:
                data = json.load(f)
            field_value = data.get(field_name)
            return field_value, field_name, False
        except FileNotFoundError:
            return None, field_name, False

    def __call__(self) -> Dict[str, Any]:
        try:
            with open(self.json_file, 'r', encoding='utf-8') as f:
                data = json.load(f)
            return {k: v for k, v in data.items() if v is not None}
        except FileNotFoundError:
            return {}

class Settings(BaseSettings):
    app_name: str = "DefaultApp"
    version: str = "1.0.0"

    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ):
        return (
            init_settings,
            env_settings,
            JsonConfigSettingsSource(settings_cls, json_file="config.json"),
            dotenv_settings,
            file_secret_settings,
        )

# config.json
# {
#   "app_name": "MyApp",
#   "version": "2.0.0"
# }

🧩 示例 3:从数据库加载配置(概念演示)

from pydantic_settings import BaseSettings, PydanticBaseSettingsSource
from typing import Dict, Any

class DatabaseConfigSettingsSource(PydanticBaseSettingsSource):
    def __init__(self, settings_cls: type[BaseSettings], db_connection=None):
        self.db = db_connection or self._connect_to_db()
        super().__init__(settings_cls)

    def _connect_to_db(self):
        # 模拟数据库连接
        return {"app_name": "DBApp", "debug": False}

    def get_field_value(self, field: Any, field_name: str) -> tuple[Any, str, bool]:
        # 从数据库查询配置
        value = self.db.get(field_name)
        return value, field_name, False

    def __call__(self) -> Dict[str, Any]:
        # 返回所有配置
        return self.db

class Settings(BaseSettings):
    app_name: str = "DefaultApp"
    debug: bool = False

    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ):
        return (
            init_settings,
            env_settings,
            DatabaseConfigSettingsSource(settings_cls),
            dotenv_settings,
            file_secret_settings,
        )

🧠 优先级顺序

settings_customise_sources 返回的元组中,越靠前的源优先级越高。例如:

return (
    init_settings,        # 优先级 1(最高)
    env_settings,         # 优先级 2
    custom_yaml_source,   # 优先级 3
    dotenv_settings,      # 优先级 4
    file_secret_settings, # 优先级 5(最低)
)

如果 app_name 在环境变量和 YAML 文件中都有定义,会使用环境变量的值。


📌 总结

方法 用途
get_field_value 获取单个字段值(可选实现)
__call__ 返回整个配置字典(必须实现)
settings_customise_sources 定义配置源的加载顺序和优先级

这个功能让你可以灵活地从任何数据源加载配置,非常适合微服务、云原生应用或需要动态配置的场景。

posted @ 2025-10-08 16:24  春水鸿鹄  阅读(63)  评论(0)    收藏  举报