跳转到内容

设置管理

Pydantic Settings 提供了可选的 Pydantic 功能,用于从环境变量或秘密文件中加载设置或配置类。

安装

安装很简单

pip install pydantic-settings

用法

如果你创建一个继承自 BaseSettings 的模型,模型初始化器将尝试通过读取环境变量来确定未作为关键字参数传递的任何字段的值。(如果未设置匹配的环境变量,仍将使用默认值。)

这使得以下操作变得容易

  • 创建一个清晰定义、带有类型提示的应用程序配置类
  • 自动从环境变量中读取配置修改
  • 在需要时(例如在单元测试中)手动覆盖初始化器中的特定设置

例如:

from collections.abc import Callable
from typing import Any

from pydantic import (
    AliasChoices,
    AmqpDsn,
    BaseModel,
    Field,
    ImportString,
    PostgresDsn,
    RedisDsn,
)

from pydantic_settings import BaseSettings, SettingsConfigDict


class SubModel(BaseModel):
    foo: str = 'bar'
    apple: int = 1


class Settings(BaseSettings):
    auth_key: str = Field(validation_alias='my_auth_key')  # (1)!

    api_key: str = Field(alias='my_api_key')  # (2)!

    redis_dsn: RedisDsn = Field(
        'redis://user:pass@localhost:6379/1',
        validation_alias=AliasChoices('service_redis_dsn', 'redis_url'),  # (3)!
    )
    pg_dsn: PostgresDsn = 'postgres://user:pass@localhost:5432/foobar'
    amqp_dsn: AmqpDsn = 'amqp://user:pass@localhost:5672/'

    special_function: ImportString[Callable[[Any], Any]] = 'math.cos'  # (4)!

    # to override domains:
    # export my_prefix_domains='["foo.com", "bar.com"]'
    domains: set[str] = set()

    # to override more_settings:
    # export my_prefix_more_settings='{"foo": "x", "apple": 1}'
    more_settings: SubModel = SubModel()

    model_config = SettingsConfigDict(env_prefix='my_prefix_')  # (5)!


print(Settings().model_dump())
"""
{
    'auth_key': 'xxx',
    'api_key': 'xxx',
    'redis_dsn': RedisDsn('redis://user:pass@localhost:6379/1'),
    'pg_dsn': PostgresDsn('postgres://user:pass@localhost:5432/foobar'),
    'amqp_dsn': AmqpDsn('amqp://user:pass@localhost:5672/'),
    'special_function': math.cos,
    'domains': set(),
    'more_settings': {'foo': 'bar', 'apple': 1},
}
"""
  1. 环境变量名通过 validation_alias 覆盖。在这种情况下,将读取环境变量 my_auth_key 而不是 auth_key

    有关更多信息,请查看Field 文档

  2. 环境变量名通过 alias 覆盖。在这种情况下,环境变量 my_api_key 将用于验证和序列化,而不是 api_key

    有关更多信息,请查看Field 文档

  3. AliasChoices 类允许为单个字段拥有多个环境变量名。将使用找到的第一个环境变量。

    有关更多信息,请查看别名选择的文档

  4. ImportString 类允许从字符串导入对象。在这种情况下,将读取环境变量 special_function 并导入函数 math.cos

  5. env_prefix 配置设置允许为所有环境变量设置前缀。

    有关更多信息,请查看环境变量名称文档

默认值验证

与 pydantic BaseModel 不同,BaseSettings 字段的默认值在默认情况下是经过验证的。你可以通过在 model_config 中设置 validate_default=False 或在字段级别通过 Field(validate_default=False) 来禁用此行为

from pydantic import Field

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(validate_default=False)

    # default won't be validated
    foo: int = 'test'


print(Settings())
#> foo='test'


class Settings1(BaseSettings):
    # default won't be validated
    foo: int = Field('test', validate_default=False)


print(Settings1())
#> foo='test'

有关更多信息,请查看默认值验证

环境变量名称

默认情况下,环境变量名称与字段名称相同。

你可以通过设置 env_prefix 配置或通过实例化时的 _env_prefix 关键字参数来更改所有环境变量的前缀

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_prefix='my_prefix_')

    auth_key: str = 'xxx'  # will be read from `my_prefix_auth_key`

注意

默认的 env_prefix''(空字符串)。env_prefix 不仅用于环境变量设置,也用于 dotenv 文件、秘密和其他来源。

如果你想更改单个字段的环境变量名称,可以使用别名。

有两种方法可以实现这一点

  • 使用 Field(alias=...)(参见上面的 api_key
  • 使用 Field(validation_alias=...)(参见上面的 auth_key

有关别名的更多信息,请查看Field 别名文档

env_prefix 不适用于带有别名的字段。这意味着环境变量名称与字段别名相同

from pydantic import Field

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_prefix='my_prefix_')

    foo: str = Field('xxx', alias='FooAlias')  # (1)!
  1. env_prefix 将被忽略,值将从 FooAlias 环境变量中读取。

大小写敏感性

默认情况下,环境变量名称不区分大小写。

如果你想让环境变量名称区分大小写,可以设置 case_sensitive 配置

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(case_sensitive=True)

    redis_host: str = 'localhost'

case_sensitiveTrue 时,环境变量名称必须与字段名称(可选带前缀)匹配,因此在这个例子中,redis_host 只能通过 export redis_host 修改。如果你想将环境变量名称全部大写,你也应该将属性名称全部大写。你仍然可以通过 Field(validation_alias=...) 随意命名环境变量。

大小写敏感性也可以通过实例化时的 _case_sensitive 关键字参数设置。

对于嵌套模型,case_sensitive 设置将应用于所有嵌套模型。

import os

from pydantic import BaseModel, ValidationError

from pydantic_settings import BaseSettings


class RedisSettings(BaseModel):
    host: str
    port: int


class Settings(BaseSettings, case_sensitive=True):
    redis: RedisSettings


os.environ['redis'] = '{"host": "localhost", "port": 6379}'
print(Settings().model_dump())
#> {'redis': {'host': 'localhost', 'port': 6379}}
os.environ['redis'] = '{"HOST": "localhost", "port": 6379}'  # (1)!
try:
    Settings()
except ValidationError as e:
    print(e)
    """
    1 validation error for Settings
    redis.host
      Field required [type=missing, input_value={'HOST': 'localhost', 'port': 6379}, input_type=dict]
        For further information visit https://errors.pydantic.dev/2/v/missing
    """
  1. 请注意,host 字段未找到,因为环境变量名称是 HOST(全大写)。

注意

在 Windows 上,Python 的 os 模块总是将环境变量视为不区分大小写,因此 case_sensitive 配置设置将不起作用 - 设置将始终忽略大小写进行更新。

解析环境变量值

默认情况下,环境变量按字面解析,包括值为空的情况。你可以通过将 env_ignore_empty 配置设置为 True 来选择忽略空环境变量。如果你更倾向于使用字段的默认值而不是环境变量中的空值,这会很有用。

对于大多数简单的字段类型(例如 intfloatstr 等),环境变量值的解析方式与直接传递给初始化器(作为字符串)时相同。

listsetdict 和子模型等复杂类型,通过将环境变量的值视为 JSON 编码字符串来从环境中填充。

填充嵌套复杂变量的另一种方法是使用 env_nested_delimiter 配置设置配置你的模型,然后使用一个指向嵌套模块字段名称的环境变量。它的作用是简单地将你的变量分解为嵌套模型或字典。因此,如果你定义一个变量 FOO__BAR__BAZ=123,它会将其转换为 FOO={'BAR': {'BAZ': 123}}。如果你有多个具有相同结构的变量,它们将被合并。

注意

子模型必须继承自 pydantic.BaseModel,否则 pydantic-settings 将初始化子模型,单独收集子模型字段的值,你可能会得到意想不到的结果。

例如,给定以下环境变量

# your environment
export V0=0
export SUB_MODEL='{"v1": "json-1", "v2": "json-2"}'
export SUB_MODEL__V2=nested-2
export SUB_MODEL__V3=3
export SUB_MODEL__DEEP__V4=v4

你可以将它们加载到以下设置模型中

from pydantic import BaseModel

from pydantic_settings import BaseSettings, SettingsConfigDict


class DeepSubModel(BaseModel):  # (1)!
    v4: str


class SubModel(BaseModel):  # (2)!
    v1: str
    v2: bytes
    v3: int
    deep: DeepSubModel


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_nested_delimiter='__')

    v0: str
    sub_model: SubModel


print(Settings().model_dump())
"""
{
    'v0': '0',
    'sub_model': {'v1': 'json-1', 'v2': b'nested-2', 'v3': 3, 'deep': {'v4': 'v4'}},
}
"""
  1. 子模型必须继承自 pydantic.BaseModel

  2. 子模型必须继承自 pydantic.BaseModel

env_nested_delimiter 可以通过 model_config(如上所示)或通过实例化时的 _env_nested_delimiter 关键字参数进行配置。

默认情况下,环境变量通过 env_nested_delimiter 分割成任意深度的嵌套字段。你可以使用 env_nested_max_split 配置设置来限制嵌套字段的深度。一个常见的用例是两级深度设置,其中 env_nested_delimiter(通常是单个 _)可能是模型字段名称的子字符串。例如

# your environment
export GENERATION_LLM_PROVIDER='anthropic'
export GENERATION_LLM_API_KEY='your-api-key'
export GENERATION_LLM_API_VERSION='2024-03-15'

