实验性功能¶
在本节中,您将找到关于 Pydantic 中新的实验性功能的文档。这些功能可能会更改或删除,我们正在征求反馈和建议,以便将它们永久纳入 Pydantic。
有关实验性功能的更多信息,请参阅我们的版本策略。
反馈¶
我们欢迎对实验性功能提供反馈!请在 Pydantic GitHub 仓库 上开启一个 issue,分享您的想法、请求或建议。
我们也鼓励您阅读现有的反馈,并将您的想法添加到现有的 issue 中。
导入时的警告¶
当您从 experimental
模块导入实验性功能时,您会看到一条警告消息,指出该功能是实验性的。您可以使用以下方法禁用此警告
import warnings
from pydantic import PydanticExperimentalWarning
warnings.filterwarnings('ignore', category=PydanticExperimentalWarning)
Pipeline API¶
Pydantic v2.8.0 引入了一个实验性的 "pipeline" API,它允许以比现有 API 更类型安全的方式组合解析(验证)、约束和转换。此 API 可能会更改或删除,我们正在征求反馈和建议,以便将其永久纳入 Pydantic。
通常,pipeline API 用于定义应用于传入数据的验证步骤序列。pipeline API 旨在比现有的 Pydantic API 更类型安全且更具可组合性。
pipeline 中的每个步骤可以是
- 对提供的类型运行 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)!
]
- 将字符串转换为小写。
- 约束整数大于零。
- 约束字符串匹配正则表达式模式。
- 您还可以使用较低级别的 transform、constrain 和 predicate 方法。
- 使用
|
或&
运算符组合步骤(如逻辑 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 代码,可能比上面的示例更易于理解、类型检查等。您选择的方法应真正取决于您的用例。您将需要比较冗长程度、性能、向用户返回有意义的错误难易程度等,以选择正确的模式。请注意不要仅仅因为可以就滥用像 pipeline API 这样的高级模式。
部分验证¶
Pydantic v2.10.0 引入了对 "部分验证" 的实验性支持。
这允许您验证不完整的 JSON 字符串,或表示不完整输入数据的 Python 对象。
当处理 LLM 的输出时,部分验证特别有用,其中模型流式传输结构化响应,并且您可能希望在仍在接收数据时开始验证流(例如,向用户显示部分数据)。
警告
部分验证是一项实验性功能,在未来的 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。
注意
如果您只想进行纯 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
。
支持的类型¶
目前,只有一部分集合验证器知道如何处理部分验证
list
set
frozenset
dict
(如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 解析器)的工作方式意味着目前无法区分完整的 JSON(如 {"a": 1, "b": "12"}
)和不完整的 JSON(如 {"a": 1, "b": "12
)。
这意味着在使用 experimental_allow_partial
时,Pydantic 将接受一些无效的 JSON,例如
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}}
注意
与 @validate_call
不同,此核心模式仅验证提供的参数,而不会调用底层的可调用对象。
这个新的核心模式将成为 Pydantic V3 中 @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}