实验性功能¶
在本节中,你将找到 Pydantic 中新的实验性功能的文档。这些功能可能会变更或移除,在将它们作为 Pydantic 的永久部分之前,我们正在寻求反馈和建议。
有关实验性功能的更多信息,请参阅我们的版本策略。
反馈¶
我们欢迎对实验性功能的反馈!请在 Pydantic GitHub 仓库 上提交 issue 来分享你的想法、请求或建议。
我们也鼓励你阅读现有的反馈,并将你的想法添加到现有的 issue 中。
导入时的警告¶
当你从 experimental 模块导入实验性功能时,你会看到一条警告消息,提示该功能是实验性的。你可以通过以下方式禁用此警告:
import warnings
from pydantic import PydanticExperimentalWarning
warnings.filterwarnings('ignore', category=PydanticExperimentalWarning)
管道 API¶
Pydantic v2.8.0 引入了一个实验性的“管道”API,它允许以比现有 API 更类型安全的方式组合解析(验证)、约束和转换。此 API 可能会变更或移除,在将其作为 Pydantic 的永久部分之前,我们正在寻求反馈和建议。
通常,管道 API 用于定义在验证期间应用于传入数据的一系列步骤。管道 API 设计为比现有的 Pydantic API 更类型安全和可组合。
管道中的每个步骤可以是:
- 对提供的类型运行 pydantic 验证的验证步骤
- 修改数据的转换步骤
- 根据条件检查数据的约束步骤
- 根据条件检查数据并在返回
False时引发错误的断言步骤
请注意,以下示例试图以复杂性为代价做到详尽无遗:如果你发现自己在类型注解中编写这么多转换,你可能需要考虑使用 UserIn 和 UserOut 模型(如下例所示)或类似的方式,通过符合习惯的普通 Python 代码进行转换。
这些 API 适用于代码节省显著且增加的复杂性相对较小的情况。
from __future__ import annotations
from datetime import datetime
from typing import Annotated
from pydantic import BaseModel
from pydantic.experimental.pipeline import validate_as
class User(BaseModel):
name: Annotated[str, validate_as(str).str_lower()] # (1)!
age: Annotated[int, validate_as(int).gt(0)] # (2)!
username: Annotated[str, validate_as(str).str_pattern(r'[a-z]+')] # (3)!
password: Annotated[
str,
validate_as(str)
.transform(str.lower)
.predicate(lambda x: x != 'password'), # (4)!
]
favorite_number: Annotated[ # (5)!
int,
(validate_as(int) | validate_as(str).str_strip().validate_as(int)).gt(
0
),
]
friends: Annotated[list[User], validate_as(...).len(0, 100)] # (6)!
bio: Annotated[
datetime,
validate_as(int)
.transform(lambda x: x / 1_000_000)
.validate_as(...), # (8)!
]
- 将字符串转换为小写。
- 约束整数大于零。
- 约束字符串匹配正则表达式模式。
- 你也可以使用较低级别的转换、约束和断言方法。
- 使用
|或&运算符组合步骤(类似于逻辑 OR 或 AND)。 - 使用
Ellipsis、...作为第一个位置参数调用validate_as(...)意味着validate_as(<字段类型>)。使用validate_as(Any)来接受任何类型。 - 你可以在其他步骤之前或之后调用
validate_as()进行预处理或后处理。
从 BeforeValidator、AfterValidator 和 WrapValidator 映射¶
validate_as 方法是定义 BeforeValidator、AfterValidator 和 WrapValidator 的更类型安全的方式:
from typing import Annotated
from pydantic.experimental.pipeline import transform, validate_as
# BeforeValidator
Annotated[int, validate_as(str).str_strip().validate_as(...)] # (1)!
# AfterValidator
Annotated[int, transform(lambda x: x * 2)] # (2)!
# WrapValidator
Annotated[
int,
validate_as(str)
.str_strip()
.validate_as(...)
.transform(lambda x: x * 2), # (3)!
]
- 在将字符串解析为整数之前去除其空白字符。
- 在解析整数后将其乘以 2。
- 去除字符串的空白字符,将其验证为整数,然后乘以 2。
替代模式¶
根据场景不同,有许多替代模式可以使用。
举个例子,考虑上面提到的 UserIn 和 UserOut 模式:
from __future__ import annotations
from pydantic import BaseModel
class UserIn(BaseModel):
favorite_number: int | str
class UserOut(BaseModel):
favorite_number: int
def my_api(user: UserIn) -> UserOut:
favorite_number = user.favorite_number
if isinstance(favorite_number, str):
favorite_number = int(user.favorite_number.strip())
return UserOut(favorite_number=favorite_number)
assert my_api(UserIn(favorite_number=' 1 ')).favorite_number == 1
这个例子使用了普通的符合习惯的 Python 代码,可能比上面的例子更容易理解、类型检查等。 你选择的方法应该真正取决于你的用例。 你需要比较冗长性、性能、向用户返回有意义的错误的难易程度等,以选择正确的模式。 请注意不要仅仅因为可以使用而滥用像管道 API 这样的高级模式。
部分验证¶
Pydantic v2.10.0 引入了对“部分验证”的实验性支持。
这允许你验证不完整的 JSON 字符串,或表示不完整输入数据的 Python 对象。
部分验证在处理 LLM 的输出时特别有用,当模型流式传输结构化响应时,你可能希望在仍在接收数据时开始验证流(例如,向用户显示部分数据)。
Warning
部分验证是一个实验性功能,可能在未来的 Pydantic 版本中变更。当前的实现应被视为概念验证,并且有许多限制。
部分验证可以在使用 TypeAdapter 上的三种验证方法时启用:TypeAdapter.validate_json()、TypeAdapter.validate_python() 和 TypeAdapter.validate_strings()。这允许你解析和验证不完整的 JSON,也可以验证通过解析任何格式的不完整数据创建的 Python 对象。
experimental_allow_partial 标志可以传递给这些方法以启用部分验证。
它可以接受以下值(默认为 False):
False或'off'- 禁用部分验证True或'on'- 启用部分验证,但不支持尾部字符串'trailing-strings'- 启用部分验证并支持尾部字符串
'trailing-strings' 模式
'trailing-strings' 模式允许在部分 JSON 的末尾包含不完整的尾部字符串。
例如,如果你针对以下模型进行验证:
from typing import TypedDict
class Model(TypedDict):
a: str
b: str
那么以下 JSON 输入将被视为有效,尽管末尾有不完整的字符串:
'{"a": "hello", "b": "wor'
并将被验证为:
{'a': 'hello', 'b': 'wor'}
experiment_allow_partial 的实际应用:
from typing import Annotated
from annotated_types import MinLen
from typing_extensions import NotRequired, TypedDict
from pydantic import TypeAdapter
class Foobar(TypedDict): # (1)!
a: int
b: NotRequired[float]
c: NotRequired[Annotated[str, MinLen(5)]]
ta = TypeAdapter(list[Foobar])
v = ta.validate_json('[{"a": 1, "b"', experimental_allow_partial=True) # (2)!
print(v)
#> [{'a': 1}]
v = ta.validate_json(
'[{"a": 1, "b": 1.0, "c": "abcd', experimental_allow_partial=True # (3)!
)
print(v)
#> [{'a': 1, 'b': 1.0}]
v = ta.validate_json(
'[{"b": 1.0, "c": "abcde"', experimental_allow_partial=True # (4)!
)
print(v)
#> []
v = ta.validate_json(
'[{"a": 1, "b": 1.0, "c": "abcde"},{"a": ', experimental_allow_partial=True
)
print(v)
#> [{'a': 1, 'b': 1.0, 'c': 'abcde'}]
v = ta.validate_python([{'a': 1}], experimental_allow_partial=True) # (5)!
print(v)
#> [{'a': 1}]
v = ta.validate_python(
[{'a': 1, 'b': 1.0, 'c': 'abcd'}], experimental_allow_partial=True # (6)!
)
print(v)
#> [{'a': 1, 'b': 1.0}]
v = ta.validate_json(
'[{"a": 1, "b": 1.0, "c": "abcdefg',
experimental_allow_partial='trailing-strings', # (7)!
)
print(v)
#> [{'a': 1, 'b': 1.0, 'c': 'abcdefg'}]
- TypedDict
Foobar有三个字段,但只有a是必需的,这意味着即使b和c字段缺失,也可以创建有效的Foobar实例。 - 解析 JSON,输入在字符串被截断的位置之前是有效的 JSON。
- 在这种情况下,输入的截断意味着
c的值(abcd)作为c字段的输入无效,因此被省略。 a字段是必需的,因此列表中唯一项的验证失败并被丢弃。- 部分验证也适用于 Python 对象,它应该具有与 JSON 相同的语义,当然你不能有一个真正“不完整”的 Python 对象。
- 与上面相同,但使用 Python 对象,
c因为不是必需的且验证失败而被丢弃。 trailing-strings模式允许在部分 JSON 的末尾包含不完整的字符串,在这种情况下,输入在字符串被截断的位置之前是有效的 JSON,因此最后一个字符串被包含。
部分验证的工作原理¶
部分验证遵循 Pydantic 的禅意——它不保证输入数据可能是什么,但保证返回你所要求类型的有效实例,或引发验证错误。
为此,experimental_allow_partial 标志启用两种行为:
1. 部分 JSON 解析¶
Pydantic 使用的 jiter JSON 解析器已经支持解析部分 JSON,
experimental_allow_partial 只是通过 allow_partial 参数传递给 jiter。
Note
如果你只想要支持部分 JSON 的纯 JSON 解析,你可以直接使用 jiter Python 库,或者在调用 pydantic_core.from_json 时传递 allow_partial 参数。
2. 忽略输入最后一个元素中的错误¶
仅能访问部分输入数据意味着错误通常发生在输入数据的最后一个元素中。
例如:
- 如果字符串有约束
MinLen(5),当你只看到部分输入时,验证可能会失败,因为部分字符串缺失(例如{"name": "Sam而不是{"name": "Samuel"}) - 如果
int字段有约束Ge(10),当你只看到部分输入时,验证可能会失败,因为数字太小(例如1而不是10) - 如果
TypedDict字段有 3 个必需字段,但部分输入只有两个字段,验证将失败,因为某些字段缺失 - 等等——还有更多类似的情况
关键是如果你只看到某些有效输入数据的一部分,验证错误通常发生在序列的最后一个元素或映射的最后一个值中。
为了避免这些错误破坏部分验证,Pydantic 将忽略输入数据最后一个元素中的所有错误。
from typing import Annotated
from annotated_types import MinLen
from pydantic import BaseModel, TypeAdapter
class MyModel(BaseModel):
a: int
b: Annotated[str, MinLen(5)]
ta = TypeAdapter(list[MyModel])
v = ta.validate_json(
'[{"a": 1, "b": "12345"}, {"a": 1,',
experimental_allow_partial=True,
)
print(v)
#> [MyModel(a=1, b='12345')]
部分验证的限制¶
仅限 TypeAdapter¶
你只能将 experiment_allow_partial 传递给 TypeAdapter 方法,它尚未通过其他 Pydantic 入口点(如 BaseModel)支持。
支持的类型¶
目前只有一部分集合验证器知道如何处理部分验证:
listsetfrozensetdict(如dict[X, Y])TypedDict— 只有非必需字段可能缺失,例如通过NotRequired或total=False)
虽然你可以在针对包含其他集合验证器的类型进行验证时使用 experimental_allow_partial,但这些类型将被“全有或全无”地验证,部分验证将无法在更嵌套的类型上工作。
例如,在上面的例子中,部分验证有效,尽管列表中的第二项被完全丢弃,因为 BaseModel(尚)不支持部分验证。
但部分验证在以下示例中完全无法工作,因为 BaseModel 不支持部分验证,因此它不会将 allow_partial 指令向下传递给 b 中的列表验证器:
from typing import Annotated
from annotated_types import MinLen
from pydantic import BaseModel, TypeAdapter, ValidationError
class MyModel(BaseModel):
a: int = 1
b: list[Annotated[str, MinLen(5)]] = [] # (1)!
ta = TypeAdapter(MyModel)
try:
v = ta.validate_json(
'{"a": 1, "b": ["12345", "12', experimental_allow_partial=True
)
except ValidationError as e:
print(e)
"""
1 validation error for MyModel
b.1
String should have at least 5 characters [type=string_too_short, input_value='12', input_type=str]
"""
b的列表验证器没有从模型验证器那里接收到传递下来的allow_partial指令,因此它不知道忽略输入最后一个元素中的错误。
一些无效但完整的 JSON 将被接受¶
jiter(Pydantic 使用的 JSON 解析器)的工作方式意味着目前无法区分像 {"a": 1, "b": "12"} 这样的完整 JSON 和像 {"a": 1, "b": "12 这样的不完整 JSON。
这意味着在使用 experimental_allow_partial 时,一些无效的 JSON 将被 Pydantic 接受,例如:
from typing import Annotated
from annotated_types import MinLen
from typing_extensions import TypedDict
from pydantic import TypeAdapter
class Foobar(TypedDict, total=False):
a: int
b: Annotated[str, MinLen(5)]
ta = TypeAdapter(Foobar)
v = ta.validate_json(
'{"a": 1, "b": "12', experimental_allow_partial=True # (1)!
)
print(v)
#> {'a': 1}
v = ta.validate_json(
'{"a": 1, "b": "12"}', experimental_allow_partial=True # (2)!
)
print(v)
#> {'a': 1}
- 这将通过验证,正如预期的那样,尽管最后一个字段将因验证失败而被省略。
- 这也将通过验证,因为传递给 pydantic-core 的 JSON 数据的二进制表示与先前情况无法区分。
from typing import Annotated
from annotated_types import MinLen
from typing import TypedDict
from pydantic import TypeAdapter
class Foobar(TypedDict, total=False):
a: int
b: Annotated[str, MinLen(5)]
ta = TypeAdapter(Foobar)
v = ta.validate_json(
'{"a": 1, "b": "12', experimental_allow_partial=True # (1)!
)
print(v)
#> {'a': 1}
v = ta.validate_json(
'{"a": 1, "b": "12"}', experimental_allow_partial=True # (2)!
)
print(v)
#> {'a': 1}
- 这将通过验证,正如预期的那样,尽管最后一个字段将因验证失败而被省略。
- 这也将通过验证,因为传递给 pydantic-core 的 JSON 数据的二进制表示与先前情况无法区分。
输入最后一个字段中的任何错误都将被忽略¶
如上面所述,截断输入可能导致许多错误。Pydantic 不是尝试特别忽略可能由截断引起的错误,而是在部分验证模式下忽略输入最后一个元素中的所有错误。
这意味着如果错误在输入的最后一个字段中,明显无效的数据将通过验证:
from typing import Annotated
from annotated_types import Ge
from pydantic import TypeAdapter
ta = TypeAdapter(list[Annotated[int, Ge(10)]])
v = ta.validate_python([20, 30, 4], experimental_allow_partial=True) # (1)!
print(v)
#> [20, 30]
ta = TypeAdapter(list[int])
v = ta.validate_python([1, 2, 'wrong'], experimental_allow_partial=True) # (2)!
print(v)
#> [1, 2]
- 正如你所期望的,这将通过验证,因为 Pydantic 正确地忽略了(截断的)最后一项中的错误。
- 这也将通过验证,因为最后一项中的错误被忽略。
可调用对象参数的验证¶
Pydantic 提供了 @validate_call 装饰器来对提供的参数(以及返回类型)执行验证。但是,它只允许通过实际调用装饰后的可调用对象来提供参数。在某些情况下,你可能只想验证参数,例如从其他数据源(如 JSON 数据)加载时。
为此,实验性的 generate_arguments_schema()
函数可用于构建核心模式,该模式稍后可与 SchemaValidator 一起使用。
from pydantic_core import SchemaValidator
from pydantic.experimental.arguments_schema import generate_arguments_schema
def func(p: bool, *args: str, **kwargs: int) -> None: ...
arguments_schema = generate_arguments_schema(func=func)
val = SchemaValidator(arguments_schema, config={'coerce_numbers_to_str': True})
args, kwargs = val.validate_json(
'{"p": true, "args": ["arg1", 1], "kwargs": {"extra": 1}}'
)
print(args, kwargs) # (1)!
#> (True, 'arg1', '1') {'extra': 1}
-
如果你想要经过验证的参数作为字典,你可以使用
Signature.bind()方法:from inspect import signature signature(func).bind(*args, **kwargs).arguments #> {'p': True, 'args': ('arg1', '1'), 'kwargs': {'extra': 1}}
Note
与 @validate_call 不同,此核心模式只会验证提供的参数;
底层可调用对象将不会被调用。
此外,你可以通过提供回调来忽略特定参数,该回调为每个参数调用:
from typing import Any
from pydantic_core import SchemaValidator
from pydantic.experimental.arguments_schema import generate_arguments_schema
def func(p: bool, *args: str, **kwargs: int) -> None: ...
def skip_first_parameter(index: int, name: str, annotation: Any) -> Any:
if index == 0:
return 'skip'
arguments_schema = generate_arguments_schema(
func=func,
parameters_callback=skip_first_parameter,
)
val = SchemaValidator(arguments_schema)
args, kwargs = val.validate_json('{"args": ["arg1"], "kwargs": {"extra": 1}}')
print(args, kwargs)
#> ('arg1',) {'extra': 1}
MISSING 标记¶
MISSING 标记是一个单例,表示在验证期间未提供字段值。
此单例可用作默认值,作为 None 的替代方案,当 None 具有明确含义时。在序列化期间,任何具有 MISSING 作为值的字段都将从输出中排除。
from typing import Union
from pydantic import BaseModel
from pydantic.experimental.missing_sentinel import MISSING
class Configuration(BaseModel):
timeout: Union[int, None, MISSING] = MISSING
# 配置默认值,存储在其他地方:
defaults = {'timeout': 200}
conf = Configuration()
# `timeout` 从序列化输出中排除:
conf.model_dump()
# {}
# `MISSING` 值不会出现在 JSON Schema 中:
Configuration.model_json_schema()['properties']['timeout']
#> {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Timeout'}}
# 可以使用 `is` 来区分标记和其他值:
timeout = conf.timeout if conf.timeout is not MISSING else defaults['timeout']
from pydantic import BaseModel
from pydantic.experimental.missing_sentinel import MISSING
class Configuration(BaseModel):
timeout: int | None | MISSING = MISSING
# 配置默认值,存储在其他地方:
defaults = {'timeout': 200}
conf = Configuration()
# `timeout` 从序列化输出中排除:
conf.model_dump()
# {}
# `MISSING` 值不会出现在 JSON Schema 中:
Configuration.model_json_schema()['properties']['timeout']
#> {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Timeout'}}
# 可以使用 `is` 来区分标记和其他值:
timeout = conf.timeout if conf.timeout is not MISSING else defaults['timeout']
此功能被标记为实验性,因为它依赖于草案 PEP 661,该草案在标准库中引入了标记。
因此,目前适用以下限制:
- 标记的静态类型检查仅支持 Pyright
1.1.402
或更高版本,并且应启用
enableExperimentalFeatures类型评估设置。 - 不支持包含
MISSING作为值的模型的序列化。