你可以将它们加载到以下设置模型中

from pydantic import BaseModel

from pydantic_settings import BaseSettings, SettingsConfigDict


class LLMConfig(BaseModel):
    provider: str = 'openai'
    api_key: str
    api_type: str = 'azure'
    api_version: str = '2023-03-15-preview'


class GenerationConfig(BaseSettings):
    model_config = SettingsConfigDict(
        env_nested_delimiter='_', env_nested_max_split=1, env_prefix='GENERATION_'
    )

    llm: LLMConfig
    ...


print(GenerationConfig().model_dump())
"""
{
    'llm': {
        'provider': 'anthropic',
        'api_key': 'your-api-key',
        'api_type': 'azure',
        'api_version': '2024-03-15',
    }
}
"""

如果没有设置 env_nested_max_split=1GENERATION_LLM_API_KEY 将被解析为 llm.api.key 而不是 llm.api_key,这将引发 ValidationError

嵌套环境变量优先于顶级环境变量 JSON(例如,在上面的示例中,SUB_MODEL__V2 优先于 SUB_MODEL)。

你还可以通过提供自己的源类来填充复杂类型。

import json
import os
from typing import Any

from pydantic.fields import FieldInfo

from pydantic_settings import (
    BaseSettings,
    EnvSettingsSource,
    PydanticBaseSettingsSource,
)


class MyCustomSource(EnvSettingsSource):
    def prepare_field_value(
        self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool
    ) -> Any:
        if field_name == 'numbers':
            return [int(x) for x in value.split(',')]
        return json.loads(value)


class Settings(BaseSettings):
    numbers: list[int]

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        return (MyCustomSource(settings_cls),)


os.environ['numbers'] = '1,2,3'
print(Settings().model_dump())
#> {'numbers': [1, 2, 3]}

禁用 JSON 解析

Pydantic-settings 默认将环境变量中的复杂类型解析为 JSON 字符串。如果你想为某个字段禁用此行为并在你自己的验证器中解析值,你可以用 NoDecode 标注该字段

import os
from typing import Annotated

from pydantic import field_validator

from pydantic_settings import BaseSettings, NoDecode


class Settings(BaseSettings):
    numbers: Annotated[list[int], NoDecode]  # (1)!

    @field_validator('numbers', mode='before')
    @classmethod
    def decode_numbers(cls, v: str) -> list[int]:
        return [int(x) for x in v.split(',')]


os.environ['numbers'] = '1,2,3'
print(Settings().model_dump())
#> {'numbers': [1, 2, 3]}
  1. NoDecode 注释禁用了 numbers 字段的 JSON 解析。将调用 decode_numbers 字段验证器来解析值。

你还可以通过将 enable_decoding 配置设置为 False 来禁用所有字段的 JSON 解析

import os

from pydantic import field_validator

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(enable_decoding=False)

    numbers: list[int]

    @field_validator('numbers', mode='before')
    @classmethod
    def decode_numbers(cls, v: str) -> list[int]:
        return [int(x) for x in v.split(',')]


os.environ['numbers'] = '1,2,3'
print(Settings().model_dump())
#> {'numbers': [1, 2, 3]}

你可以通过用 ForceDecode 注释来强制对某个字段进行 JSON 解析。这将绕过 enable_decoding 配置设置

import os
from typing import Annotated

from pydantic import field_validator

from pydantic_settings import BaseSettings, ForceDecode, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(enable_decoding=False)

    numbers: Annotated[list[int], ForceDecode]
    numbers1: list[int]  # (1)!

    @field_validator('numbers1', mode='before')
    @classmethod
    def decode_numbers1(cls, v: str) -> list[int]:
        return [int(x) for x in v.split(',')]


os.environ['numbers'] = '["1","2","3"]'
os.environ['numbers1'] = '1,2,3'
print(Settings().model_dump())
#> {'numbers': [1, 2, 3], 'numbers1': [1, 2, 3]}
  1. numbers1 字段未用 ForceDecode 注释,因此它不会被解析为 JSON。我们需要提供一个自定义验证器来解析该值。

嵌套模型默认局部更新

默认情况下,Pydantic settings 不允许对嵌套模型默认对象进行局部更新。可以通过将 nested_model_default_partial_update 标志设置为 True 来覆盖此行为,这将允许对嵌套模型默认对象字段进行局部更新。

import os

from pydantic import BaseModel

from pydantic_settings import BaseSettings, SettingsConfigDict


class SubModel(BaseModel):
    val: int = 0
    flag: bool = False


class SettingsPartialUpdate(BaseSettings):
    model_config = SettingsConfigDict(
        env_nested_delimiter='__', nested_model_default_partial_update=True
    )

    nested_model: SubModel = SubModel(val=1)


class SettingsNoPartialUpdate(BaseSettings):
    model_config = SettingsConfigDict(
        env_nested_delimiter='__', nested_model_default_partial_update=False
    )

    nested_model: SubModel = SubModel(val=1)


# Apply a partial update to the default object using environment variables
os.environ['NESTED_MODEL__FLAG'] = 'True'

# When partial update is enabled, the existing SubModel instance is updated
# with nested_model.flag=True change
assert SettingsPartialUpdate().model_dump() == {
    'nested_model': {'val': 1, 'flag': True}
}

# When partial update is disabled, a new SubModel instance is instantiated
# with nested_model.flag=True change
assert SettingsNoPartialUpdate().model_dump() == {
    'nested_model': {'val': 0, 'flag': True}
}

Dotenv (.env) 支持

Dotenv 文件(通常命名为 .env)是一种常见的模式,可以轻松地以平台无关的方式使用环境变量。

dotenv 文件遵循所有环境变量的相同一般原则,它看起来像这样

.env
# ignore comment
ENVIRONMENT="production"
REDIS_ADDRESS=localhost:6379
MEANING_OF_LIFE=42
MY_VAR='Hello world'

一旦你的 .env 文件中填充了变量,pydantic 支持通过两种方式加载它

  1. BaseSettings 类的 model_config 上设置 env_file(如果你不希望使用操作系统的默认编码,也可以设置 env_file_encoding
    from pydantic_settings import BaseSettings, SettingsConfigDict
    
    
    class Settings(BaseSettings):
        model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8')
    
  2. 使用 _env_file 关键字参数(如果需要,还可以使用 _env_file_encoding)实例化 BaseSettings 派生类
    from pydantic_settings import BaseSettings, SettingsConfigDict
    
    
    class Settings(BaseSettings):
        model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8')
    
    
    settings = Settings(_env_file='prod.env', _env_file_encoding='utf-8')
    
    在任何一种情况下,传递参数的值可以是任何有效的路径或文件名,无论是绝对路径还是相对于当前工作目录的相对路径。从那里,pydantic 将为你处理所有事情,加载你的变量并验证它们。

注意

如果为 env_file 指定了文件名,Pydantic 将只检查当前工作目录,而不会检查任何父目录以查找 .env 文件。

即使使用 dotenv 文件,pydantic 仍会读取环境变量和 dotenv 文件,**环境变量始终优先于从 dotenv 文件加载的值**。

通过实例化时的 _env_file 关键字参数(方法 2)传递文件路径将覆盖 model_config 类上设置的值(如果有)。如果上述代码片段结合使用,将加载 prod.env,而 .env 将被忽略。

如果你需要加载多个 dotenv 文件,可以将多个文件路径作为元组或列表传递。文件将按顺序加载,每个文件都会覆盖前一个文件。

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        # `.env.prod` takes priority over `.env`
        env_file=('.env', '.env.prod')
    )

你还可以使用关键字参数 override 来告诉 Pydantic 完全不加载任何文件(即使在 model_config 类中设置了文件),方法是将 None 作为实例化关键字参数传递,例如 settings = Settings(_env_file=None)

因为 python-dotenv 用于解析文件,所以可以使用类似 bash 的语义,例如 export,这(取决于你的操作系统和环境)可能允许你的 dotenv 文件也与 source 一起使用,有关更多详细信息,请参阅 python-dotenv 的文档

Pydantic 设置在 dotenv 文件的情况下考虑 extra 配置。这意味着如果你在 model_config 上设置了 extra=forbid默认值),并且你的 dotenv 文件包含一个未在设置模型中定义的字段的条目,它将在设置构建中引发 ValidationError

为了与 pydantic 1.x BaseSettings 兼容,你应该使用 extra=ignore

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file='.env', extra='ignore')

注意

Pydantic 设置从 dotenv 文件加载所有值并将其传递给模型,无论模型的 env_prefix 如何。因此,如果你在 dotenv 文件中提供了额外的值,无论它们是否以 env_prefix 开头,都会引发 ValidationError

命令行支持

Pydantic 设置提供了集成的 CLI 支持,使得使用 Pydantic 模型快速定义 CLI 应用程序变得容易。Pydantic 设置 CLI 主要有两种用例

  1. 当使用 CLI 覆盖 Pydantic 模型中的字段时。
  2. 当使用 Pydantic 模型定义 CLI 时。

默认情况下,体验是针对用例 #1 定制的,并建立在解析环境变量中建立的基础之上。如果你的用例主要属于 #2,你可能希望启用创建 CLI 应用程序末尾概述的大部分默认设置。

基础知识

首先,让我们回顾一下解析环境变量中提出的示例,但使用 Pydantic 设置 CLI

