设置管理¶
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},
}
"""
-
环境变量名称使用
validation_alias
覆盖。 在这种情况下,将读取环境变量my_auth_key
而不是auth_key
。查看
Field
文档 以获取更多信息。 -
环境变量名称使用
alias
覆盖。 在这种情况下,环境变量my_api_key
将用于验证和序列化,而不是api_key
。查看
Field
文档 以获取更多信息。 -
AliasChoices
类允许为单个字段设置多个环境变量名称。 将使用找到的第一个环境变量。查看 关于别名选择的文档 以获取更多信息。
-
ImportString
类允许从字符串导入对象。 在这种情况下,将读取环境变量special_function
,并将导入函数math.cos
。 -
env_prefix
配置设置允许为所有环境变量设置前缀。查看 环境变量名称文档 以获取更多信息。
默认值验证¶
与 pydantic BaseModel
不同,默认情况下会验证 BaseSettings
字段的默认值。 您可以通过在 model_config
中或在字段级别通过 Field(validate_default=False)
设置 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
不仅适用于 env 设置,还适用于 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)!
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_sensitive
为 True
时,环境变量名称必须与字段名称匹配(可选地带有前缀),因此在本示例中,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
"""
- 请注意,由于环境变量名称为
HOST
(全部大写),因此找不到host
字段。
注意
在 Windows 上,Python 的 os
模块始终将环境变量视为不区分大小写,因此 case_sensitive
配置设置将不起作用 - 设置将始终在忽略大小写的情况下更新。
解析环境变量值¶
默认情况下,环境变量按字面意思解析,包括值为空的情况。 您可以选择通过将 env_ignore_empty
配置设置设置为 True
来忽略空环境变量。 如果您希望使用字段的默认值而不是来自环境的空值,这将非常有用。
对于大多数简单字段类型(例如 int
、float
、str
等),环境变量值的解析方式与直接传递给初始化器(作为字符串)的方式相同。
诸如 list
、set
、dict
和子模型之类的复杂类型是通过将环境变量的值视为 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'}},
}
"""
-
子模型必须继承自
pydantic.BaseModel
。 -
子模型必须继承自
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=1
,GENERATION_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]}
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]}
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 文件遵循所有环境变量的相同一般原则,它看起来像这样:
# ignore comment
ENVIRONMENT="production"
REDIS_ADDRESS=localhost:6379
MEANING_OF_LIFE=42
MY_VAR='Hello world'
一旦您的 .env
文件中填充了变量,pydantic 支持两种加载方式:
- 在
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')
- 使用
_env_file
关键字参数(以及需要的_env_file_encoding
)实例化BaseSettings
派生类在任何一种情况下,传递的参数值都可以是任何有效路径或文件名,绝对路径或相对于当前工作目录的相对路径。 从那里,pydantic 将为您处理一切,加载您的变量并验证它们。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')
注意
如果为 env_file
指定了文件名,Pydantic 将仅检查当前工作目录,而不会检查任何父目录中的 .env
文件。
即使在使用 dotenv 文件时,pydantic 仍将读取环境变量以及 dotenv 文件,**环境变量将始终优先于从 dotenv 文件加载的值**。
通过实例化时(方法 2)的 _env_file
关键字参数传递文件路径将覆盖在 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')
)
您还可以使用关键字参数覆盖来告诉 Pydantic 不加载任何文件(即使在 model_config
类中设置了一个文件),方法是传递 None
作为实例化关键字参数,例如 settings = Settings(_env_file=None)
。
由于 python-dotenv 用于解析文件,因此可以使用类似 bash 的语义,例如 export
,这(取决于您的操作系统和环境)可能允许您的 dotenv 文件也与 source
一起使用,有关更多详细信息,请参见 python-dotenv 的文档。
对于 dotenv 文件,Pydantic settings 考虑 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 settings 从 dotenv 文件加载所有值并将其传递给模型,而与模型的 env_prefix
无关。 因此,如果您在 dotenv 文件中提供额外的值,无论它们是否以 env_prefix
开头,都会引发 ValidationError
。
命令行支持¶
Pydantic settings 提供了集成的 CLI 支持,可以轻松地使用 Pydantic 模型快速定义 CLI 应用程序。 Pydantic settings CLI 有两个主要用例:
- 当使用 CLI 覆盖 Pydantic 模型中的字段时。
- 当使用 Pydantic 模型定义 CLI 时。
默认情况下,体验是为用例 #1 量身定制的,并建立在 解析环境变量 中建立的基础之上。 如果您的用例主要属于 #2,则您可能需要启用 创建 CLI 应用程序 末尾概述的大多数默认设置。
基础知识¶
为了开始使用,让我们回顾一下 解析环境变量 中介绍的示例,但这次使用 Pydantic settings 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'}
子命令和位置参数¶
子命令和位置参数使用 CliSubCommand
和 CliPositionalArg
注解表示。 子命令注解只能应用于必需字段(即没有默认值的字段)。 此外,子命令必须是从 pydantic BaseModel
或 pydantic.dataclasses dataclass
派生的有效类型。
可以使用 get_subcommand
实用程序函数从模型实例中检索已解析的子命令。 如果子命令不是必需的,请将 is_required
标志设置为 False
,以禁用在未找到子命令时引发错误。
注意
CLI 设置子命令限制为每个模型一个子解析器。 换句话说,模型的全部子命令都分组在单个子解析器下; 它不允许有多个子解析器,每个子解析器都有自己的一组子命令。 有关子解析器的更多信息,请参见 argparse 子命令。
注意
CliSubCommand
和 CliPositionalArg
始终区分大小写。
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',
}
CliSubCommand
和 CliPositionalArg
注解还支持联合操作和别名。 对于 Pydantic 模型的联合,重要的是要记住验证期间可能出现的细微差别。 具体来说,对于内容相同的子命令的联合,建议将它们分解为单独的 CliSubCommand
字段,以避免任何复杂情况。 最后,从联合派生的子命令名称将是 Pydantic 模型类本身的名称。
当为 CliSubCommand
或 CliPositionalArg
字段分配别名时,只能分配单个别名。 对于非联合子命令,别名将更改显示的帮助文本和子命令名称。 相反,对于联合子命令,从 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.run
和 CliApp.run_subcommand
,可用于将 Pydantic BaseSettings
、BaseModel
或 pydantic.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
方法。
对于 BaseModel
和 pydantic.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 settings 支持通过 CliApp.run
和 CliApp.run_subcommand
运行异步 CLI 命令。 借助此功能,您可以在 Pydantic 模型(包括子命令)中定义 async def 方法,并像执行同步方法一样执行它们。 具体来说:
- 支持异步方法:您现在可以将您的 cli_cmd 或类似的 CLI 入口点方法标记为 async def,并让 CliApp 执行它们。
- 子命令也可能是异步的:如果您有嵌套的 CLI 子命令,则最终(最低级别)子命令方法同样可以是异步的。
- 将异步方法限制为最终子命令:不建议将父命令定义为异步,因为它可能导致创建额外的线程和事件循环。 为了获得最佳性能并避免不必要的资源使用,仅将您最深层(子级)的子命令实现为 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 settings 会自动检测当前线程是否已具有活动的事件循环。 如果是这样,则异步命令将在新线程中运行以避免冲突。 否则,它将在当前线程中使用 asyncio.run()。 此处理确保您的异步子命令“只是工作”,而无需额外的手动设置。
互斥组¶
可以通过继承 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
,这消除了传递显式值的需要。
此外,提供的 CliImplicitFlag
和 CliExplicitFlag
注解可以在必要时用于更精细的控制。
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 不会忽略任何参数。
import sys
from pydantic_settings import BaseSettings
class Settings(BaseSettings, cli_parse_args=True, cli_ignore_unknown_args=True):
good_arg: str
sys.argv = ['example.py', '--bad-arg=bad', 'ANOTHER_BAD_ARG', '--good_arg=hello world']
print(Settings().model_dump())
#> {'good_arg': 'hello world'}
CLI 烤串命名法参数¶
通过启用 cli_kebab_case
更改 CLI 参数是否应使用烤串命名法。
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_json
为 False
,则默认为 “null”,如果 cli_avoid_json
为 True
,则默认为 “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 设置源(指定 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
。 仅当解析器方法是必需的但设置为 None
时,CLI 设置才会在连接到根解析器时引发错误。
注意
formatter_class
仅应用于子命令。 CliSettingsSource
永远不会触及或修改任何外部解析器设置,以避免破坏性更改。 由于子命令驻留在它们自己的内部解析器树上,因此我们可以安全地应用 formatter_class
设置,而不会破坏外部解析器逻辑。
密钥¶
将密钥值放在文件中是为应用程序提供敏感配置的常见模式。
密钥文件遵循与 dotenv 文件相同的原则,不同之处在于它仅包含单个值,并且文件名用作键。 密钥文件看起来像这样:
super_secret_database_password
一旦您拥有了密钥文件,pydantic 支持两种加载方式:
- 在
BaseSettings
类中的model_config
上设置secrets_dir
到您的密钥文件存储的目录。from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(secrets_dir='/var/run') database_password: str
- 使用
_secrets_dir
关键字参数实例化BaseSettings
派生类。settings = Settings(_secrets_dir='/var/run')
在任何一种情况下,传递的参数值都可以是任何有效目录,绝对路径或相对于当前工作目录的相对路径。 **请注意,不存在的目录只会生成警告**。 从那里,pydantic 将为您处理一切,加载您的变量并验证它们。
即使在使用密钥目录时,pydantic 仍将从 dotenv 文件或环境中读取环境变量,**dotenv 文件和环境变量将始终优先于从密钥目录加载的值**。
通过实例化时(方法 2)的 _secrets_dir
关键字参数传递文件路径将覆盖在 model_config
类上设置的值(如果有)。
如果您需要从多个密钥目录加载设置,则可以将多个路径作为元组或列表传递。 就像 env_file
一样,来自后续路径的值会覆盖先前路径的值。
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 密钥可用于为在 Docker 容器中运行的应用程序提供敏感配置。 要在 pydantic 应用程序中使用这些密钥,过程很简单。 有关在 Docker 中创建、管理和使用密钥的更多信息,请参见官方 Docker 文档。
首先,使用指定密钥目录的 SettingsConfigDict
定义您的 Settings
类。
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
AWS Secrets Manager¶
您必须设置一个参数
secret_id
:AWS 密钥 ID
您必须在密钥的值中以及字段名称中采用相同的命名约定。例如,如果密钥中的键名为 SqlServerPassword
,则字段名称必须相同。您也可以使用别名。
在 AWS Secrets Manager 中,嵌套模型通过键名中的 --
分隔符来支持。例如,SqlServer--Password
。
不支持数组(例如,MySecret--0
、MySecret--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--0
、MySecret--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,
)
其他设置源¶
其他设置源可用于常见的配置文件
- 使用
json_file
和json_file_encoding
参数的JsonConfigSettingsSource
- 使用 (可选)
pyproject_toml_depth
和 (可选)pyproject_toml_table_header
参数的PyprojectTomlConfigSettingsSource
- 使用
toml_file
参数的TomlConfigSettingsSource
- 使用
yaml_file
和 yaml_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
字段以多种方式指定值的情况下,选定的值按以下方式确定(按优先级降序排列)
- 如果启用
cli_parse_args
,则为在 CLI 中传入的参数。 - 传递给
Settings
类初始化程序的参数。 - 环境变量,例如上面描述的
my_prefix_special_function
。 - 从 dotenv (
.env
) 文件加载的变量。 - 从 secrets 目录加载的变量。
Settings
模型的默认字段值。
自定义设置源¶
如果默认的优先级顺序不符合您的需求,可以通过覆盖 Settings
的 settings_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_settings
和 init_settings
,环境变量现在比 __init__
kwargs 具有更高的优先级。
添加源¶
如前所述,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