import sys

from pydantic import BaseModel

from pydantic_settings import BaseSettings, SettingsConfigDict


class DeepSubModel(BaseModel):
    v4: str


class SubModel(BaseModel):
    v1: str
    v2: bytes
    v3: int
    deep: DeepSubModel


class Settings(BaseSettings):
    model_config = SettingsConfigDict(cli_parse_args=True)

    v0: str
    sub_model: SubModel


sys.argv = [
    'example.py',
    '--v0=0',
    '--sub_model={"v1": "json-1", "v2": "json-2"}',
    '--sub_model.v2=nested-2',
    '--sub_model.v3=3',
    '--sub_model.deep.v4=v4',
]

print(Settings().model_dump())
"""
{
    'v0': '0',
    'sub_model': {'v1': 'json-1', 'v2': b'nested-2', 'v3': 3, 'deep': {'v4': 'v4'}},
}
"""

要启用 CLI 解析,我们只需将 cli_parse_args 标志设置为有效值,该值保留了 argparse 中定义的类似含义。

请注意,CLI 设置源默认是最顶层的源,除非其优先级值被自定义

import os
import sys

from pydantic_settings import (
    BaseSettings,
    CliSettingsSource,
    PydanticBaseSettingsSource,
)


class Settings(BaseSettings):
    my_foo: str

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        return env_settings, CliSettingsSource(settings_cls, cli_parse_args=True)


os.environ['MY_FOO'] = 'from environment'

sys.argv = ['example.py', '--my_foo=from cli']

print(Settings().model_dump())
#> {'my_foo': 'from environment'}

列表

CLI 参数解析列表支持以下三种样式的混合

  • JSON 样式 --field='[1,2]'
  • Argparse 样式 --field 1 --field 2
  • 懒惰样式 --field=1,2
import sys

from pydantic_settings import BaseSettings


class Settings(BaseSettings, cli_parse_args=True):
    my_list: list[int]


sys.argv = ['example.py', '--my_list', '[1,2]']
print(Settings().model_dump())
#> {'my_list': [1, 2]}

sys.argv = ['example.py', '--my_list', '1', '--my_list', '2']
print(Settings().model_dump())
#> {'my_list': [1, 2]}

sys.argv = ['example.py', '--my_list', '1,2']
print(Settings().model_dump())
#> {'my_list': [1, 2]}

字典

CLI 参数解析字典支持以下两种样式的混合

  • JSON 样式 --field='{"k1": 1, "k2": 2}'
  • 环境变量样式 --field k1=1 --field k2=2

这些也可以与列表形式结合使用,例如

  • --field k1=1,k2=2 --field k3=3 --field '{"k4": 4}' 等。
import sys

from pydantic_settings import BaseSettings


class Settings(BaseSettings, cli_parse_args=True):
    my_dict: dict[str, int]


sys.argv = ['example.py', '--my_dict', '{"k1":1,"k2":2}']
print(Settings().model_dump())
#> {'my_dict': {'k1': 1, 'k2': 2}}

sys.argv = ['example.py', '--my_dict', 'k1=1', '--my_dict', 'k2=2']
print(Settings().model_dump())
#> {'my_dict': {'k1': 1, 'k2': 2}}

字面量和枚举

字面量和枚举的 CLI 参数解析将转换为 CLI 选项。

import sys
from enum import IntEnum
from typing import Literal

from pydantic_settings import BaseSettings


class Fruit(IntEnum):
    pear = 0
    kiwi = 1
    lime = 2


class Settings(BaseSettings, cli_parse_args=True):
    fruit: Fruit
    pet: Literal['dog', 'cat', 'bird']


sys.argv = ['example.py', '--fruit', 'lime', '--pet', 'cat']
print(Settings().model_dump())
#> {'fruit': <Fruit.lime: 2>, 'pet': 'cat'}

别名

Pydantic 字段别名作为 CLI 参数别名添加。长度为一的别名转换为短选项。

import sys

from pydantic import AliasChoices, AliasPath, Field

from pydantic_settings import BaseSettings


class User(BaseSettings, cli_parse_args=True):
    first_name: str = Field(
        validation_alias=AliasChoices('f', 'fname', AliasPath('name', 0))
    )
    last_name: str = Field(
        validation_alias=AliasChoices('l', 'lname', AliasPath('name', 1))
    )


sys.argv = ['example.py', '--fname', 'John', '--lname', 'Doe']
print(User().model_dump())
#> {'first_name': 'John', 'last_name': 'Doe'}

sys.argv = ['example.py', '-f', 'John', '-l', 'Doe']
print(User().model_dump())
#> {'first_name': 'John', 'last_name': 'Doe'}

sys.argv = ['example.py', '--name', 'John,Doe']
print(User().model_dump())
#> {'first_name': 'John', 'last_name': 'Doe'}

sys.argv = ['example.py', '--name', 'John', '--lname', 'Doe']
print(User().model_dump())
#> {'first_name': 'John', 'last_name': 'Doe'}

子命令和位置参数

子命令和位置参数使用 CliSubCommandCliPositionalArg 注解表示。子命令注解只能应用于必填字段(即没有默认值的字段)。此外,子命令必须是派生自 pydantic BaseModel 或 pydantic.dataclasses dataclass 的有效类型。

已解析的子命令可以使用 get_subcommand 工具函数从模型实例中检索。如果子命令不是必需的,请将 is_required 标志设置为 False 以在未找到子命令时禁用引发错误。

注意

CLI 设置子命令限制为每个模型一个子解析器。换句话说,模型的所有子命令都分组在一个子解析器下;它不允许具有多个子解析器,每个子解析器都有自己的一组子命令。有关子解析器的更多信息,请参阅 argparse 子命令

注意

CliSubCommandCliPositionalArg 始终区分大小写。

import sys

from pydantic import BaseModel

from pydantic_settings import (
    BaseSettings,
    CliPositionalArg,
    CliSubCommand,
    SettingsError,
    get_subcommand,
)


class Init(BaseModel):
    directory: CliPositionalArg[str]


class Clone(BaseModel):
    repository: CliPositionalArg[str]
    directory: CliPositionalArg[str]


class Git(BaseSettings, cli_parse_args=True, cli_exit_on_error=False):
    clone: CliSubCommand[Clone]
    init: CliSubCommand[Init]


# Run without subcommands
sys.argv = ['example.py']
cmd = Git()
assert cmd.model_dump() == {'clone': None, 'init': None}

try:
    # Will raise an error since no subcommand was provided
    get_subcommand(cmd).model_dump()
except SettingsError as err:
    assert str(err) == 'Error: CLI subcommand is required {clone, init}'

# Will not raise an error since subcommand is not required
assert get_subcommand(cmd, is_required=False) is None


# Run the clone subcommand
sys.argv = ['example.py', 'clone', 'repo', 'dest']
cmd = Git()
assert cmd.model_dump() == {
    'clone': {'repository': 'repo', 'directory': 'dest'},
    'init': None,
}

# Returns the subcommand model instance (in this case, 'clone')
assert get_subcommand(cmd).model_dump() == {
    'directory': 'dest',
    'repository': 'repo',
}

CliSubCommandCliPositionalArg 注解还支持联合操作和别名。对于 Pydantic 模型的联合,重要的是要记住在验证过程中可能出现的细微差别。具体来说,对于内容相同的子命令联合,建议将它们拆分为单独的 CliSubCommand 字段,以避免任何复杂情况。最后,从联合派生的子命令名称将是 Pydantic 模型类本身的名称。

当为 CliSubCommandCliPositionalArg 字段分配别名时,只能分配一个别名。对于非联合子命令,别名将更改显示的帮助文本和子命令名称。相反,对于联合子命令,从 CLI 设置源的角度来看,别名没有实际效果。最后,对于位置参数,别名将更改为字段显示的 CLI 帮助文本。

import sys
from typing import Union

from pydantic import BaseModel, Field

from pydantic_settings import (
    BaseSettings,
    CliPositionalArg,
    CliSubCommand,
    get_subcommand,
)


class Alpha(BaseModel):
    """Apha Help"""

    cmd_alpha: CliPositionalArg[str] = Field(alias='alpha-cmd')


class Beta(BaseModel):
    """Beta Help"""

    opt_beta: str = Field(alias='opt-beta')


class Gamma(BaseModel):
    """Gamma Help"""

    opt_gamma: str = Field(alias='opt-gamma')


class Root(BaseSettings, cli_parse_args=True, cli_exit_on_error=False):
    alpha_or_beta: CliSubCommand[Union[Alpha, Beta]] = Field(alias='alpha-or-beta-cmd')
    gamma: CliSubCommand[Gamma] = Field(alias='gamma-cmd')


sys.argv = ['example.py', 'Alpha', 'hello']
assert get_subcommand(Root()).model_dump() == {'cmd_alpha': 'hello'}

sys.argv = ['example.py', 'Beta', '--opt-beta=hey']
assert get_subcommand(Root()).model_dump() == {'opt_beta': 'hey'}

sys.argv = ['example.py', 'gamma-cmd', '--opt-gamma=hi']
assert get_subcommand(Root()).model_dump() == {'opt_gamma': 'hi'}

创建 CLI 应用程序

CliApp 类提供了两个实用方法,CliApp.runCliApp.run_subcommand,可用于将 Pydantic BaseSettingsBaseModelpydantic.dataclasses.dataclass 作为 CLI 应用程序运行。这些方法主要为运行与模型关联的 cli_cmd 方法提供了结构。

CliApp.run 可用于直接提供要解析的 cli_args,并在实例化后运行模型 cli_cmd 方法(如果已定义)

from pydantic_settings import BaseSettings, CliApp


class Settings(BaseSettings):
    this_foo: str

    def cli_cmd(self) -> None:
        # Print the parsed data
        print(self.model_dump())
        #> {'this_foo': 'is such a foo'}

        # Update the parsed data showing cli_cmd ran
        self.this_foo = 'ran the foo cli cmd'


s = CliApp.run(Settings, cli_args=['--this_foo', 'is such a foo'])
print(s.model_dump())
#> {'this_foo': 'ran the foo cli cmd'}

同样,CliApp.run_subcommand 可以递归地用于运行子命令的 cli_cmd 方法

from pydantic import BaseModel

from pydantic_settings import CliApp, CliPositionalArg, CliSubCommand


class Init(BaseModel):
    directory: CliPositionalArg[str]

    def cli_cmd(self) -> None:
        print(f'git init "{self.directory}"')
        #> git init "dir"
        self.directory = 'ran the git init cli cmd'


class Clone(BaseModel):
    repository: CliPositionalArg[str]
    directory: CliPositionalArg[str]

    def cli_cmd(self) -> None:
        print(f'git clone from "{self.repository}" into "{self.directory}"')
        self.directory = 'ran the clone cli cmd'


class Git(BaseModel):
    clone: CliSubCommand[Clone]
    init: CliSubCommand[Init]

    def cli_cmd(self) -> None:
        CliApp.run_subcommand(self)


cmd = CliApp.run(Git, cli_args=['init', 'dir'])
assert cmd.model_dump() == {
    'clone': None,
    'init': {'directory': 'ran the git init cli cmd'},
}

注意

CliApp.run 不同,CliApp.run_subcommand 要求子命令模型具有定义的 cli_cmd 方法。

对于 BaseModelpydantic.dataclasses.dataclass 类型,CliApp.run 将内部使用以下 BaseSettings 配置默认值

  • nested_model_default_partial_update=True
  • case_sensitive=True
  • cli_hide_none_type=True
  • cli_avoid_json=True
  • cli_enforce_required=True
  • cli_implicit_flags=True
  • cli_kebab_case=True

异步 CLI 命令

Pydantic 设置支持通过 CliApp.runCliApp.run_subcommand 运行异步 CLI 命令。通过此功能,你可以在 Pydantic 模型(包括子命令)中定义 async def 方法,并像它们的同步对应项一样执行它们。具体来说

  1. 支持异步方法:你现在可以将你的 cli_cmd 或类似的 CLI 入口点方法标记为 async def,并让 CliApp 执行它们。
  2. 子命令也可以是异步的:如果你有嵌套的 CLI 子命令,最终(最低级)子命令方法也可以是异步的。
  3. 将异步方法限制为最终子命令:不建议将父命令定义为异步,因为它可能导致创建额外的线程和事件循环。为了获得最佳性能并避免不必要的资源使用,只将最深(子)子命令实现为 async def。

下面是一个简单的示例,演示了一个异步顶层命令

from pydantic_settings import BaseSettings, CliApp


class AsyncSettings(BaseSettings):
    async def cli_cmd(self) -> None:
        print('Hello from an async CLI method!')
        #> Hello from an async CLI method!


# If an event loop is already running, a new thread will be used;
# otherwise, asyncio.run() is used to execute this async method.
assert CliApp.run(AsyncSettings, cli_args=[]).model_dump() == {}

异步子命令

如上所述,你也可以将子命令定义为异步。但是,只对叶子(最低级)子命令这样做,以避免在父命令中不必要地生成新线程和事件循环

from pydantic import BaseModel

from pydantic_settings import (
    BaseSettings,
    CliApp,
    CliPositionalArg,
    CliSubCommand,
)


class Clone(BaseModel):
    repository: CliPositionalArg[str]
    directory: CliPositionalArg[str]

    async def cli_cmd(self) -> None:
        # Perform async tasks here, e.g. network or I/O operations
        print(f'Cloning async from "{self.repository}" into "{self.directory}"')
        #> Cloning async from "repo" into "dir"


class Git(BaseSettings):
    clone: CliSubCommand[Clone]

    def cli_cmd(self) -> None:
        # Run the final subcommand (clone/init). It is recommended to define async methods only at the deepest level.
        CliApp.run_subcommand(self)


CliApp.run(Git, cli_args=['clone', 'repo', 'dir']).model_dump() == {
    'repository': 'repo',
    'directory': 'dir',
}

当执行带有异步 cli_cmd 的子命令时,Pydantic 设置会自动检测当前线程是否已存在活动事件循环。如果是,则在新的线程中运行异步命令以避免冲突。否则,它会在当前线程中使用 asyncio.run()。这种处理确保你的异步子命令“开箱即用”,无需额外的手动设置。

序列化 CLI 参数

实例化的 Pydantic 模型可以使用 CliApp.serialize 方法序列化为 CLI 参数。

from pydantic import BaseModel

from pydantic_settings import CliApp


class Nested(BaseModel):
    that: int


class Settings(BaseModel):
    this: str
    nested: Nested


print(CliApp.serialize(Settings(this='hello', nested=Nested(that=123))))
#> ['--this', 'hello', '--nested.that', '123']

互斥组

可以通过继承 CliMutuallyExclusiveGroup 类来创建 CLI 互斥组。

注意

CliMutuallyExclusiveGroup 不能用于联合或包含嵌套模型。

from typing import Optional

from pydantic import BaseModel

from pydantic_settings import CliApp, CliMutuallyExclusiveGroup, SettingsError


class Circle(CliMutuallyExclusiveGroup):
    radius: Optional[float] = None
    diameter: Optional[float] = None
    perimeter: Optional[float] = None


class Settings(BaseModel):
    circle: Circle


try:
    CliApp.run(
        Settings,
        cli_args=['--circle.radius=1', '--circle.diameter=2'],
        cli_exit_on_error=False,
    )
except SettingsError as e:
    print(e)
    """
    error parsing CLI: argument --circle.diameter: not allowed with argument --circle.radius
    """

自定义 CLI 体验

以下标志可用于根据你的需求自定义 CLI 体验。

更改显示的程序名称

通过设置 cli_prog_name 来更改帮助文本用法中显示的默认程序名称。默认情况下,它将像 argparse 一样从 sys.argv[0] 派生当前正在执行的程序的名称。

import sys

from pydantic_settings import BaseSettings


class Settings(BaseSettings, cli_parse_args=True, cli_prog_name='appdantic'):
    pass


try:
    sys.argv = ['example.py', '--help']
    Settings()
except SystemExit as e:
    print(e)
    #> 0
"""
usage: appdantic [-h]

options:
  -h, --help  show this help message and exit
"""

CLI 布尔标志

使用 cli_implicit_flags 设置更改布尔字段默认是显式还是隐式。默认情况下,布尔字段是“显式”的,这意味着必须在 CLI 上显式提供布尔值,例如 --flag=True。相反,“隐式”的布尔字段从标志本身派生值,例如 --flag,--no-flag,这消除了传递显式值的需要。

此外,提供的 CliImplicitFlagCliExplicitFlag 注解可在必要时用于更精细的控制。

from pydantic_settings import BaseSettings, CliExplicitFlag, CliImplicitFlag


class ExplicitSettings(BaseSettings, cli_parse_args=True):
    """Boolean fields are explicit by default."""

    explicit_req: bool
    """
    --explicit_req bool   (required)
    """

    explicit_opt: bool = False
    """
    --explicit_opt bool   (default: False)
    """

    # Booleans are explicit by default, so must override implicit flags with annotation
    implicit_req: CliImplicitFlag[bool]
    """
    --implicit_req, --no-implicit_req (required)
    """

    implicit_opt: CliImplicitFlag[bool] = False
    """
    --implicit_opt, --no-implicit_opt (default: False)
    """


class ImplicitSettings(BaseSettings, cli_parse_args=True, cli_implicit_flags=True):
    """With cli_implicit_flags=True, boolean fields are implicit by default."""

    # Booleans are implicit by default, so must override explicit flags with annotation
    explicit_req: CliExplicitFlag[bool]
    """
    --explicit_req bool   (required)
    """

    explicit_opt: CliExplicitFlag[bool] = False
    """
    --explicit_opt bool   (default: False)
    """

    implicit_req: bool
    """
    --implicit_req, --no-implicit_req (required)
    """

    implicit_opt: bool = False
    """
    --implicit_opt, --no-implicit_opt (default: False)
    """

忽略并检索未知参数

使用 cli_ignore_unknown_args 更改是否忽略未知 CLI 参数并仅解析已知参数。默认情况下,CLI 不忽略任何参数。然后可以使用 CliUnknownArgs 注解检索被忽略的参数。

import sys

from pydantic_settings import BaseSettings, CliUnknownArgs


class Settings(BaseSettings, cli_parse_args=True, cli_ignore_unknown_args=True):
    good_arg: str
    ignored_args: CliUnknownArgs


sys.argv = ['example.py', '--bad-arg=bad', 'ANOTHER_BAD_ARG', '--good_arg=hello world']
print(Settings().model_dump())
#> {'good_arg': 'hello world', 'ignored_args': ['--bad-arg=bad', 'ANOTHER_BAD_ARG']}

CLI 烤串式命名参数

通过启用 cli_kebab_case 更改 CLI 参数是否应使用 kebab-case。默认情况下,cli_kebab_case=True 将忽略枚举字段,并等同于 cli_kebab_case='no_enums'。要将 kebab-case 应用于所有内容,包括枚举,请使用 cli_kebab_case='all'

import sys

from pydantic import Field

from pydantic_settings import BaseSettings


class Settings(BaseSettings, cli_parse_args=True, cli_kebab_case=True):
    my_option: str = Field(description='will show as kebab case on CLI')


try:
    sys.argv = ['example.py', '--help']
    Settings()
except SystemExit as e:
    print(e)
    #> 0
"""
usage: example.py [-h] [--my-option str]

options:
  -h, --help       show this help message and exit
  --my-option str  will show as kebab case on CLI (required)
"""

更改 CLI 是否在出错时退出

使用 cli_exit_on_error 更改 CLI 内部解析器是否在出错时退出或引发 SettingsError 异常。默认情况下,CLI 内部解析器将在出错时退出。

import sys

from pydantic_settings import BaseSettings, SettingsError


class Settings(BaseSettings, cli_parse_args=True, cli_exit_on_error=False): ...


try:
    sys.argv = ['example.py', '--bad-arg']
    Settings()
except SettingsError as e:
    print(e)
    #> error parsing CLI: unrecognized arguments: --bad-arg

在 CLI 强制执行必填参数

Pydantic settings 旨在在实例化模型时从各种来源获取值。这意味着必填字段并非严格要求来自任何单个来源(例如 CLI)。相反,重要的是其中一个来源提供了所需的值。

但是,如果你的用例更符合 #2,即使用 Pydantic 模型定义 CLI,你可能希望必填字段**在 CLI 严格要求**。我们可以通过使用 cli_enforce_required 来启用此行为。

注意

一个必填的 CliPositionalArg 字段始终在 CLI 严格要求(强制执行)。

import os
import sys

from pydantic import Field

from pydantic_settings import BaseSettings, SettingsError


class Settings(
    BaseSettings,
    cli_parse_args=True,
    cli_enforce_required=True,
    cli_exit_on_error=False,
):
    my_required_field: str = Field(description='a top level required field')


os.environ['MY_REQUIRED_FIELD'] = 'hello from environment'

try:
    sys.argv = ['example.py']
    Settings()
except SettingsError as e:
    print(e)
    #> error parsing CLI: the following arguments are required: --my_required_field

更改 None 类型解析字符串

通过设置 cli_parse_none_str 来更改将被解析为 None 的 CLI 字符串值(例如“null”,“void”,“None”等)。默认情况下,如果设置了 env_parse_none_str,它将使用该值。否则,如果 cli_avoid_jsonFalse,它将默认为“null”,如果 cli_avoid_jsonTrue,则默认为“None”。

import sys
from typing import Optional

from pydantic import Field

from pydantic_settings import BaseSettings


class Settings(BaseSettings, cli_parse_args=True, cli_parse_none_str='void'):
    v1: Optional[int] = Field(description='the top level v0 option')


sys.argv = ['example.py', '--v1', 'void']
print(Settings().model_dump())
#> {'v1': None}

隐藏 None 类型值

通过启用 cli_hide_none_type 从 CLI 帮助文本中隐藏 None 值。

import sys
from typing import Optional

from pydantic import Field

from pydantic_settings import BaseSettings


class Settings(BaseSettings, cli_parse_args=True, cli_hide_none_type=True):
    v0: Optional[str] = Field(description='the top level v0 option')


try:
    sys.argv = ['example.py', '--help']
    Settings()
except SystemExit as e:
    print(e)
    #> 0
"""
usage: example.py [-h] [--v0 str]

options:
  -h, --help  show this help message and exit
  --v0 str    the top level v0 option (required)
"""

避免添加 JSON CLI 选项

通过启用 cli_avoid_json 避免在 CLI 中添加导致 JSON 字符串的复杂字段。

import sys

from pydantic import BaseModel, Field

from pydantic_settings import BaseSettings


class SubModel(BaseModel):
    v1: int = Field(description='the sub model v1 option')


class Settings(BaseSettings, cli_parse_args=True, cli_avoid_json=True):
    sub_model: SubModel = Field(
        description='The help summary for SubModel related options'
    )


try:
    sys.argv = ['example.py', '--help']
    Settings()
except SystemExit as e:
    print(e)
    #> 0
"""
usage: example.py [-h] [--sub_model.v1 int]

options:
  -h, --help          show this help message and exit

sub_model options:
  The help summary for SubModel related options

  --sub_model.v1 int  the sub model v1 option (required)
"""

使用类文档字符串作为组帮助文本

默认情况下,当为嵌套模型填充组帮助文本时,它将从字段描述中提取。另外,我们还可以配置 CLI 设置以从类文档字符串中提取。

注意

如果字段是嵌套模型的联合,即使 cli_use_class_docs_for_groups 设置为 True,组帮助文本也将始终从字段描述中提取。

import sys

from pydantic import BaseModel, Field

from pydantic_settings import BaseSettings


class SubModel(BaseModel):
    """The help text from the class docstring."""

    v1: int = Field(description='the sub model v1 option')


class Settings(BaseSettings, cli_parse_args=True, cli_use_class_docs_for_groups=True):
    """My application help text."""

    sub_model: SubModel = Field(description='The help text from the field description')


try:
    sys.argv = ['example.py', '--help']
    Settings()
except SystemExit as e:
    print(e)
    #> 0
"""
usage: example.py [-h] [--sub_model JSON] [--sub_model.v1 int]

My application help text.

options:
  -h, --help          show this help message and exit

sub_model options:
  The help text from the class docstring.

  --sub_model JSON    set sub_model from JSON string
  --sub_model.v1 int  the sub model v1 option (required)
"""

更改 CLI 标志前缀字符

通过设置 cli_flag_prefix_char 更改 CLI 可选参数中使用的 CLI 标志前缀字符。

import sys

from pydantic import AliasChoices, Field

from pydantic_settings import BaseSettings


class Settings(BaseSettings, cli_parse_args=True, cli_flag_prefix_char='+'):
    my_arg: str = Field(validation_alias=AliasChoices('m', 'my-arg'))


sys.argv = ['example.py', '++my-arg', 'hi']
print(Settings().model_dump())
#> {'my_arg': 'hi'}

sys.argv = ['example.py', '+m', 'hi']
print(Settings().model_dump())
#> {'my_arg': 'hi'}

从 CLI 帮助文本中抑制字段

要从 CLI 帮助文本中抑制字段,可以将 CliSuppress 注解用于字段类型,或者将 CLI_SUPPRESS 字符串常量用于字段描述。

import sys

from pydantic import Field

from pydantic_settings import CLI_SUPPRESS, BaseSettings, CliSuppress


class Settings(BaseSettings, cli_parse_args=True):
    """Suppress fields from CLI help text."""

    field_a: CliSuppress[int] = 0
    field_b: str = Field(default=1, description=CLI_SUPPRESS)


try:
    sys.argv = ['example.py', '--help']
    Settings()
except SystemExit as e:
    print(e)
    #> 0
"""
usage: example.py [-h]

Suppress fields from CLI help text.

options:
  -h, --help          show this help message and exit
"""

CLI 参数快捷方式

使用 SettingsConfigDict 中的 cli_shortcuts 选项为字段添加替代 CLI 参数名称(快捷方式)。这允许你为 CLI 参数定义额外的名称,这对于为深层嵌套或冗长的字段名提供更用户友好或更短的别名特别有用。

cli_shortcuts 选项接受一个字典,该字典将目标字段名(使用点表示法表示嵌套字段)映射到一个或多个快捷方式名称。如果多个字段共享相同的快捷方式,则第一个匹配的字段将优先。

扁平示例

from pydantic import Field

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    option: str = Field(default='foo')
    list_option: str = Field(default='fizz')

    model_config = SettingsConfigDict(
        cli_shortcuts={'option': 'option2', 'list_option': ['list_option2']}
    )


# Now you can use the shortcuts on the CLI:
# --option2 sets 'option', --list_option2 sets 'list_option'

嵌套示例

from pydantic import BaseModel, Field

from pydantic_settings import BaseSettings, SettingsConfigDict


class TwiceNested(BaseModel):
    option: str = Field(default='foo')


class Nested(BaseModel):
    twice_nested_option: TwiceNested = TwiceNested()
    option: str = Field(default='foo')


class Settings(BaseSettings):
    nested: Nested = Nested()
    model_config = SettingsConfigDict(
        cli_shortcuts={
            'nested.option': 'option2',
            'nested.twice_nested_option.option': 'twice_nested_option',
        }
    )


# Now you can use --option2 to set nested.option and --twice_nested_option to set nested.twice_nested_option.option

如果快捷方式冲突(映射到多个字段),它将应用于模型中第一个匹配的字段。

与现有解析器集成

CLI 设置源可以通过用用户定义的、指定 root_parser 对象的 CLI 设置源覆盖默认 CLI 设置源来与现有解析器集成。

import sys
from argparse import ArgumentParser

from pydantic_settings import BaseSettings, CliApp, CliSettingsSource

parser = ArgumentParser()
parser.add_argument('--food', choices=['pear', 'kiwi', 'lime'])


class Settings(BaseSettings):
    name: str = 'Bob'


# Set existing `parser` as the `root_parser` object for the user defined settings source
cli_settings = CliSettingsSource(Settings, root_parser=parser)

# Parse and load CLI settings from the command line into the settings source.
sys.argv = ['example.py', '--food', 'kiwi', '--name', 'waldo']
s = CliApp.run(Settings, cli_settings_source=cli_settings)
print(s.model_dump())
#> {'name': 'waldo'}

# Load CLI settings from pre-parsed arguments. i.e., the parsing occurs elsewhere and we
# just need to load the pre-parsed args into the settings source.
parsed_args = parser.parse_args(['--food', 'kiwi', '--name', 'ralph'])
s = CliApp.run(Settings, cli_args=parsed_args, cli_settings_source=cli_settings)
print(s.model_dump())
#> {'name': 'ralph'}

CliSettingsSource 通过使用解析器方法将 settings_cls 字段添加为命令行参数来与 root_parser 对象连接。CliSettingsSource 内部解析器表示基于 argparse 库,因此需要支持与其 argparse 对应项相同属性的解析器方法。可以自定义的可用解析器方法及其 argparse 对应项(默认值)如下所示

  • parse_args_method - (argparse.ArgumentParser.parse_args)
  • add_argument_method - (argparse.ArgumentParser.add_argument)
  • add_argument_group_method - (argparse.ArgumentParser.add_argument_group)
  • add_parser_method - (argparse._SubParsersAction.add_parser)
  • add_subparsers_method - (argparse.ArgumentParser.add_subparsers)
  • formatter_class - (argparse.RawDescriptionHelpFormatter)

对于非 argparse 解析器,如果不支持,可以将解析器方法设置为 None。CLI 设置仅在连接到根解析器时,如果某个解析器方法是必需但设置为 None 时才引发错误。

注意

formatter_class 仅适用于子命令。CliSettingsSource 从不触及或修改任何外部解析器设置,以避免破坏性更改。由于子命令位于它们自己的内部解析器树上,我们可以安全地应用 formatter_class 设置而不会破坏外部解析器逻辑。

秘密

将秘密值放在文件中是向应用程序提供敏感配置的常见模式。

秘密文件遵循与 dotenv 文件相同的原则,但它只包含单个值,并且文件名用作键。秘密文件将如下所示

/var/run/database_password
super_secret_database_password

一旦你有了秘密文件,pydantic 支持两种加载方式

  1. BaseSettings 类中的 model_config 上将 secrets_dir 设置为存储秘密文件的目录。
    from pydantic_settings import BaseSettings, SettingsConfigDict
    
    
    class Settings(BaseSettings):
        model_config = SettingsConfigDict(secrets_dir='/var/run')
    
        database_password: str
    
  2. 使用 _secrets_dir 关键字参数实例化 BaseSettings 派生类
    settings = Settings(_secrets_dir='/var/run')
    

在这两种情况下,传递参数的值可以是任何有效的目录,无论是绝对路径还是相对于当前工作目录的相对路径。**请注意,不存在的目录只会生成警告**。从那里,*pydantic* 将为你处理所有事情,加载你的变量并验证它们。

即使使用秘密目录,*pydantic* 仍会从 dotenv 文件或环境中读取环境变量,**dotenv 文件和环境变量始终优先于从秘密目录加载的值**。

通过实例化时的 _secrets_dir 关键字参数(方法 2)传递文件路径将覆盖 model_config 类上设置的值(如果有)。

如果你需要从多个秘密目录加载设置,可以以元组或列表形式传递多个路径。文件将按顺序加载,后续路径中的值会覆盖之前的值。

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    # files in '/run/secrets' take priority over '/var/run'
    model_config = SettingsConfigDict(secrets_dir=('/var/run', '/run/secrets'))

    database_password: str

如果任何 secrets_dir 缺失,它将被忽略并显示警告。如果任何 secrets_dir 是文件,则会引发错误。

用例:Docker 秘密

Docker Secrets 可用于为在 Docker 容器中运行的应用程序提供敏感配置。要在 *pydantic* 应用程序中使用这些秘密,过程很简单。有关在 Docker 中创建、管理和使用秘密的更多信息,请参阅官方 Docker 文档

首先,定义你的 Settings 类,其中包含指定秘密目录的 SettingsConfigDict

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(secrets_dir='/run/secrets')

    my_secret_data: str

注意

默认情况下,Docker 使用 /run/secrets 作为目标挂载点。如果你想使用不同的位置,请相应地更改 Config.secrets_dir

然后,通过 Docker CLI 创建你的秘密

printf "This is a secret" | docker secret create my_secret_data -

最后,在 Docker 容器中运行你的应用程序并提供你新创建的秘密

docker service create --name pydantic-with-secrets --secret my_secret_data pydantic-app:latest

嵌套秘密

默认的秘密实现 SecretsSettingsSource 的行为并不总是符合预期或足够。例如,默认实现不支持嵌套子模型中的秘密字段。

NestedSecretsSettingsSource 可以用作 SecretsSettingsSource 的直接替代品,以调整默认行为。所有差异总结在下表中。

SecretsSettingsSource NestedSecretsSettingsSourcee
秘密字段必须属于顶层模型。 秘密可以是嵌套模型的字段。
秘密文件只能放在 secrets_dir 中。 秘密文件可以放在嵌套模型的子目录中。
秘密文件发现基于 EnvSettingsSource 使用的相同配置选项:case_sensitiveenv_nested_delimiterenv_prefix 默认选项受到尊重,但可以通过 secrets_case_sensitivesecrets_nested_delimitersecrets_prefix 覆盖。
当文件系统中缺少 secrets_dir 时,会生成警告。 使用 secrets_dir_missing 选项选择是发出警告、引发错误还是静默忽略。

用例:纯目录布局

📂 secrets
├── 📄 app_key
└── 📄 db_passwd

在下面的示例中,秘密嵌套分隔符 '_' 与环境嵌套分隔符 '__' 不同。Settings.db.user 的值可以通过环境变量 MY_DB__USER 传递。

from pydantic import BaseModel, SecretStr

from pydantic_settings import (
    BaseSettings,
    NestedSecretsSettingsSource,
    SettingsConfigDict,
)


class AppSettings(BaseModel):
    key: SecretStr


class DbSettings(BaseModel):
    user: str
    passwd: SecretStr


class Settings(BaseSettings):
    app: AppSettings
    db: DbSettings

    model_config = SettingsConfigDict(
        env_prefix='MY_',
        env_nested_delimiter='__',
        secrets_dir='secrets',
        secrets_nested_delimiter='_',
    )

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls,
        init_settings,
        env_settings,
        dotenv_settings,
        file_secret_settings,
    ):
        return (
            init_settings,
            env_settings,
            dotenv_settings,
            NestedSecretsSettingsSource(file_secret_settings),
        )

用例:嵌套目录布局

📂 secrets
├── 📂 app
│   └── 📄 key
└── 📂 db
    └── 📄 passwd
from pydantic import BaseModel, SecretStr

from pydantic_settings import (
    BaseSettings,
    NestedSecretsSettingsSource,
    SettingsConfigDict,
)


class AppSettings(BaseModel):
    key: SecretStr


class DbSettings(BaseModel):
    user: str
    passwd: SecretStr


class Settings(BaseSettings):
    app: AppSettings
    db: DbSettings

    model_config = SettingsConfigDict(
        env_prefix='MY_',
        env_nested_delimiter='__',
        secrets_dir='secrets',
        secrets_nested_subdir=True,
    )

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls,
        init_settings,
        env_settings,
        dotenv_settings,
        file_secret_settings,
    ):
        return (
            init_settings,
            env_settings,
            dotenv_settings,
            NestedSecretsSettingsSource(file_secret_settings),
        )

用例:多个嵌套目录

📂 secrets
├── 📂 default
│   ├── 📂 app
│   │   └── 📄 key
│   └── 📂 db
│       └── 📄 passwd
└── 📂 override
    ├── 📂 app
    │   └── 📄 key
    └── 📂 db
        └── 📄 passwd
from pydantic import BaseModel, SecretStr

from pydantic_settings import (
    BaseSettings,
    NestedSecretsSettingsSource,
    SettingsConfigDict,
)


class AppSettings(BaseModel):
    key: SecretStr


class DbSettings(BaseModel):
    user: str
    passwd: SecretStr


class Settings(BaseSettings):
    app: AppSettings
    db: DbSettings

    model_config = SettingsConfigDict(
        env_prefix='MY_',
        env_nested_delimiter='__',
        secrets_dir=['secrets/default', 'secrets/override'],
        secrets_nested_subdir=True,
    )

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls,
        init_settings,
        env_settings,
        dotenv_settings,
        file_secret_settings,
    ):
        return (
            init_settings,
            env_settings,
            dotenv_settings,
            NestedSecretsSettingsSource(file_secret_settings),
        )

配置选项

secrets_dir

秘密目录的路径,与 SecretsSettingsSource.secrets_dir 相同。如果是 list,则最后一个匹配项获胜。如果 secrets_dir 在源构造函数和模型配置中都传递了,则值不会合并(构造函数获胜)。

secrets_dir_missing

如果 secrets_dir 不存在,原始 SecretsSettingsSource 会发出警告。然而,这可能不是我们想要的,例如,如果我们在开发环境中不挂载 Docker Secrets。使用 secrets_dir_missing 来选择

  • 'ok' — 如果 secrets_dir 不存在,则不执行任何操作
  • 'warn'(默认)— 打印警告,与 SecretsSettingsSource 相同
  • 'error' — 引发 SettingsError

如果传递了多个 secrets_dir,则相同的 secrets_dir_missing 操作适用于每个目录。

secrets_dir_max_size

出于安全原因限制 secrets_dir 的大小,默认为 SECRETS_DIR_MAX_SIZE,即 16 MiB。

NestedSecretsSettingsSourceEnvSettingsSource 的一个薄包装器,它在初始化时加载所有潜在的秘密。如果我们挂载一个大文件到 secrets_dir 下,这可能导致 MemoryError

如果传递了多个 secrets_dir,则限制独立应用于每个目录。

secrets_case_sensitive

case_sensitive 相同,但仅适用于秘密。如果未指定,则默认为 case_sensitive

secrets_nested_delimiter

env_nested_delimiter 相同,但仅适用于秘密。如果未指定,则默认为 env_nested_delimiter。此选项用于实现**嵌套秘密目录**布局,甚至可以执行 /run/secrets/model/delim/nested1/delim/nested2 等恶劣操作。

secrets_nested_subdir

布尔标志,用于开启**嵌套秘密目录**模式,默认为 False。如果为 True,则将 secrets_nested_delimiter 设置为 os.sep。如果 secrets_nested_delimiter 已指定,则引发 SettingsError

secrets_prefix

秘密路径前缀,类似于 env_prefix,但仅适用于秘密。如果未指定,则默认为 env_prefix。在纯目录模式和嵌套目录模式下均适用,例如 '/run/secrets/prefix_model__nested''/run/secrets/prefix_model/nested'

AWS Secrets Manager

你必须设置一个参数

  • secret_id: AWS 秘密 ID

秘密中的键值必须与字段名称具有相同的命名约定。例如,如果秘密中的键名为 SqlServerPassword,则字段名称必须相同。你也可以使用别名。

在 AWS Secrets Manager 中,嵌套模型支持键名中使用 -- 分隔符。例如,SqlServer--Password

不支持数组(例如 MySecret--0MySecret--1)。

import os

from pydantic import BaseModel

from pydantic_settings import (
    AWSSecretsManagerSettingsSource,
    BaseSettings,
    PydanticBaseSettingsSource,
)


class SubModel(BaseModel):
    a: str


class AWSSecretsManagerSettings(BaseSettings):
    foo: str
    bar: int
    sub: SubModel

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        aws_secrets_manager_settings = AWSSecretsManagerSettingsSource(
            settings_cls,
            os.environ['AWS_SECRETS_MANAGER_SECRET_ID'],
        )
        return (
            init_settings,
            env_settings,
            dotenv_settings,
            file_secret_settings,
            aws_secrets_manager_settings,
        )

Azure Key Vault

你必须设置两个参数

  • url: 例如,https://my-resource.vault.azure.net/
  • credential: 如果你使用 DefaultAzureCredential,在本地你可以执行 az login 获取你的身份凭据。该身份必须具有角色分配(推荐的是 Key Vault Secrets User),这样你才能访问秘密。

你必须在字段名和 Key Vault 秘密名中使用相同的命名约定。例如,如果秘密名为 SqlServerPassword,则字段名必须相同。你也可以使用别名。

在 Key Vault 中,嵌套模型支持 -- 分隔符。例如,SqlServer--Password

不支持 Key Vault 数组(例如 MySecret--0MySecret--1)。

import os

from azure.identity import DefaultAzureCredential
from pydantic import BaseModel

from pydantic_settings import (
    AzureKeyVaultSettingsSource,
    BaseSettings,
    PydanticBaseSettingsSource,
)


class SubModel(BaseModel):
    a: str


class AzureKeyVaultSettings(BaseSettings):
    foo: str
    bar: int
    sub: SubModel

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        az_key_vault_settings = AzureKeyVaultSettingsSource(
            settings_cls,
            os.environ['AZURE_KEY_VAULT_URL'],
            DefaultAzureCredential(),
        )
        return (
            init_settings,
            env_settings,
            dotenv_settings,
            file_secret_settings,
            az_key_vault_settings,
        )

蛇形命名转换

Azure Key Vault 源接受 snake_case_convertion 选项,默认禁用,用于通过将 Key Vault 秘密名称映射到 Python 的 snake_case 字段名称进行转换,而无需使用别名。

import os

from azure.identity import DefaultAzureCredential

from pydantic_settings import (
    AzureKeyVaultSettingsSource,
    BaseSettings,
    PydanticBaseSettingsSource,
)


class AzureKeyVaultSettings(BaseSettings):
    my_setting: str

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        az_key_vault_settings = AzureKeyVaultSettingsSource(
            settings_cls,
            os.environ['AZURE_KEY_VAULT_URL'],
            DefaultAzureCredential(),
            snake_case_conversion=True,
        )
        return (az_key_vault_settings,)

此设置将加载 Azure Key Vault 秘密(例如,MySettingmySettingmy-secretMY-SECRET),将它们映射到 snake case 版本(此例中为 my_setting)。

破折号到下划线映射

Azure Key Vault 源接受 dash_to_underscore 选项,默认禁用,以支持 Key Vault kebab-case 秘密名称,通过将其映射到 Python 的 snake_case 字段名称。启用后,秘密名称中的破折号 (-) 在验证期间会映射到字段名称中的下划线 (_)。

此映射仅适用于*字段名称*,不适用于别名。

import os

from azure.identity import DefaultAzureCredential
from pydantic import Field

from pydantic_settings import (
    AzureKeyVaultSettingsSource,
    BaseSettings,
    PydanticBaseSettingsSource,
)


class AzureKeyVaultSettings(BaseSettings):
    field_with_underscore: str
    field_with_alias: str = Field(..., alias='Alias-With-Dashes')

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        az_key_vault_settings = AzureKeyVaultSettingsSource(
            settings_cls,
            os.environ['AZURE_KEY_VAULT_URL'],
            DefaultAzureCredential(),
            dash_to_underscore=True,
        )
        return (az_key_vault_settings,)

此设置将加载名为 field-with-underscoreAlias-With-Dashes 的 Azure Key Vault 秘密,并将它们分别映射到 field_with_underscorefield_with_alias 字段。

提示

或者,你可以配置一个 别名生成器 来映射 PascalCase 秘密。

Google Cloud Secret Manager

Google Cloud Secret Manager 允许你在 Google Cloud Platform 中存储、管理和访问敏感信息作为秘密。此集成允许你直接从 GCP Secret Manager 检索秘密,以便在 Pydantic 设置中使用。

安装

Google Cloud Secret Manager 集成需要额外的依赖项

pip install "pydantic-settings[gcp-secret-manager]"

基本用法

要使用 Google Cloud Secret Manager,你需要

  1. 创建一个 GoogleSecretManagerSettingsSource。(有关身份验证选项,请参阅GCP 身份验证。)
  2. 将此源添加到你的设置自定义管道中
from pydantic import BaseModel

from pydantic_settings import (
    BaseSettings,
    GoogleSecretManagerSettingsSource,
    PydanticBaseSettingsSource,
    SettingsConfigDict,
)


class Database(BaseModel):
    password: str
    user: str


class Settings(BaseSettings):
    database: Database

    model_config = SettingsConfigDict(env_nested_delimiter='__')

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        # Create the GCP Secret Manager settings source
        gcp_settings = GoogleSecretManagerSettingsSource(
            settings_cls,
            # If not provided, will use google.auth.default()
            # to get credentials from the environemnt
            # credentials=your_credentials,
            # If not provided, will use google.auth.default()
            # to get project_id from the environemnt
            project_id='your-gcp-project-id',
        )

        return (
            init_settings,
            env_settings,
            dotenv_settings,
            file_secret_settings,
            gcp_settings,
        )

GCP 认证

GoogleSecretManagerSettingsSource 支持多种身份验证方法

  1. **默认凭据** - 如果你未提供凭据或项目 ID,它将使用 google.auth.default() 获取它们。这适用于

  2. 来自 GOOGLE_APPLICATION_CREDENTIALS 环境变量的服务帐户凭据

  3. 来自 gcloud auth application-default login 的用户凭据
  4. Compute Engine、GKE、Cloud Run 或 Cloud Functions 默认服务帐户

  5. **显式凭据** - 你也可以直接提供 credentials。例如 sa_credentials = google.oauth2.service_account.Credentials.from_service_account_file('path/to/service-account.json'),然后 GoogleSecretManagerSettingsSource(credentials=sa_credentials)

嵌套模型

对于嵌套模型,Secret Manager 支持 env_nested_delimiter 设置,只要它符合命名规则。在上面的示例中,你将在 Secret Manager 中创建名为 database__passworddatabase__user 的秘密。

重要说明

  1. **大小写敏感性**:默认情况下,秘密名称是大小写敏感的。
  2. **秘密命名**:在 Google Secret Manager 中创建的秘密名称必须与你的字段名称(包括任何前缀)匹配。根据 Secret Manager 文档,秘密名称可以包含大写和小写字母、数字、连字符和下划线。名称的最大允许长度为 255 个字符。
  3. **秘密版本**:GoogleSecretManagerSettingsSource 使用秘密的“最新”版本。

有关在 Google Cloud Secret Manager 中创建和管理秘密的更多详细信息,请参阅官方 Google Cloud 文档

其他设置来源

其他设置来源可用于常见的配置文件

  • 使用 json_filejson_file_encoding 参数的 JsonConfigSettingsSource
  • 使用 *(可选)* pyproject_toml_depth 和 *(可选)* pyproject_toml_table_header 参数的 PyprojectTomlConfigSettingsSource
  • 使用 toml_file 参数的 TomlConfigSettingsSource
  • 使用 yaml_fileyaml_file_encoding 参数的 YamlConfigSettingsSource

你还可以通过提供路径列表来提供多个文件

toml_file = ['config.default.toml', 'config.custom.toml']
要使用它们,你可以使用此处描述的相同机制

from pydantic import BaseModel

from pydantic_settings import (
    BaseSettings,
    PydanticBaseSettingsSource,
    SettingsConfigDict,
    TomlConfigSettingsSource,
)


class Nested(BaseModel):
    nested_field: str


class Settings(BaseSettings):
    foobar: str
    nested: Nested
    model_config = SettingsConfigDict(toml_file='config.toml')

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        return (TomlConfigSettingsSource(settings_cls),)

这将能够读取位于当前工作目录中的以下“config.toml”文件

foobar = "Hello"
[nested]
nested_field = "world!"

pyproject.toml

"pyproject.toml" 是一个标准化的文件,用于在 Python 项目中提供配置值。PEP 518 定义了一个 [tool] 表,可用于提供任意工具配置。虽然鼓励使用 [tool] 表,但 PyprojectTomlConfigSettingsSource 可用于从 "pyproject.toml" 文件中的任何位置加载变量。

这通过提供 SettingsConfigDict(pyproject_toml_table_header=tuple[str, ...]) 来控制,其中值是标头部分的元组。默认情况下,pyproject_toml_table_header=('tool', 'pydantic-settings'),它将从 [tool.pydantic-settings] 表加载变量。

from pydantic_settings import (
    BaseSettings,
    PydanticBaseSettingsSource,
    PyprojectTomlConfigSettingsSource,
    SettingsConfigDict,
)


class Settings(BaseSettings):
    """Example loading values from the table used by default."""

    field: str

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        return (PyprojectTomlConfigSettingsSource(settings_cls),)


class SomeTableSettings(Settings):
    """Example loading values from a user defined table."""

    model_config = SettingsConfigDict(
        pyproject_toml_table_header=('tool', 'some-table')
    )


class RootSettings(Settings):
    """Example loading values from the root of a pyproject.toml file."""

    model_config = SettingsConfigDict(extra='ignore', pyproject_toml_table_header=())

这将能够读取位于当前工作目录中的以下“pyproject.toml”文件,从而得到 Settings(field='default-table')SomeTableSettings(field='some-table')RootSettings(field='root')

field = "root"

[tool.pydantic-settings]
field = "default-table"

[tool.some-table]
field = "some-table"

默认情况下,PyprojectTomlConfigSettingsSource 只会在你的当前工作目录中查找 "pyproject.toml"。但是,有两个选项可以更改此行为。

  • 可以提供 SettingsConfigDict(pyproject_toml_depth=<int>) 来检查目录树中**向上** <int> 个目录,以查找 "pyproject.toml"(如果当前工作目录中未找到)。默认情况下,不检查任何父目录。
  • 可以在实例化源时提供显式文件路径(例如 PyprojectTomlConfigSettingsSource(settings_cls, Path('~/.config').resolve() / 'pyproject.toml'))。如果以这种方式提供了文件路径,它将被视为绝对路径(不检查其他位置)。
from pathlib import Path

from pydantic_settings import (
    BaseSettings,
    PydanticBaseSettingsSource,
    PyprojectTomlConfigSettingsSource,
    SettingsConfigDict,
)


class DiscoverSettings(BaseSettings):
    """Example of discovering a pyproject.toml in parent directories in not in `Path.cwd()`."""

    model_config = SettingsConfigDict(pyproject_toml_depth=2)

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        return (PyprojectTomlConfigSettingsSource(settings_cls),)


class ExplicitFilePathSettings(BaseSettings):
    """Example of explicitly providing the path to the file to load."""

    field: str

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        return (
            PyprojectTomlConfigSettingsSource(
                settings_cls, Path('~/.config').resolve() / 'pyproject.toml'
            ),
        )

字段值优先级

在以多种方式为同一个 Settings 字段指定值的情况下,所选值按以下方式确定(按优先级降序排列)

  1. 如果启用了 cli_parse_args,则在 CLI 中传递的参数。
  2. 传递给 Settings 类初始化器的参数。
  3. 环境变量,例如上面描述的 my_prefix_special_function
  4. 从 dotenv (.env) 文件加载的变量。
  5. 从秘密目录加载的变量。
  6. Settings 模型的默认字段值。

自定义设置来源

如果默认的优先级顺序不符合你的需求,可以通过覆盖 Settingssettings_customise_sources 方法来更改它。

settings_customise_sources 接收四个可调用对象作为参数,并返回任意数量的可调用对象作为元组。然后,这些可调用对象被调用以构建设置类字段的输入。

每个可调用对象都应将其设置类的实例作为其唯一参数,并返回一个 dict

更改优先级

返回的可调用对象的顺序决定了输入的优先级;第一个项目具有最高优先级。

from pydantic import PostgresDsn

from pydantic_settings import BaseSettings, PydanticBaseSettingsSource


class Settings(BaseSettings):
    database_dsn: PostgresDsn

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        return env_settings, init_settings, file_secret_settings


print(Settings(database_dsn='postgres://postgres@localhost:5432/kwargs_db'))
#> database_dsn=PostgresDsn('postgres://postgres@localhost:5432/kwargs_db')

通过翻转 env_settingsinit_settings,环境变量现在优先于 __init__ 关键字参数。

添加来源

如前所述,*pydantic* 附带了多个内置设置源。但是,你偶尔可能需要添加自己的自定义源,settings_customise_sources 使这变得非常容易

import json
from pathlib import Path
from typing import Any

from pydantic.fields import FieldInfo

from pydantic_settings import (
    BaseSettings,
    PydanticBaseSettingsSource,
    SettingsConfigDict,
)


class JsonConfigSettingsSource(PydanticBaseSettingsSource):
    """
    A simple settings source class that loads variables from a JSON file
    at the project's root.

    Here we happen to choose to use the `env_file_encoding` from Config
    when reading `config.json`
    """

    def get_field_value(
        self, field: FieldInfo, field_name: str
    ) -> tuple[Any, str, bool]:
        encoding = self.config.get('env_file_encoding')
        file_content_json = json.loads(
            Path('tests/example_test_config.json').read_text(encoding)
        )
        field_value = file_content_json.get(field_name)
        return field_value, field_name, False

    def prepare_field_value(
        self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool
    ) -> Any:
        return value

    def __call__(self) -> dict[str, Any]:
        d: dict[str, Any] = {}

        for field_name, field in self.settings_cls.model_fields.items():
            field_value, field_key, value_is_complex = self.get_field_value(
                field, field_name
            )
            field_value = self.prepare_field_value(
                field_name, field, field_value, value_is_complex
            )
            if field_value is not None:
                d[field_key] = field_value

        return d


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file_encoding='utf-8')

    foobar: str

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        return (
            init_settings,
            JsonConfigSettingsSource(settings_cls),
            env_settings,
            file_secret_settings,
        )


print(Settings())
#> foobar='test'

访问先前来源的结果

每个设置来源都可以访问前一个来源的输出。

from typing import Any

from pydantic.fields import FieldInfo

from pydantic_settings import PydanticBaseSettingsSource


class MyCustomSource(PydanticBaseSettingsSource):
    def get_field_value(
        self, field: FieldInfo, field_name: str
    ) -> tuple[Any, str, bool]: ...

    def __call__(self) -> dict[str, Any]:
        # Retrieve the aggregated settings from previous sources
        current_state = self.current_state
        current_state.get('some_setting')

        # Retrive settings from all sources individually
        # self.settings_sources_data["SettingsSourceName"]: dict[str, Any]
        settings_sources_data = self.settings_sources_data
        settings_sources_data['SomeSettingsSource'].get('some_setting')

        # Your code here...

移除来源

你可能还想禁用一个来源

from pydantic import ValidationError

from pydantic_settings import BaseSettings, PydanticBaseSettingsSource


class Settings(BaseSettings):
    my_api_key: str

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        # here we choose to ignore arguments from init_settings
        return env_settings, file_secret_settings


try:
    Settings(my_api_key='this is ignored')
except ValidationError as exc_info:
    print(exc_info)
    """
    1 validation error for Settings
    my_api_key
      Field required [type=missing, input_value={}, input_type=dict]
        For further information visit https://errors.pydantic.dev/2/v/missing
    """

原地重新加载

如果你想原地重新加载现有设置,可以使用其 __init__ 方法

import os

from pydantic import Field

from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    foo: str = Field('foo')


mutable_settings = Settings()

print(mutable_settings.foo)
#> foo

os.environ['foo'] = 'bar'
print(mutable_settings.foo)
#> foo

mutable_settings.__init__()
print(mutable_settings.foo)
#> bar

os.environ.pop('foo')
mutable_settings.__init__()
print(mutable_settings.foo)
#> foo