字段类型
在可能的情况下 pydantic 使用 标准库类型 来定义字段,从而平滑学习曲线。 然而,对于许多有用的应用程序,不存在标准库类型,因此 pydantic 实现 许多常用类型。
如果没有适合您目的的现有类型,您还可以使用自定义属性和验证来实现您的自己的 pydantic 兼容类型。
标准库类型(Standard Library Types)¶
pydantic 支持 Python 标准库中的许多常见类型。 如果您需要更严格的处理,请参阅 严格类型; 如果您需要限制允许的值(例如需要一个正整数),请参阅 Constrained Types。
None
、type(None)
或 Literal[None]
(等同于 PEP 484) : 只允许 None
值
bool
- 请参阅下面的 Booleans,了解有关如何验证布尔值以及允许使用哪些值的详细信息
int
- pydantic 使用
int(v)
将类型强制转换为int
; 请参阅 this 关于数据转换期间信息丢失的警告 float
- 类似地,
float(v)
用于将值强制转换为浮点数 str
- 字符串按原样接受,
int
float
和Decimal
使用str(v)
强制转换,bytes
和bytearray
使用v.decode()
转换,枚举继承自str
使用v.value
进行转换,所有其他类型都会导致错误 bytes
bytes
按原样接受,bytearray
使用bytes(v)
转换,str
使用v.encode()
转换,int
、float
和Decimal
是 使用str(v).encode()
强制list
- 允许
list
、tuple
、set
、frozenset
、deque
或生成器和转换为列表; 有关子类型约束,请参见下面的typing.List
tuple
- 允许
list
、tuple
、set
、frozenset
、deque
或生成器和转换为元组; 有关子类型约束,请参见下面的typing.Tuple
dict
dict(v)
用于尝试转换字典; 请参阅下面的typing.Dict
了解子类型约束set
- 允许将
list
、tuple
、set
、frozenset
、deque
或生成器和强制转换为一个集合; 有关子类型约束,请参见下面的typing.Set
frozenset
- 允许
list
、tuple
、set
、frozenset
、deque
或生成器和强制转换为冻结集; 有关子类型约束,请参阅下面的typing.FrozenSet
deque
- 允许
list
、tuple
、set
、frozenset
、deque
或生成器和转换为双端队列; 有关子类型约束,请参见下面的typing.Deque
datetime.date
- 有关解析和验证的更多详细信息,请参阅下面的 Datetime Types
datetime.time
- 有关解析和验证的更多详细信息,请参阅下面的 Datetime Types
datetime.datetime
- 有关解析和验证的更多详细信息,请参阅下面的 Datetime Types
datetime.timedelta
- 有关解析和验证的更多详细信息,请参阅下面的 Datetime Types
typing.Any
- 允许任何值,包括
None
,因此Any
字段是可选的 typing.Annotated
- 根据 PEP-593,允许使用任意元数据包装另一种类型。
Annotated
提示可能包含对Field
函数 的单个调用,但其他元数据将被忽略并使用根类型。 typing.TypeVar
- 根据
constraints
或bound
限制允许的值,参见 TypeVar typing.Union
- 有关解析和验证的更多详细信息,请参阅下面的 Union
typing.Optional
Optional[x]
只是Union[x, None]
的简写; 有关解析和验证的更多详细信息,请参阅下面的 Union,有关可以接收None
作为值的必填字段的详细信息,请参阅 Required Fields。typing.List
- 有关解析和验证的更多详细信息,请参阅下面的 Typing Iterables
typing.Tuple
- 有关解析和验证的更多详细信息,请参阅下面的 Typing Iterables
subclass of typing.NamedTuple
- 与
tuple
相同,但使用给定的namedtuple
实例化并验证字段,因为它们是带注释的。 有关解析和验证的更多详细信息,请参阅下面的 注释类型 subclass of collections.namedtuple
- 与
subclass of typing.NamedTuple
相同,但所有字段都将具有Any
类型,因为它们没有注释 typing.Dict
- 有关解析和验证的更多详细信息,请参阅下面的 Typing Iterables
subclass of typing.TypedDict
- 与
dict
相同,但 pydantic 将验证字典,因为键被注释了。 有关解析和验证的更多详细信息,请参阅下面的 注释类型 typing.Set
- 有关解析和验证的更多详细信息,请参阅下面的 Typing Iterables
typing.FrozenSet
- 有关解析和验证的更多详细信息,请参阅下面的 Typing Iterables
typing.Deque
- 有关解析和验证的更多详细信息,请参阅下面的 Typing Iterables
typing.Sequence
- 有关解析和验证的更多详细信息,请参阅下面的 Typing Iterables
typing.Iterable
- 这是为不应使用的迭代器保留的。 有关解析和验证的更多详细信息,请参阅下面的 Infinite Generators
typing.Type
- 有关解析和验证的更多详细信息,请参阅下面的 Type
typing.Callable
- 有关解析和验证的更多详细信息,请参阅下面的 Callable
typing.Pattern
- 将导致输入值被传递给
re.compile(v)
以创建正则表达式模式 ipaddress.IPv4Address
- 通过将值传递给
IPv4Address(v)
来简单地使用类型本身进行验证; 有关其他自定义 IP 地址类型,请参阅 Pydantic Types ipaddress.IPv4Interface
- 通过将值传递给
IPv4Address(v)
来简单地使用类型本身进行验证; 有关其他自定义 IP 地址类型,请参阅 Pydantic Types ipaddress.IPv4Network
- 通过将值传递给
IPv4Network(v)
来简单地使用类型本身进行验证; 有关其他自定义 IP 地址类型,请参阅 Pydantic Types ipaddress.IPv6Address
- 通过将值传递给
IPv6Address(v)
来简单地使用类型本身进行验证; 有关其他自定义 IP 地址类型,请参阅 Pydantic Types ipaddress.IPv6Interface
- 通过将值传递给
IPv6Interface(v)
来简单地使用类型本身进行验证; 有关其他自定义 IP 地址类型,请参阅 Pydantic Types ipaddress.IPv6Network
- 通过将值传递给
IPv6Network(v)
来简单地使用类型本身进行验证; 有关其他自定义 IP 地址类型,请参阅 Pydantic Types enum.Enum
- 检查该值是否为有效的 Enum 实例
subclass of enum.Enum
- 检查该值是否是枚举的有效成员; 有关详细信息,请参阅 枚举和选择
enum.IntEnum
- 检查该值是否为有效的 IntEnum 实例
subclass of enum.IntEnum
- 检查该值是否是整数枚举的有效成员; 有关详细信息,请参阅 枚举和选择
decimal.Decimal
- pydantic 尝试将值转换为字符串,然后将字符串传递给“Decimal(v)”
pathlib.Path
- 通过将值传递给
Path(v)
来简单地使用类型本身进行验证; 有关其他更严格的路径类型,请参阅 Pydantic Types uuid.UUID
- 字符串和字节(转换为字符串)被传递给
UUID(v)
,对于bytes
和bytearray
回退到UUID(bytes=v)
; 有关其他更严格的 UUID 类型,请参阅 Pydantic Types ByteSize
- 将带单位的字节字符串转换为字节
可迭代类型(Typing Iterables)¶
pydantic 使用 PEP 484 中定义的标准库“键入”类型来定义复杂对象。
from typing import (
Deque, Dict, FrozenSet, List, Optional, Sequence, Set, Tuple, Union
)
from pydantic import BaseModel
class Model(BaseModel):
simple_list: list = None
list_of_ints: List[int] = None
simple_tuple: tuple = None
tuple_of_different_types: Tuple[int, float, str, bool] = None
simple_dict: dict = None
dict_str_float: Dict[str, float] = None
simple_set: set = None
set_bytes: Set[bytes] = None
frozen_set: FrozenSet[int] = None
str_or_bytes: Union[str, bytes] = None
none_or_str: Optional[str] = None
sequence_of_ints: Sequence[int] = None
compound: Dict[Union[str, bytes], List[Set[int]]] = None
deque: Deque[int] = None
print(Model(simple_list=['1', '2', '3']).simple_list)
#> ['1', '2', '3']
print(Model(list_of_ints=['1', '2', '3']).list_of_ints)
#> [1, 2, 3]
print(Model(simple_dict={'a': 1, b'b': 2}).simple_dict)
#> {'a': 1, b'b': 2}
print(Model(dict_str_float={'a': 1, b'b': 2}).dict_str_float)
#> {'a': 1.0, 'b': 2.0}
print(Model(simple_tuple=[1, 2, 3, 4]).simple_tuple)
#> (1, 2, 3, 4)
print(Model(tuple_of_different_types=[4, 3, 2, 1]).tuple_of_different_types)
#> (4, 3.0, '2', True)
print(Model(sequence_of_ints=[1, 2, 3, 4]).sequence_of_ints)
#> [1, 2, 3, 4]
print(Model(sequence_of_ints=(1, 2, 3, 4)).sequence_of_ints)
#> (1, 2, 3, 4)
print(Model(deque=[1, 2, 3]).deque)
#> deque([1, 2, 3])
from typing import (
Deque, Optional, Union
)
from collections.abc import Sequence
from pydantic import BaseModel
class Model(BaseModel):
simple_list: list = None
list_of_ints: list[int] = None
simple_tuple: tuple = None
tuple_of_different_types: tuple[int, float, str, bool] = None
simple_dict: dict = None
dict_str_float: dict[str, float] = None
simple_set: set = None
set_bytes: set[bytes] = None
frozen_set: frozenset[int] = None
str_or_bytes: Union[str, bytes] = None
none_or_str: Optional[str] = None
sequence_of_ints: Sequence[int] = None
compound: dict[Union[str, bytes], list[set[int]]] = None
deque: Deque[int] = None
print(Model(simple_list=['1', '2', '3']).simple_list)
#> ['1', '2', '3']
print(Model(list_of_ints=['1', '2', '3']).list_of_ints)
#> [1, 2, 3]
print(Model(simple_dict={'a': 1, b'b': 2}).simple_dict)
#> {'a': 1, b'b': 2}
print(Model(dict_str_float={'a': 1, b'b': 2}).dict_str_float)
#> {'a': 1.0, 'b': 2.0}
print(Model(simple_tuple=[1, 2, 3, 4]).simple_tuple)
#> (1, 2, 3, 4)
print(Model(tuple_of_different_types=[4, 3, 2, 1]).tuple_of_different_types)
#> (4, 3.0, '2', True)
print(Model(sequence_of_ints=[1, 2, 3, 4]).sequence_of_ints)
#> [1, 2, 3, 4]
print(Model(sequence_of_ints=(1, 2, 3, 4)).sequence_of_ints)
#> (1, 2, 3, 4)
print(Model(deque=[1, 2, 3]).deque)
#> deque([1, 2, 3])
from typing import (
Deque
)
from collections.abc import Sequence
from pydantic import BaseModel
class Model(BaseModel):
simple_list: list = None
list_of_ints: list[int] = None
simple_tuple: tuple = None
tuple_of_different_types: tuple[int, float, str, bool] = None
simple_dict: dict = None
dict_str_float: dict[str, float] = None
simple_set: set = None
set_bytes: set[bytes] = None
frozen_set: frozenset[int] = None
str_or_bytes: str | bytes = None
none_or_str: str | None = None
sequence_of_ints: Sequence[int] = None
compound: dict[str | bytes, list[set[int]]] = None
deque: Deque[int] = None
print(Model(simple_list=['1', '2', '3']).simple_list)
#> ['1', '2', '3']
print(Model(list_of_ints=['1', '2', '3']).list_of_ints)
#> [1, 2, 3]
print(Model(simple_dict={'a': 1, b'b': 2}).simple_dict)
#> {'a': 1, b'b': 2}
print(Model(dict_str_float={'a': 1, b'b': 2}).dict_str_float)
#> {'a': 1.0, 'b': 2.0}
print(Model(simple_tuple=[1, 2, 3, 4]).simple_tuple)
#> (1, 2, 3, 4)
print(Model(tuple_of_different_types=[4, 3, 2, 1]).tuple_of_different_types)
#> (4, 3.0, '2', True)
print(Model(sequence_of_ints=[1, 2, 3, 4]).sequence_of_ints)
#> [1, 2, 3, 4]
print(Model(sequence_of_ints=(1, 2, 3, 4)).sequence_of_ints)
#> (1, 2, 3, 4)
print(Model(deque=[1, 2, 3]).deque)
#> deque([1, 2, 3])
(这个脚本是完整的,它应该“按原样”运行)
无限生成器(Infinite Generators)¶
如果你有一个生成器,你可以使用上面描述的Sequence
。 在这种情况下,生成器将被使用并作为列表存储在模型中,其值将使用Sequence
的子类型(例如Sequence[int]
中的int
)进行验证。
但是如果你有一个你不想被消耗的生成器,例如 无限生成器或远程数据加载器,您可以使用 Iterable
定义其类型:
from typing import Iterable
from pydantic import BaseModel
class Model(BaseModel):
infinite: Iterable[int]
def infinite_ints():
i = 0
while True:
yield i
i += 1
m = Model(infinite=infinite_ints())
print(m)
#> infinite=<generator object infinite_ints at 0x7fad44aa1d80>
for i in m.infinite:
print(i)
#> 0
#> 1
#> 2
#> 3
#> 4
#> 5
#> 6
#> 7
#> 8
#> 9
#> 10
if i == 10:
break
from collections.abc import Iterable
from pydantic import BaseModel
class Model(BaseModel):
infinite: Iterable[int]
def infinite_ints():
i = 0
while True:
yield i
i += 1
m = Model(infinite=infinite_ints())
print(m)
#> infinite=<generator object infinite_ints at 0x7fad44aa1900>
for i in m.infinite:
print(i)
#> 0
#> 1
#> 2
#> 3
#> 4
#> 5
#> 6
#> 7
#> 8
#> 9
#> 10
if i == 10:
break
(这个脚本是完整的,它应该“按原样”运行)
Warning
Iterable
字段只执行一个简单的检查,以确保参数是可迭代的并且不会被消耗。
不会对它们的值进行验证,因为如果不使用可迭代对象就无法完成验证。
Tip
如果您想验证无限生成器的值,您可以创建一个单独的模型并在使用生成器时使用它,并根据需要报告验证错误。
pydantic 无法自动为您验证这些值,因为它需要使用无限生成器。
验证第一个值(Validating the first value)¶
您可以创建一个 validator 来验证无限生成器中的第一个值,但仍然不会完全消耗它。
import itertools
from typing import Iterable
from pydantic import BaseModel, validator, ValidationError
from pydantic.fields import ModelField
class Model(BaseModel):
infinite: Iterable[int]
@validator('infinite')
# You don't need to add the "ModelField", but it will help your
# editor give you completion and catch errors
def infinite_first_int(cls, iterable, field: ModelField):
first_value = next(iterable)
if field.sub_fields:
# The Iterable had a parameter type, in this case it's int
# We use it to validate the first value
sub_field = field.sub_fields[0]
v, error = sub_field.validate(first_value, {}, loc='first_value')
if error:
raise ValidationError([error], cls)
# This creates a new generator that returns the first value and then
# the rest of the values from the (already started) iterable
return itertools.chain([first_value], iterable)
def infinite_ints():
i = 0
while True:
yield i
i += 1
m = Model(infinite=infinite_ints())
print(m)
#> infinite=<itertools.chain object at 0x7fad449e28f0>
def infinite_strs():
while True:
yield from 'allthesingleladies'
try:
Model(infinite=infinite_strs())
except ValidationError as e:
print(e)
"""
1 validation error for Model
infinite -> first_value
value is not a valid integer (type=type_error.integer)
"""
import itertools
from collections.abc import Iterable
from pydantic import BaseModel, validator, ValidationError
from pydantic.fields import ModelField
class Model(BaseModel):
infinite: Iterable[int]
@validator('infinite')
# You don't need to add the "ModelField", but it will help your
# editor give you completion and catch errors
def infinite_first_int(cls, iterable, field: ModelField):
first_value = next(iterable)
if field.sub_fields:
# The Iterable had a parameter type, in this case it's int
# We use it to validate the first value
sub_field = field.sub_fields[0]
v, error = sub_field.validate(first_value, {}, loc='first_value')
if error:
raise ValidationError([error], cls)
# This creates a new generator that returns the first value and then
# the rest of the values from the (already started) iterable
return itertools.chain([first_value], iterable)
def infinite_ints():
i = 0
while True:
yield i
i += 1
m = Model(infinite=infinite_ints())
print(m)
#> infinite=<itertools.chain object at 0x7fad449e2ec0>
def infinite_strs():
while True:
yield from 'allthesingleladies'
try:
Model(infinite=infinite_strs())
except ValidationError as e:
print(e)
"""
1 validation error for Model
infinite -> first_value
value is not a valid integer (type=type_error.integer)
"""
(这个脚本是完整的,它应该“按原样”运行)
联合(Unions)¶
Union
类型允许模型属性接受不同的类型,例如:
from uuid import UUID
from typing import Union
from pydantic import BaseModel
class User(BaseModel):
id: Union[int, str, UUID]
name: str
user_01 = User(id=123, name='John Doe')
print(user_01)
#> id=123 name='John Doe'
print(user_01.id)
#> 123
user_02 = User(id='1234', name='John Doe')
print(user_02)
#> id=1234 name='John Doe'
print(user_02.id)
#> 1234
user_03_uuid = UUID('cf57432e-809e-4353-adbd-9d5c0d733868')
user_03 = User(id=user_03_uuid, name='John Doe')
print(user_03)
#> id=275603287559914445491632874575877060712 name='John Doe'
print(user_03.id)
#> 275603287559914445491632874575877060712
print(user_03_uuid.int)
#> 275603287559914445491632874575877060712
from uuid import UUID
from pydantic import BaseModel
class User(BaseModel):
id: int | str | UUID
name: str
user_01 = User(id=123, name='John Doe')
print(user_01)
#> id=123 name='John Doe'
print(user_01.id)
#> 123
user_02 = User(id='1234', name='John Doe')
print(user_02)
#> id=1234 name='John Doe'
print(user_02.id)
#> 1234
user_03_uuid = UUID('cf57432e-809e-4353-adbd-9d5c0d733868')
user_03 = User(id=user_03_uuid, name='John Doe')
print(user_03)
#> id=275603287559914445491632874575877060712 name='John Doe'
print(user_03.id)
#> 275603287559914445491632874575877060712
print(user_03_uuid.int)
#> 275603287559914445491632874575877060712
(这个脚本是完整的,它应该“按原样”运行)
但是,如上所示,pydantic 将尝试match
在 Union
下定义的任何类型,并将使用第一个匹配的类型。 在上面的示例中,user_03
的id
被定义为uuid.UUID
类(在属性的 Union
注释下定义),但uuid.UUID
可以编组为int
它选择匹配int
类型并忽略其他类型。
Warning
typing.Union
在 定义 时也会忽略顺序,所以 Union[int, float] == Union[float, int]
当与基于其他类型定义中的 Union
类型顺序的匹配相结合时,例如 List
和 Dict
类型(因为 Python 将这些定义视为单例)。
例如,Dict[str, Union[int, float]] == Dict[str, Union[float, int]]
的顺序基于第一次定义。
请注意,这也可能 受第三方库影响 及其内部类型定义和导入顺序。
因此,建议在定义 Union
注解时,首先包含最具体的类型,然后是不太具体的类型。
在上面的示例中,UUID
类应该在 int
和 str
类之前,以排除这样的意外表示:
from uuid import UUID
from typing import Union
from pydantic import BaseModel
class User(BaseModel):
id: Union[UUID, int, str]
name: str
user_03_uuid = UUID('cf57432e-809e-4353-adbd-9d5c0d733868')
user_03 = User(id=user_03_uuid, name='John Doe')
print(user_03)
#> id=UUID('cf57432e-809e-4353-adbd-9d5c0d733868') name='John Doe'
print(user_03.id)
#> cf57432e-809e-4353-adbd-9d5c0d733868
print(user_03_uuid.int)
#> 275603287559914445491632874575877060712
from uuid import UUID
from pydantic import BaseModel
class User(BaseModel):
id: UUID | int | str
name: str
user_03_uuid = UUID('cf57432e-809e-4353-adbd-9d5c0d733868')
user_03 = User(id=user_03_uuid, name='John Doe')
print(user_03)
#> id=UUID('cf57432e-809e-4353-adbd-9d5c0d733868') name='John Doe'
print(user_03.id)
#> cf57432e-809e-4353-adbd-9d5c0d733868
print(user_03_uuid.int)
#> 275603287559914445491632874575877060712
(这个脚本是完整的,它应该“按原样”运行)
区别联合【Discriminated Unions (a.k.a. Tagged Unions)】¶
当 Union
与多个子模型一起使用时,您有时会确切地知道需要检查和验证哪个子模型并希望强制执行此操作。
为此,您可以在每个具有判别值的子模型中设置相同的字段 - 让我们称之为 my_discriminator
,这是一个(或多个)Literal
值。
对于您的 Union
,您可以在其值中设置鉴别器:Field(discriminator='my_discriminator')
。
建立受歧视的工会有很多好处:
- 验证速度更快,因为它只针对一个模型进行尝试
- 失败时仅引发一个显式错误
- 生成的 JSON 模式实现了相关的 OpenAPI 规范
from typing import Literal, Union
from pydantic import BaseModel, Field, ValidationError
class Cat(BaseModel):
pet_type: Literal['cat']
meows: int
class Dog(BaseModel):
pet_type: Literal['dog']
barks: float
class Lizard(BaseModel):
pet_type: Literal['reptile', 'lizard']
scales: bool
class Model(BaseModel):
pet: Union[Cat, Dog, Lizard] = Field(..., discriminator='pet_type')
n: int
print(Model(pet={'pet_type': 'dog', 'barks': 3.14}, n=1))
#> pet=Dog(pet_type='dog', barks=3.14) n=1
try:
Model(pet={'pet_type': 'dog'}, n=1)
except ValidationError as e:
print(e)
"""
1 validation error for Model
pet -> Dog -> barks
field required (type=value_error.missing)
"""
from typing import Literal
from pydantic import BaseModel, Field, ValidationError
class Cat(BaseModel):
pet_type: Literal['cat']
meows: int
class Dog(BaseModel):
pet_type: Literal['dog']
barks: float
class Lizard(BaseModel):
pet_type: Literal['reptile', 'lizard']
scales: bool
class Model(BaseModel):
pet: Cat | Dog | Lizard = Field(..., discriminator='pet_type')
n: int
print(Model(pet={'pet_type': 'dog', 'barks': 3.14}, n=1))
#> pet=Dog(pet_type='dog', barks=3.14) n=1
try:
Model(pet={'pet_type': 'dog'}, n=1)
except ValidationError as e:
print(e)
"""
1 validation error for Model
pet -> Dog -> barks
field required (type=value_error.missing)
"""
(这个脚本是完整的,它应该“按原样”运行)
Note
使用 Annotated Fields syntax 可以方便地重新组合 Union
和 discriminator
信息。 请参阅下面的示例!
Warning
区别联合不能仅与单个变体一起使用,例如 Union[Cat]
。
Python 在解释时将 Union[T]
更改为 T
,因此 pydantic
无法区分 Union[T]
和 T
的字段。
嵌套的区别联合(Nested Discriminated Unions)¶
一个字段只能设置一个鉴别器(discriminator),但有时你想组合多个鉴别器(discriminator)。
在这种情况下,您始终可以使用 __root__
创建“中间(intermediate)”模型并添加鉴别器。
from typing import Literal, Union
from typing_extensions import Annotated
from pydantic import BaseModel, Field, ValidationError
class BlackCat(BaseModel):
pet_type: Literal['cat']
color: Literal['black']
black_name: str
class WhiteCat(BaseModel):
pet_type: Literal['cat']
color: Literal['white']
white_name: str
# Can also be written with a custom root type
#
# class Cat(BaseModel):
# __root__: Annotated[Union[BlackCat, WhiteCat], Field(discriminator='color')]
Cat = Annotated[Union[BlackCat, WhiteCat], Field(discriminator='color')]
class Dog(BaseModel):
pet_type: Literal['dog']
name: str
Pet = Annotated[Union[Cat, Dog], Field(discriminator='pet_type')]
class Model(BaseModel):
pet: Pet
n: int
m = Model(pet={'pet_type': 'cat', 'color': 'black', 'black_name': 'felix'}, n=1)
print(m)
#> pet=BlackCat(pet_type='cat', color='black', black_name='felix') n=1
try:
Model(pet={'pet_type': 'cat', 'color': 'red'}, n='1')
except ValidationError as e:
print(e)
"""
1 validation error for Model
pet -> Union[BlackCat, WhiteCat]
No match for discriminator 'color' and value 'red' (allowed values:
'black', 'white')
(type=value_error.discriminated_union.invalid_discriminator;
discriminator_key=color; discriminator_value=red; allowed_values='black',
'white')
"""
try:
Model(pet={'pet_type': 'cat', 'color': 'black'}, n='1')
except ValidationError as e:
print(e)
"""
1 validation error for Model
pet -> Union[BlackCat, WhiteCat] -> BlackCat -> black_name
field required (type=value_error.missing)
"""
from typing import Literal, Union
from typing import Annotated
from pydantic import BaseModel, Field, ValidationError
class BlackCat(BaseModel):
pet_type: Literal['cat']
color: Literal['black']
black_name: str
class WhiteCat(BaseModel):
pet_type: Literal['cat']
color: Literal['white']
white_name: str
# Can also be written with a custom root type
#
# class Cat(BaseModel):
# __root__: Annotated[Union[BlackCat, WhiteCat], Field(discriminator='color')]
Cat = Annotated[Union[BlackCat, WhiteCat], Field(discriminator='color')]
class Dog(BaseModel):
pet_type: Literal['dog']
name: str
Pet = Annotated[Union[Cat, Dog], Field(discriminator='pet_type')]
class Model(BaseModel):
pet: Pet
n: int
m = Model(pet={'pet_type': 'cat', 'color': 'black', 'black_name': 'felix'}, n=1)
print(m)
#> pet=BlackCat(pet_type='cat', color='black', black_name='felix') n=1
try:
Model(pet={'pet_type': 'cat', 'color': 'red'}, n='1')
except ValidationError as e:
print(e)
"""
1 validation error for Model
pet -> Union[BlackCat, WhiteCat]
No match for discriminator 'color' and value 'red' (allowed values:
'black', 'white')
(type=value_error.discriminated_union.invalid_discriminator;
discriminator_key=color; discriminator_value=red; allowed_values='black',
'white')
"""
try:
Model(pet={'pet_type': 'cat', 'color': 'black'}, n='1')
except ValidationError as e:
print(e)
"""
1 validation error for Model
pet -> Union[BlackCat, WhiteCat] -> BlackCat -> black_name
field required (type=value_error.missing)
"""
(这个脚本是完整的,它应该“按原样”运行)
枚举和选择(Enums and Choices)¶
pydantic 使用 Python 的标准enum
类来定义选择。
from enum import Enum, IntEnum
from pydantic import BaseModel, ValidationError
class FruitEnum(str, Enum):
pear = 'pear'
banana = 'banana'
class ToolEnum(IntEnum):
spanner = 1
wrench = 2
class CookingModel(BaseModel):
fruit: FruitEnum = FruitEnum.pear
tool: ToolEnum = ToolEnum.spanner
print(CookingModel())
#> fruit=<FruitEnum.pear: 'pear'> tool=<ToolEnum.spanner: 1>
print(CookingModel(tool=2, fruit='banana'))
#> fruit=<FruitEnum.banana: 'banana'> tool=<ToolEnum.wrench: 2>
try:
CookingModel(fruit='other')
except ValidationError as e:
print(e)
"""
1 validation error for CookingModel
fruit
value is not a valid enumeration member; permitted: 'pear', 'banana'
(type=type_error.enum; enum_values=[<FruitEnum.pear: 'pear'>,
<FruitEnum.banana: 'banana'>])
"""
(这个脚本是完整的,它应该“按原样”运行)
日期时间类型(Datetime Types)¶
Pydantic 支持以下 datetime 类型:
-
datetime
字段可以是:datetime
,现有的datetime
对象int
或float
,假定为 Unix 时间,即自 1970 年 1 月 1 日以来的秒数(如果 >=-2e10
或 <=2e10
)或毫秒(如果 <-2e10
或 >2e10
)-
str
, 以下格式有效:YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z or [±]HH[:]MM]
int
或float
作为字符串(假定为 Unix 时间)
-
date
字段可以是:date
, 现有的date
对象int
或float
, 见datetime
-
str
, 以下格式有效:YYYY-MM-DD
int
或float
, 见datetime
-
time
字段可以是:time
, 现有的time
对象-
str
, 以下格式有效:HH:MM[:SS[.ffffff]][Z or [±]HH[:]MM]
-
timedelta
字段可以是:timedelta
, 现有的timedelta
对象int
或float
, 假定为秒-
str
, 以下格式有效:[-][DD ][HH:MM]SS[.ffffff]
[±]P[DD]DT[HH]H[MM]M[SS]S
(ISO 8601 timedelta 的格式)
from datetime import date, datetime, time, timedelta
from pydantic import BaseModel
class Model(BaseModel):
d: date = None
dt: datetime = None
t: time = None
td: timedelta = None
m = Model(
d=1966280412345.6789,
dt='2032-04-23T10:20:30.400+02:30',
t=time(4, 8, 16),
td='P3DT12H30M5S',
)
print(m.dict())
"""
{
'd': datetime.date(2032, 4, 22),
'dt': datetime.datetime(2032, 4, 23, 10, 20, 30, 400000,
tzinfo=datetime.timezone(datetime.timedelta(seconds=9000))),
't': datetime.time(4, 8, 16),
'td': datetime.timedelta(days=3, seconds=45005),
}
"""
(这个脚本是完整的,它应该“按原样”运行)
布尔类型(Booleans)¶
Warning
从 v1.0 版本开始,解析 bool
字段的逻辑发生了变化。
在 v1.0 之前,bool
解析从未失败,导致一些意外结果。
新逻辑如下所述。
如果值不是以下之一,标准的 bool
字段将引发 ValidationError
:
- 一个有效的布尔值(即
True
或False
), - 整数
0
或1
, - 一个
str
,当转换为小写时,它是其中之一'0', 'off', 'f', 'false', 'n', 'no', '1', 'on', 't', 'true', 'y', 'yes'
- 解码为
str
时有效(根据前面的规则)的bytes
Note
如果你想要更严格的布尔逻辑(例如,一个只允许 True
和 False
的字段)你可以使用 StrictBool
。
这是一个演示其中一些行为的脚本:
from pydantic import BaseModel, ValidationError
class BooleanModel(BaseModel):
bool_value: bool
print(BooleanModel(bool_value=False))
#> bool_value=False
print(BooleanModel(bool_value='False'))
#> bool_value=False
try:
BooleanModel(bool_value=[])
except ValidationError as e:
print(str(e))
"""
1 validation error for BooleanModel
bool_value
value could not be parsed to a boolean (type=type_error.bool)
"""
(这个脚本是完整的,它应该“按原样”运行)
可调用类型(Callable)¶
字段也可以是 Callable
类型:
from typing import Callable
from pydantic import BaseModel
class Foo(BaseModel):
callback: Callable[[int], int]
m = Foo(callback=lambda x: x)
print(m)
#> callback=<function <lambda> at 0x7fad4496a5c0>
from collections.abc import Callable
from pydantic import BaseModel
class Foo(BaseModel):
callback: Callable[[int], int]
m = Foo(callback=lambda x: x)
print(m)
#> callback=<function <lambda> at 0x7fad4496a840>
(这个脚本是完整的,它应该“按原样”运行)
Warning
可调用字段仅执行简单检查参数是否可调用; 不执行参数、它们的类型或返回类型的验证。
类型(Type)¶
pydantic 支持使用 Type[T]
来指定字段只能接受作为 T
子类的类(而不是实例)。
from typing import Type
from pydantic import BaseModel
from pydantic import ValidationError
class Foo:
pass
class Bar(Foo):
pass
class Other:
pass
class SimpleModel(BaseModel):
just_subclasses: Type[Foo]
SimpleModel(just_subclasses=Foo)
SimpleModel(just_subclasses=Bar)
try:
SimpleModel(just_subclasses=Other)
except ValidationError as e:
print(e)
"""
1 validation error for SimpleModel
just_subclasses
subclass of Foo expected (type=type_error.subclass; expected_class=Foo)
"""
from pydantic import BaseModel
from pydantic import ValidationError
class Foo:
pass
class Bar(Foo):
pass
class Other:
pass
class SimpleModel(BaseModel):
just_subclasses: type[Foo]
SimpleModel(just_subclasses=Foo)
SimpleModel(just_subclasses=Bar)
try:
SimpleModel(just_subclasses=Other)
except ValidationError as e:
print(e)
"""
1 validation error for SimpleModel
just_subclasses
subclass of Foo expected (type=type_error.subclass; expected_class=Foo)
"""
(这个脚本是完整的,它应该“按原样”运行)
您还可以使用 Type
来指定允许使用任何类。
from typing import Type
from pydantic import BaseModel, ValidationError
class Foo:
pass
class LenientSimpleModel(BaseModel):
any_class_goes: Type
LenientSimpleModel(any_class_goes=int)
LenientSimpleModel(any_class_goes=Foo)
try:
LenientSimpleModel(any_class_goes=Foo())
except ValidationError as e:
print(e)
"""
1 validation error for LenientSimpleModel
any_class_goes
a class is expected (type=type_error.class)
"""
(这个脚本是完整的,它应该“按原样”运行)
类型声明(TypeVar)¶
TypeVar
支持不受约束、受约束或有界限。
from typing import TypeVar
from pydantic import BaseModel
Foobar = TypeVar('Foobar')
BoundFloat = TypeVar('BoundFloat', bound=float)
IntStr = TypeVar('IntStr', int, str)
class Model(BaseModel):
a: Foobar # equivalent of ": Any"
b: BoundFloat # equivalent of ": float"
c: IntStr # equivalent of ": Union[int, str]"
print(Model(a=[1], b=4.2, c='x'))
#> a=[1] b=4.2 c='x'
# a may be None and is therefore optional
print(Model(b=1, c=1))
#> a=None b=1.0 c=1
(这个脚本是完整的,它应该“按原样”运行)
文字类型(Literal Type)¶
Note
这是从 Python 3.8 开始的 Python 标准库的一个新特性; 在 Python 3.8 之前,它需要 typing-extensions 包。
pydantic 支持使用 typing.Literal
(或 Python 3.8 之前的 typing_extensions.Literal
)作为一种轻量级的方式来指定一个字段只能接受特定的文字值:
from typing import Literal
from pydantic import BaseModel, ValidationError
class Pie(BaseModel):
flavor: Literal['apple', 'pumpkin']
Pie(flavor='apple')
Pie(flavor='pumpkin')
try:
Pie(flavor='cherry')
except ValidationError as e:
print(str(e))
"""
1 validation error for Pie
flavor
unexpected value; permitted: 'apple', 'pumpkin'
(type=value_error.const; given=cherry; permitted=('apple', 'pumpkin'))
"""
(这个脚本是完整的,它应该“按原样”运行)
这种字段类型的一个好处是它可以用来检查一个或多个特定值是否相等,而无需声明自定义验证器:
from typing import ClassVar, List, Union
from typing import Literal
from pydantic import BaseModel, ValidationError
class Cake(BaseModel):
kind: Literal['cake']
required_utensils: ClassVar[List[str]] = ['fork', 'knife']
class IceCream(BaseModel):
kind: Literal['icecream']
required_utensils: ClassVar[List[str]] = ['spoon']
class Meal(BaseModel):
dessert: Union[Cake, IceCream]
print(type(Meal(dessert={'kind': 'cake'}).dessert).__name__)
#> Cake
print(type(Meal(dessert={'kind': 'icecream'}).dessert).__name__)
#> IceCream
try:
Meal(dessert={'kind': 'pie'})
except ValidationError as e:
print(str(e))
"""
2 validation errors for Meal
dessert -> kind
unexpected value; permitted: 'cake' (type=value_error.const; given=pie;
permitted=('cake',))
dessert -> kind
unexpected value; permitted: 'icecream' (type=value_error.const;
given=pie; permitted=('icecream',))
"""
from typing import ClassVar, Union
from typing import Literal
from pydantic import BaseModel, ValidationError
class Cake(BaseModel):
kind: Literal['cake']
required_utensils: ClassVar[list[str]] = ['fork', 'knife']
class IceCream(BaseModel):
kind: Literal['icecream']
required_utensils: ClassVar[list[str]] = ['spoon']
class Meal(BaseModel):
dessert: Union[Cake, IceCream]
print(type(Meal(dessert={'kind': 'cake'}).dessert).__name__)
#> Cake
print(type(Meal(dessert={'kind': 'icecream'}).dessert).__name__)
#> IceCream
try:
Meal(dessert={'kind': 'pie'})
except ValidationError as e:
print(str(e))
"""
2 validation errors for Meal
dessert -> kind
unexpected value; permitted: 'cake' (type=value_error.const; given=pie;
permitted=('cake',))
dessert -> kind
unexpected value; permitted: 'icecream' (type=value_error.const;
given=pie; permitted=('icecream',))
"""
from typing import ClassVar
from typing import Literal
from pydantic import BaseModel, ValidationError
class Cake(BaseModel):
kind: Literal['cake']
required_utensils: ClassVar[list[str]] = ['fork', 'knife']
class IceCream(BaseModel):
kind: Literal['icecream']
required_utensils: ClassVar[list[str]] = ['spoon']
class Meal(BaseModel):
dessert: Cake | IceCream
print(type(Meal(dessert={'kind': 'cake'}).dessert).__name__)
#> Cake
print(type(Meal(dessert={'kind': 'icecream'}).dessert).__name__)
#> IceCream
try:
Meal(dessert={'kind': 'pie'})
except ValidationError as e:
print(str(e))
"""
2 validation errors for Meal
dessert -> kind
unexpected value; permitted: 'cake' (type=value_error.const; given=pie;
permitted=('cake',))
dessert -> kind
unexpected value; permitted: 'icecream' (type=value_error.const;
given=pie; permitted=('icecream',))
"""
(这个脚本是完整的,它应该“按原样”运行)
通过在带注释的 Union
中正确排序,您可以使用它来解析递减特异性的类型:
from typing import Optional, Union
from typing import Literal
from pydantic import BaseModel
class Dessert(BaseModel):
kind: str
class Pie(Dessert):
kind: Literal['pie']
flavor: Optional[str]
class ApplePie(Pie):
flavor: Literal['apple']
class PumpkinPie(Pie):
flavor: Literal['pumpkin']
class Meal(BaseModel):
dessert: Union[ApplePie, PumpkinPie, Pie, Dessert]
print(type(Meal(dessert={'kind': 'pie', 'flavor': 'apple'}).dessert).__name__)
#> ApplePie
print(type(Meal(dessert={'kind': 'pie', 'flavor': 'pumpkin'}).dessert).__name__)
#> PumpkinPie
print(type(Meal(dessert={'kind': 'pie'}).dessert).__name__)
#> Pie
print(type(Meal(dessert={'kind': 'cake'}).dessert).__name__)
#> Dessert
from typing import Literal
from pydantic import BaseModel
class Dessert(BaseModel):
kind: str
class Pie(Dessert):
kind: Literal['pie']
flavor: str | None
class ApplePie(Pie):
flavor: Literal['apple']
class PumpkinPie(Pie):
flavor: Literal['pumpkin']
class Meal(BaseModel):
dessert: ApplePie | PumpkinPie | Pie | Dessert
print(type(Meal(dessert={'kind': 'pie', 'flavor': 'apple'}).dessert).__name__)
#> ApplePie
print(type(Meal(dessert={'kind': 'pie', 'flavor': 'pumpkin'}).dessert).__name__)
#> PumpkinPie
print(type(Meal(dessert={'kind': 'pie'}).dessert).__name__)
#> Pie
print(type(Meal(dessert={'kind': 'cake'}).dessert).__name__)
#> Dessert
(这个脚本是完整的,它应该“按原样”运行)
已注解类型(Annotated Types)¶
命名元组(NamedTuple)¶
from typing import NamedTuple
from pydantic import BaseModel, ValidationError
class Point(NamedTuple):
x: int
y: int
class Model(BaseModel):
p: Point
print(Model(p=('1', '2')))
#> p=Point(x=1, y=2)
try:
Model(p=('1.3', '2'))
except ValidationError as e:
print(e)
"""
1 validation error for Model
p -> x
value is not a valid integer (type=type_error.integer)
"""
(这个脚本是完整的,它应该“按原样”运行)
标记类型字典(TypedDict)¶
Note
这是从 Python 3.8 开始的 Python 标准库的一个新特性。 在 Python 3.8 之前,它需要 typing-extensions 包。
但仅自 Python 3.9 起才正确区分必填字段和可选字段。
因此,我们建议在 Python 3.8 中也使用 typing-extensions。
from typing_extensions import TypedDict
from pydantic import BaseModel, Extra, ValidationError
# `total=False` means keys are non-required
class UserIdentity(TypedDict, total=False):
name: str
surname: str
class User(TypedDict):
identity: UserIdentity
age: int
class Model(BaseModel):
u: User
class Config:
extra = Extra.forbid
print(Model(u={'identity': {'name': 'Smith', 'surname': 'John'}, 'age': '37'}))
#> u={'identity': {'name': 'Smith', 'surname': 'John'}, 'age': 37}
print(Model(u={'identity': {'name': None, 'surname': 'John'}, 'age': '37'}))
#> u={'identity': {'name': None, 'surname': 'John'}, 'age': 37}
print(Model(u={'identity': {}, 'age': '37'}))
#> u={'identity': {}, 'age': 37}
try:
Model(u={'identity': {'name': ['Smith'], 'surname': 'John'}, 'age': '24'})
except ValidationError as e:
print(e)
"""
1 validation error for Model
u -> identity -> name
str type expected (type=type_error.str)
"""
try:
Model(
u={
'identity': {'name': 'Smith', 'surname': 'John'},
'age': '37',
'email': 'john.smith@me.com',
}
)
except ValidationError as e:
print(e)
"""
1 validation error for Model
u -> email
extra fields not permitted (type=value_error.extra)
"""
(这个脚本是完整的,它应该“按原样”运行)
Pydantic特有类型(Pydantic Types)¶
pydantic 还提供了多种其他有用的类型:
FilePath
- 类似
Path
, 但路径必须存在并且是一个文件 DirectoryPath
- 类似
Path
, 但路径必须存在并且是一个目录 PastDate
- 类似
date
, 但日期应该是过去的 FutureDate
- 类似
date
, 但日期应该在未来 EmailStr
- 需要安装 email-validator; 输入字符串必须是一个有效的电子邮件地址,输出是一个简单的字符串
NameEmail
-
需要安装 email-validator;
输入字符串必须是有效的电子邮件地址或格式为
Fred Bloggs <fred.bloggs@example.com>
,输出是一个NameEmail
对象,它具有两个属性:name
和email
。对于
Fred Bloggs <fred.bloggs@example.com>
,名称将是Fred Bloggs
;对于
fred.bloggs@example.com
,它将是fred.bloggs
。 PyObject
- 需要一个字符串并加载在该下划线路径中可导入的 Python 对象;
例如 如果提供了
math.cos
,则结果字段值将是函数cos
Color
- 用于解析 HTML 和 CSS 颜色; 参见颜色类型
Json
- 在解析之前加载 JSON 的特殊类型包装器; 参见 JSON 类型
PaymentCardNumber
- 用于解析和验证支付卡; 参见支付卡
AnyUrl
- 任何网址; 参见 URL
AnyHttpUrl
- 一个 HTTP 网址; 参见 URL
HttpUrl
- 更严格的 HTTP URL; 参见 URL
FileUrl
- 文件路径 URL; 参见 URL
PostgresDsn
- 文件路径 URL; 参见 URL
CockroachDsn
- cockroachdb DSN 样式的 URL; 参见 URL
AmqpDsn
- RabbitMQ、StormMQ、ActiveMQ 等使用的
AMQP
DSN 样式 URL; 参见 URL RedisDsn
- 一个 redis DSN 样式的 URL; 参见 URL
MongoDsn
- 一个 MongoDB DSN 样式的 URL; 参见 URL
KafkaDsn
- kafka DSN 样式的 URL; 参见 URL
stricturl
- 任意 URL 约束的类型方法; 参见 URL
UUID1
- 需要类型 1 的有效 UUID; 参见
UUID
above UUID3
- 需要类型 3 的有效 UUID; 参见
UUID
above UUID4
- 需要类型 4 的有效 UUID; 参见
UUID
above UUID5
- 需要类型 5 的有效 UUID; 参见
UUID
above SecretBytes
- 值部分保密的字节; 参见 Secrets
SecretStr
- 值部分保密的字符串; 参见 Secrets
IPvAnyAddress
- 允许
IPv4Address
或IPv6Address
IPvAnyInterface
- 允许
IPv4Interface
或IPv6Interface
IPvAnyNetwork
- 允许
IPv4Network
或IPv6Network
NegativeFloat
- 允许一个负数的浮点数; 使用标准的
float
解析然后检查值是否小于 0; 参见 约束类型 NegativeInt
- 允许一个负数的整数; 使用标准的
int
解析然后检查值是否小于 0; 参见 约束类型 PositiveFloat
- 允许一个正的浮点数; 使用标准的
float
解析然后检查值是否大于 0; 参见 约束类型 PositiveInt
- 允许一个正整数; 使用标准的
int
解析然后检查值是否大于 0; 参见 约束类型 conbytes
- 用于约束字节的类型方法; 参见 约束类型
condecimal
- 用于约束 Decimals 的类型方法; 参见 约束类型
confloat
- 用于约束浮点数的类型方法; 参见 约束类型
conint
- 用于约束整数的类型方法; 参见 约束类型
condate
- 限制日期的类型方法; 参见 约束类型
conlist
- 用于约束列表的类型方法; 参见 约束类型
conset
- 约束集的类型方法; 参见 约束类型
confrozenset
- 用于约束冻结集的类型方法; 参见 约束类型
constr
- 用于约束 字符串 的类型方法; 参见 约束类型
链接类型(URLs)¶
对于 URI/URL 验证,可以使用以下类型:
AnyUrl
: 允许任何方案,不需要 TLD,需要主机地址AnyHttpUrl
: 方案http
或https
,不需要 TLD,需要主机地址HttpUrl
: 方案http
或https
,需要 TLD,需要主机地址,最大长度 2083FileUrl
: 匹配file
, 不需要主机地址PostgresDsn
: 需要用户信息,不需要 TLD,需要主机地址,从 V.10 开始,PostgresDsn
支持多个主机。 以下方案是被支持的:postgres
postgresql
postgresql+asyncpg
postgresql+pg8000
postgresql+psycopg
postgresql+psycopg2
postgresql+psycopg2cffi
postgresql+py-postgresql
postgresql+pygresql
CockroachDsn
: 方案cockroachdb
,需要用户信息,不需要 TLD,需要主机地址。 此外,它支持的 DBAPI 方言:cockroachdb+asyncpg
cockroachdb+psycopg2
AmqpDsn
: 模式amqp
或amqps
,不需要用户信息,不需要 TLD,不需要主机地址RedisDsn
: 匹配redis
或rediss
,不需要用户信息,不需要 tld,不需要主机地址(已更改:用户信息)(例如,rediss://:pass@localhost
)MongoDsn
: 匹配mongodb
, 不需要用户信息,不需要数据库名称,从 v1.6 开始不需要端口),用户信息可以在没有用户部分的情况下传递(例如,mongodb://mongodb0.example.com:27017
)stricturl
: 具有以下关键字参数的方法:strip_whitespace: bool = True
min_length: int = 1
max_length: int = 2 ** 16
tld_required: bool = True
host_required: bool = True
allowed_schemes: Optional[Set[str]] = None
Warning
在 V1.10.0 和 v1.10.1 中,stricturl
还采用可选的 quote_plus
参数,并且 URL 组件在某些情况下采用百分比编码。 此功能已在 v1.10.2 中删除,请参阅 #4470 了解说明和更多详细信息。
当提供无效 URL 时,上述类型(全部继承自 AnyUrl
)将尝试给出描述性错误:
from pydantic import BaseModel, HttpUrl, ValidationError
class MyModel(BaseModel):
url: HttpUrl
m = MyModel(url='http://www.example.com')
print(m.url)
#> http://www.example.com
try:
MyModel(url='ftp://invalid.url')
except ValidationError as e:
print(e)
"""
1 validation error for MyModel
url
URL scheme not permitted (type=value_error.url.scheme;
allowed_schemes={'https', 'http'})
"""
try:
MyModel(url='not a url')
except ValidationError as e:
print(e)
"""
1 validation error for MyModel
url
invalid or missing URL scheme (type=value_error.url.scheme)
"""
(这个脚本是完整的,它应该“按原样”运行)
如果您需要自定义 URI/URL 类型,可以使用与上面定义的类型类似的方式创建它。
网址属性(URL Properties)¶
假设输入 URL 为http://samuel:pass@example.com:8000/the/path/?query=here#fragment=is;this=bit
,上述类型导出以下属性:
scheme
: 始终设置 - url 方案(上面的http
)host
: 始终设置 - 网址主机(上面的“example.com”)-
host_type
: 始终设置 - 描述主机类型,或者:domain
: 例如example.com
,int_domain
: 国际域名,见下文,例如exampl£e.org
,ipv4
: IP V4 地址,例如127.0.0.1
,或ipv6
: IP V6 地址,例如2001:db8:ff00:42
user
: 可选 - 用户名(如果包含)(上面的samuel
)password
: 可选 - 如果包含密码(上面的pass
)tld
: 可选 - 顶级域(上面的“com”), 注意:这对于任何二级域都是错误的,例如 “co.uk”. 如果您需要完整的 TLD 验证,您需要实施自己的 TLD 列表port
: 可选 - 端口(上面的“8000”)path
: 可选 - 路径(上面的/the/path/
)query
: 可选 - URL 查询(又名 GET 参数或“搜索字符串”)(上面的query=here
)fragment
: 可选 - 片段(上面的fragment=is;this=bit
)
如果需要进一步验证,验证器可以使用这些属性来强制执行特定行为:
from pydantic import BaseModel, HttpUrl, PostgresDsn, ValidationError, validator
class MyModel(BaseModel):
url: HttpUrl
m = MyModel(url='http://www.example.com')
# the repr() method for a url will display all properties of the url
print(repr(m.url))
#> HttpUrl('http://www.example.com', )
print(m.url.scheme)
#> http
print(m.url.host)
#> www.example.com
print(m.url.host_type)
#> domain
print(m.url.port)
#> 80
class MyDatabaseModel(BaseModel):
db: PostgresDsn
@validator('db')
def check_db_name(cls, v):
assert v.path and len(v.path) > 1, 'database must be provided'
return v
m = MyDatabaseModel(db='postgres://user:pass@localhost:5432/foobar')
print(m.db)
#> postgres://user:pass@localhost:5432/foobar
try:
MyDatabaseModel(db='postgres://user:pass@localhost:5432')
except ValidationError as e:
print(e)
"""
1 validation error for MyDatabaseModel
db
database must be provided (type=assertion_error)
"""
(这个脚本是完整的,它应该“按原样”运行)
国际域名(International Domains)¶
“国际域”(例如,主机或 TLD 包含非 ascii 字符的 URL)将通过 punycode 进行编码(参见 本文 很好地说明了为什么这很重要):
from pydantic import BaseModel, HttpUrl
class MyModel(BaseModel):
url: HttpUrl
m1 = MyModel(url='http://puny£code.com')
print(m1.url)
#> http://xn--punycode-eja.com
print(m1.url.host_type)
#> int_domain
m2 = MyModel(url='https://www.аррӏе.com/')
print(m2.url)
#> https://www.xn--80ak6aa92e.com/
print(m2.url.host_type)
#> int_domain
m3 = MyModel(url='https://www.example.珠宝/')
print(m3.url)
#> https://www.example.xn--pbt977c/
print(m3.url.host_type)
#> int_domain
(这个脚本是完整的,它应该“按原样”运行)
Warning
主机名中的下划线¶
在 pydantic 中,域的所有部分都允许使用下划线,除了 tld。从技术上讲,这可能是错误的——理论上主机名不能有下划线,但子域可以。
解释这一点; 考虑以下两种情况:
exam_ple.co.uk
: 主机名是exam_ple
,这是不允许的,因为它包含下划线foo_bar.example.com
主机名是example
,应该允许,因为下划线在子域中
如果没有详尽的 TLD 列表,就不可能区分这两者。 因此允许使用下划线,但如果需要,您始终可以在验证器中进行进一步验证。
此外,Chrome、Firefox 和 Safari 目前都接受 http://exam_ple.com
作为 URL,所以我们的关系很好(或者至少是大)。
颜色类型(Color Type)¶
您可以根据 CSS3 规范 使用 Color
数据类型来存储颜色。 颜色可以通过以下方式定义:
- name (例如
"Black"
,"azure"
) - hexadecimal value
(例如
"0x000"
,"#FFFFFF"
,"7fffd4"
) - RGB/RGBA 元组 (例如
(255, 255, 255)
,(255, 255, 255, 0.5)
) - RGB/RGBA 字符串
(例如
"rgb(255, 255, 255)"
,"rgba(255, 255, 255, 0.5)"
) - HSL 字符串
(例如
"hsl(270, 60%, 70%)"
,"hsl(270, 60%, 70%, .5)"
)
from pydantic import BaseModel, ValidationError
from pydantic.color import Color
c = Color('ff00ff')
print(c.as_named())
#> magenta
print(c.as_hex())
#> #f0f
c2 = Color('green')
print(c2.as_rgb_tuple())
#> (0, 128, 0)
print(c2.original())
#> green
print(repr(Color('hsl(180, 100%, 50%)')))
#> Color('cyan', rgb=(0, 255, 255))
class Model(BaseModel):
color: Color
print(Model(color='purple'))
#> color=Color('purple', rgb=(128, 0, 128))
try:
Model(color='hello')
except ValidationError as e:
print(e)
"""
1 validation error for Model
color
value is not a valid color: string not recognised as a valid color
(type=value_error.color; reason=string not recognised as a valid color)
"""
(这个脚本是完整的,它应该“按原样”运行)
Color
拥有下列方法:
original
- 传递给
Color
的原始字符串或元组 as_named
- 返回命名的 CSS3 颜色; 如果设置了 alpha 通道或不存在这样的颜色,则失败,除非提供了
fallback=True
,在这种情况下它会回退到as_hex
as_hex
- 返回格式为
#fff
或#ffffff
的字符串; 如果设置了 alpha 通道,将包含 4(或 8)个十六进制值, 例如#7f33cc26
as_rgb
- 如果设置了 alpha 通道,则返回格式为
rgb(<red>, <green>, <blue>)
或rgba(<red>, <green>, <blue>, <alpha>)
的字符串 as_rgb_tuple
- 返回 RGB(a) 格式的 3 元组或 4 元组。
alpha
关键字参数可用于定义是否应包含 alpha 通道; 选项:True
- 始终包括,False
- 从不包括,None
(默认)- 如果设置则包括 as_hsl
- 格式为
hsl(<hue deg>, <saturation %>, <lightness %>)
或hsl(<hue deg>, <saturation %>, <lightness %>, <alpha>)
的字符串,如果 alpha 通道已设置 as_hsl_tuple
- 返回 HSL(a) 格式的 3 元组或 4 元组。
alpha
关键字参数可用于定义是否应包含 alpha 通道; 选项:True
- 始终包括,False
- 从不包括,None
(默认值)- 如果设置则包括
Color
的__str__
方法返回self.as_named(fallback=True)
。
Note
as_hsl*
指的是 html 和世界上大多数地方使用的色调、饱和度、亮度HSL
,not Python 的colorsys
中使用的HLS
。
保密类型(Secret Types)¶
您可以使用 SecretStr
和 SecretBytes
数据类型来存储您不希望在日志记录或回溯中可见的敏感信息。 SecretStr
和 SecretBytes
可以幂等地初始化,也可以分别使用 str
或 bytes
进行初始化。 SecretStr
和 SecretBytes
在转换为 json 时将被格式化为 '**********'
或 ''
。
from pydantic import BaseModel, SecretStr, SecretBytes, ValidationError
class SimpleModel(BaseModel):
password: SecretStr
password_bytes: SecretBytes
sm = SimpleModel(password='IAmSensitive', password_bytes=b'IAmSensitiveBytes')
# Standard access methods will not display the secret
print(sm)
#> password=SecretStr('**********') password_bytes=SecretBytes(b'**********')
print(sm.password)
#> **********
print(sm.dict())
"""
{
'password': SecretStr('**********'),
'password_bytes': SecretBytes(b'**********'),
}
"""
print(sm.json())
#> {"password": "**********", "password_bytes": "**********"}
# Use get_secret_value method to see the secret's content.
print(sm.password.get_secret_value())
#> IAmSensitive
print(sm.password_bytes.get_secret_value())
#> b'IAmSensitiveBytes'
try:
SimpleModel(password=[1, 2, 3], password_bytes=[1, 2, 3])
except ValidationError as e:
print(e)
"""
2 validation errors for SimpleModel
password
str type expected (type=type_error.str)
password_bytes
byte type expected (type=type_error.bytes)
"""
# If you want the secret to be dumped as plain-text using the json method,
# you can use json_encoders in the Config class.
class SimpleModelDumpable(BaseModel):
password: SecretStr
password_bytes: SecretBytes
class Config:
json_encoders = {
SecretStr: lambda v: v.get_secret_value() if v else None,
SecretBytes: lambda v: v.get_secret_value() if v else None,
}
sm2 = SimpleModelDumpable(
password='IAmSensitive', password_bytes=b'IAmSensitiveBytes'
)
# Standard access methods will not display the secret
print(sm2)
#> password=SecretStr('**********') password_bytes=SecretBytes(b'**********')
print(sm2.password)
#> **********
print(sm2.dict())
"""
{
'password': SecretStr('**********'),
'password_bytes': SecretBytes(b'**********'),
}
"""
# But the json method will
print(sm2.json())
#> {"password": "IAmSensitive", "password_bytes": "IAmSensitiveBytes"}
(这个脚本是完整的,它应该“按原样”运行)
Json类型(Json Type)¶
您可以使用 Json
数据类型让 pydantic 首先加载原始 JSON 字符串。 它还可以选择用于将加载的对象解析为另一种类型,基于参数化的Json
类型:
from typing import Any, List
from pydantic import BaseModel, Json, ValidationError
class AnyJsonModel(BaseModel):
json_obj: Json[Any]
class ConstrainedJsonModel(BaseModel):
json_obj: Json[List[int]]
print(AnyJsonModel(json_obj='{"b": 1}'))
#> json_obj={'b': 1}
print(ConstrainedJsonModel(json_obj='[1, 2, 3]'))
#> json_obj=[1, 2, 3]
try:
ConstrainedJsonModel(json_obj=12)
except ValidationError as e:
print(e)
"""
1 validation error for ConstrainedJsonModel
json_obj
JSON object must be str, bytes or bytearray (type=type_error.json)
"""
try:
ConstrainedJsonModel(json_obj='[a, b]')
except ValidationError as e:
print(e)
"""
1 validation error for ConstrainedJsonModel
json_obj
Invalid JSON (type=value_error.json)
"""
try:
ConstrainedJsonModel(json_obj='["a", "b"]')
except ValidationError as e:
print(e)
"""
2 validation errors for ConstrainedJsonModel
json_obj -> 0
value is not a valid integer (type=type_error.integer)
json_obj -> 1
value is not a valid integer (type=type_error.integer)
"""
from typing import Any
from pydantic import BaseModel, Json, ValidationError
class AnyJsonModel(BaseModel):
json_obj: Json[Any]
class ConstrainedJsonModel(BaseModel):
json_obj: Json[list[int]]
print(AnyJsonModel(json_obj='{"b": 1}'))
#> json_obj={'b': 1}
print(ConstrainedJsonModel(json_obj='[1, 2, 3]'))
#> json_obj=[1, 2, 3]
try:
ConstrainedJsonModel(json_obj=12)
except ValidationError as e:
print(e)
"""
1 validation error for ConstrainedJsonModel
json_obj
JSON object must be str, bytes or bytearray (type=type_error.json)
"""
try:
ConstrainedJsonModel(json_obj='[a, b]')
except ValidationError as e:
print(e)
"""
1 validation error for ConstrainedJsonModel
json_obj
Invalid JSON (type=value_error.json)
"""
try:
ConstrainedJsonModel(json_obj='["a", "b"]')
except ValidationError as e:
print(e)
"""
2 validation errors for ConstrainedJsonModel
json_obj -> 0
value is not a valid integer (type=type_error.integer)
json_obj -> 1
value is not a valid integer (type=type_error.integer)
"""
(这个脚本是完整的,它应该“按原样”运行)
支付卡号码(Payment Card Numbers)¶
PaymentCardNumber
类型验证支付卡(例如借记卡或信用卡)。
from datetime import date
from pydantic import BaseModel
from pydantic.types import PaymentCardBrand, PaymentCardNumber, constr
class Card(BaseModel):
name: constr(strip_whitespace=True, min_length=1)
number: PaymentCardNumber
exp: date
@property
def brand(self) -> PaymentCardBrand:
return self.number.brand
@property
def expired(self) -> bool:
return self.exp < date.today()
card = Card(
name='Georg Wilhelm Friedrich Hegel',
number='4000000000000002',
exp=date(2023, 9, 30),
)
assert card.number.brand == PaymentCardBrand.visa
assert card.number.bin == '400000'
assert card.number.last4 == '0002'
assert card.number.masked == '400000******0002'
(这个脚本是完整的,它应该“按原样”运行)
PaymentCardBrand
可以是基于 BIN 的以下之一:
PaymentCardBrand.amex
PaymentCardBrand.mastercard
PaymentCardBrand.visa
PaymentCardBrand.other
实际验证验证卡号是:
- 只有数字的
str
- luhn 有效的
- 基于 BIN 的正确长度,如果是美国运通卡、万事达卡或维萨卡,以及所有其他品牌的 12 到 19 位数字
约束类型(Constrained Types)¶
可以使用 con*
类型函数来限制许多常见类型的值:
from decimal import Decimal
from pydantic import (
BaseModel,
NegativeFloat,
NegativeInt,
PositiveFloat,
PositiveInt,
NonNegativeFloat,
NonNegativeInt,
NonPositiveFloat,
NonPositiveInt,
conbytes,
condecimal,
confloat,
conint,
conlist,
conset,
constr,
Field,
)
class Model(BaseModel):
upper_bytes: conbytes(to_upper=True)
lower_bytes: conbytes(to_lower=True)
short_bytes: conbytes(min_length=2, max_length=10)
strip_bytes: conbytes(strip_whitespace=True)
upper_str: constr(to_upper=True)
lower_str: constr(to_lower=True)
short_str: constr(min_length=2, max_length=10)
regex_str: constr(regex=r'^apple (pie|tart|sandwich)$')
strip_str: constr(strip_whitespace=True)
big_int: conint(gt=1000, lt=1024)
mod_int: conint(multiple_of=5)
pos_int: PositiveInt
neg_int: NegativeInt
non_neg_int: NonNegativeInt
non_pos_int: NonPositiveInt
big_float: confloat(gt=1000, lt=1024)
unit_interval: confloat(ge=0, le=1)
mod_float: confloat(multiple_of=0.5)
pos_float: PositiveFloat
neg_float: NegativeFloat
non_neg_float: NonNegativeFloat
non_pos_float: NonPositiveFloat
short_list: conlist(int, min_items=1, max_items=4)
short_set: conset(int, min_items=1, max_items=4)
decimal_positive: condecimal(gt=0)
decimal_negative: condecimal(lt=0)
decimal_max_digits_and_places: condecimal(max_digits=2, decimal_places=2)
mod_decimal: condecimal(multiple_of=Decimal('0.25'))
bigger_int: int = Field(..., gt=10000)
(这个脚本是完整的,它应该“按原样”运行)
其中 Field
指的是 字段函数。
conlist
的参数¶
使用 conlist
类型函数时可以使用以下参数
item_type: Type[T]
: 列表项的类型min_items: int = None
: 列表中的最小项目数max_items: int = None
: 列表中的最大项目数unique_items: bool = None
: 强制列表元素是唯一的
conset
的参数¶
使用 conset
类型函数时可以使用以下参数
item_type: Type[T]
: 设置项目的类型min_items: int = None
: 集合中的最小项目数max_items: int = None
: 集合中的最大项目数
confrozenset
的参数¶
使用 confrozenset
类型函数时可以使用以下参数
item_type: Type[T]
: frozenset 项目的类型min_items: int = None
: frozenset 中的最小项目数max_items: int = None
: frozenset 中的最大项目数
conint
的参数¶
使用 conint
类型函数时可以使用以下参数
strict: bool = False
: 控制类型强制gt: int = None
: 强制整数大于设定值ge: int = None
: 强制整数大于或等于设定值lt: int = None
: 强制整数小于设定值le: int = None
: 强制整数小于或等于设定值multiple_of: int = None
: 强制整数为设定值的倍数
confloat
的参数¶
使用 confloat
类型函数时可以使用以下参数
strict: bool = False
: 控制类型强制gt: float = None
: 强制浮动大于设定值ge: float = None
: 强制float大于等于设定值lt: float = None
: 强制 float 小于设定值le: float = None
: 强制 float 小于或等于设置值multiple_of: float = None
: 强制 float 是设置值的倍数allow_inf_nan: bool = True
: 是否允许无穷大(+inf
和-inf
)和 NaN 值,默认为True
,设置为False
以与JSON
兼容, 见 #3994 获取更多详情, 在 V1.10 版本中添加
condecimal
的参数¶
使用 condecimal
类型函数时可以使用以下参数
gt: Decimal = None
: 强制小数大于设定值ge: Decimal = None
: 强制小数大于或等于设定值lt: Decimal = None
: 强制小数小于设定值le: Decimal = None
: 强制小数小于或等于设定值max_digits: int = None
: 小数点内的最大位数。 它不包括小数点前的零或尾随的小数零decimal_places: int = None
: 允许的最大小数位数。 它不包括尾随的小数零multiple_of: Decimal = None
: 强制小数为设定值的倍数
constr
的参数¶
使用 constr
类型函数时可以使用以下参数
strip_whitespace: bool = False
: 删除前导和尾随空格to_upper: bool = False
: 将所有字符转为大写to_lower: bool = False
: 将所有字符变为小写strict: bool = False
: 控制类型强制min_length: int = None
: 字符串的最小长度max_length: int = None
: 字符串的最大长度curtail_length: int = None
: 当字符串长度超过设定值时,将字符串长度收缩到设定值regex: str = None
: 用于验证字符串的正则表达式
conbytes
的参数¶
使用 conbytes
类型函数时可以使用以下参数
strip_whitespace: bool = False
: 删除前导和尾随空格to_upper: bool = False
: 将所有字符转为大写to_lower: bool = False
: 将所有字符变为小写min_length: int = None
: 字节串的最小长度max_length: int = None
: 字节串的最大长度strict: bool = False
: 控制类型强制
condate
的参数¶
使用 condate
类型函数时可以使用以下参数
gt: date = None
: 强制日期大于设定值ge: date = None
: 强制日期大于或等于设定值lt: date = None
: 强制日期小于设定值le: date = None
: 强制日期小于或等于设定值
严格类型(Strict Types)¶
您可以使用StrictStr
、StrictBytes
、StrictInt
、StrictFloat
和StrictBool
类型来防止来自兼容类型的强制转换。
只有当验证值属于相应类型或该类型的子类型时,这些类型才会通过验证。
此行为也通过ConstrainedStr
、ConstrainedBytes
、ConstrainedFloat
和ConstrainedInt
类的strict
字段公开,并且可以与大量复杂的验证规则结合使用。
以下注意事项适用:
StrictBytes
(以及ConstrainedBytes
的strict
选项)将接受bytes
和bytearray
类型。StrictInt
(以及ConstrainedInt
的strict
选项)将不接受bool
类型,即使bool
是 Python 中int
的子类。 其他子类将起作用。StrictFloat
(以及ConstrainedFloat
的strict
选项)将不接受int
。
from pydantic import (
BaseModel,
StrictBytes,
StrictBool,
StrictInt,
ValidationError,
confloat,
)
class StrictBytesModel(BaseModel):
strict_bytes: StrictBytes
try:
StrictBytesModel(strict_bytes='hello world')
except ValidationError as e:
print(e)
"""
1 validation error for StrictBytesModel
strict_bytes
byte type expected (type=type_error.bytes)
"""
class StrictIntModel(BaseModel):
strict_int: StrictInt
try:
StrictIntModel(strict_int=3.14159)
except ValidationError as e:
print(e)
"""
1 validation error for StrictIntModel
strict_int
value is not a valid integer (type=type_error.integer)
"""
class ConstrainedFloatModel(BaseModel):
constrained_float: confloat(strict=True, ge=0.0)
try:
ConstrainedFloatModel(constrained_float=3)
except ValidationError as e:
print(e)
"""
1 validation error for ConstrainedFloatModel
constrained_float
value is not a valid float (type=type_error.float)
"""
try:
ConstrainedFloatModel(constrained_float=-1.23)
except ValidationError as e:
print(e)
"""
1 validation error for ConstrainedFloatModel
constrained_float
ensure this value is greater than or equal to 0.0
(type=value_error.number.not_ge; limit_value=0.0)
"""
class StrictBoolModel(BaseModel):
strict_bool: StrictBool
try:
StrictBoolModel(strict_bool='False')
except ValidationError as e:
print(str(e))
"""
1 validation error for StrictBoolModel
strict_bool
value is not a valid boolean (type=value_error.strictbool)
"""
(这个脚本是完整的,它应该“按原样”运行)
字节大小类型(ByteSize)¶
您可以使用ByteSize
数据类型将字节字符串表示形式转换为原始字节,并打印出人类可读的字节版本。
Info
请注意,1b
将被解析为1 byte
而不是1 bit
。
from pydantic import BaseModel, ByteSize
class MyModel(BaseModel):
size: ByteSize
print(MyModel(size=52000).size)
#> 52000
print(MyModel(size='3000 KiB').size)
#> 3072000
m = MyModel(size='50 PB')
print(m.size.human_readable())
#> 44.4PiB
print(m.size.human_readable(decimal=True))
#> 50.0PB
print(m.size.to('TiB'))
#> 45474.73508864641
(这个脚本是完整的,它应该“按原样”运行)
自定义数据类型(Custom Data Types)¶
您还可以定义自己的自定义数据类型。 有几种方法可以实现它。
带有 __get_validators__
的类 (Classes with __get_validators__
)¶
您使用带有类方法 __get_validators__
的自定义类。 它将被调用以获取验证器来解析和验证输入数据。
Tip
这些验证器具有与 Validators 中相同的语义,您可以声明参数 config
、field
等。
import re
from pydantic import BaseModel
# https://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom#Validation
post_code_regex = re.compile(
r'(?:'
r'([A-Z]{1,2}[0-9][A-Z0-9]?|ASCN|STHL|TDCU|BBND|[BFS]IQQ|PCRN|TKCA) ?'
r'([0-9][A-Z]{2})|'
r'(BFPO) ?([0-9]{1,4})|'
r'(KY[0-9]|MSR|VG|AI)[ -]?[0-9]{4}|'
r'([A-Z]{2}) ?([0-9]{2})|'
r'(GE) ?(CX)|'
r'(GIR) ?(0A{2})|'
r'(SAN) ?(TA1)'
r')'
)
class PostCode(str):
"""
Partial UK postcode validation. Note: this is just an example, and is not
intended for use in production; in particular this does NOT guarantee
a postcode exists, just that it has a valid format.
"""
@classmethod
def __get_validators__(cls):
# one or more validators may be yielded which will be called in the
# order to validate the input, each validator will receive as an input
# the value returned from the previous validator
yield cls.validate
@classmethod
def __modify_schema__(cls, field_schema):
# __modify_schema__ should mutate the dict it receives in place,
# the returned value will be ignored
field_schema.update(
# simplified regex here for brevity, see the wikipedia link above
pattern='^[A-Z]{1,2}[0-9][A-Z0-9]? ?[0-9][A-Z]{2}$',
# some example postcodes
examples=['SP11 9DG', 'w1j7bu'],
)
@classmethod
def validate(cls, v):
if not isinstance(v, str):
raise TypeError('string required')
m = post_code_regex.fullmatch(v.upper())
if not m:
raise ValueError('invalid postcode format')
# you could also return a string here which would mean model.post_code
# would be a string, pydantic won't care but you could end up with some
# confusion since the value's type won't match the type annotation
# exactly
return cls(f'{m.group(1)} {m.group(2)}')
def __repr__(self):
return f'PostCode({super().__repr__()})'
class Model(BaseModel):
post_code: PostCode
model = Model(post_code='sw8 5el')
print(model)
#> post_code=PostCode('SW8 5EL')
print(model.post_code)
#> SW8 5EL
print(Model.schema())
"""
{
'title': 'Model',
'type': 'object',
'properties': {
'post_code': {
'title': 'Post Code',
'pattern': '^[A-Z]{1,2}[0-9][A-Z0-9]? ?[0-9][A-Z]{2}$',
'examples': ['SP11 9DG', 'w1j7bu'],
'type': 'string',
},
},
'required': ['post_code'],
}
"""
(这个脚本是完整的,它应该“按原样”运行)
类似的验证可以使用 constr(regex=...)
来实现,除了值不会用空格格式化,模式将只包含完整模式,返回值将是 香草字符串。
有关如何生成模型架构的更多详细信息,请参阅 schema。
允许任意类型(Arbitrary Types Allowed)¶
您可以使用 模型配置 中的 arbitrary_types_allowed
配置允许任意类型。
from pydantic import BaseModel, ValidationError
# This is not a pydantic model, it's an arbitrary class
class Pet:
def __init__(self, name: str):
self.name = name
class Model(BaseModel):
pet: Pet
owner: str
class Config:
arbitrary_types_allowed = True
pet = Pet(name='Hedwig')
# A simple check of instance type is used to validate the data
model = Model(owner='Harry', pet=pet)
print(model)
#> pet=<types_arbitrary_allowed.Pet object at 0x7fad44babd10> owner='Harry'
print(model.pet)
#> <types_arbitrary_allowed.Pet object at 0x7fad44babd10>
print(model.pet.name)
#> Hedwig
print(type(model.pet))
#> <class 'types_arbitrary_allowed.Pet'>
try:
# If the value is not an instance of the type, it's invalid
Model(owner='Harry', pet='Hedwig')
except ValidationError as e:
print(e)
"""
1 validation error for Model
pet
instance of Pet expected (type=type_error.arbitrary_type;
expected_arbitrary_type=Pet)
"""
# Nothing in the instance of the arbitrary type is checked
# Here name probably should have been a str, but it's not validated
pet2 = Pet(name=42)
model2 = Model(owner='Harry', pet=pet2)
print(model2)
#> pet=<types_arbitrary_allowed.Pet object at 0x7fad44801990> owner='Harry'
print(model2.pet)
#> <types_arbitrary_allowed.Pet object at 0x7fad44801990>
print(model2.pet.name)
#> 42
print(type(model2.pet))
#> <class 'types_arbitrary_allowed.Pet'>
(这个脚本是完整的,它应该“按原样”运行)
作为类型的通用类(Generic Classes as Types)¶
Warning
这是一种您一开始可能不需要的高级技术。 在大多数情况下,您可能会使用标准的 pydantic 模型。
您可以使用 Generic Classes 作为字段类型,并根据“类型参数(type parameters)”(或子类型)执行自定义验证 与 __get_validators__
。
如果您用作子类型的通用类具有类方法__get_validators__
,则无需使用arbitrary_types_allowed
即可工作。
因为您可以声明接收当前 field
的验证器,所以您可以提取 sub_field
(从通用类类型参数)并使用它们验证数据。
from pydantic import BaseModel, ValidationError
from pydantic.fields import ModelField
from typing import TypeVar, Generic
AgedType = TypeVar('AgedType')
QualityType = TypeVar('QualityType')
# This is not a pydantic model, it's an arbitrary generic class
class TastingModel(Generic[AgedType, QualityType]):
def __init__(self, name: str, aged: AgedType, quality: QualityType):
self.name = name
self.aged = aged
self.quality = quality
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
# You don't need to add the "ModelField", but it will help your
# editor give you completion and catch errors
def validate(cls, v, field: ModelField):
if not isinstance(v, cls):
# The value is not even a TastingModel
raise TypeError('Invalid value')
if not field.sub_fields:
# Generic parameters were not provided so we don't try to validate
# them and just return the value as is
return v
aged_f = field.sub_fields[0]
quality_f = field.sub_fields[1]
errors = []
# Here we don't need the validated value, but we want the errors
valid_value, error = aged_f.validate(v.aged, {}, loc='aged')
if error:
errors.append(error)
# Here we don't need the validated value, but we want the errors
valid_value, error = quality_f.validate(v.quality, {}, loc='quality')
if error:
errors.append(error)
if errors:
raise ValidationError(errors, cls)
# Validation passed without errors, return the same instance received
return v
class Model(BaseModel):
# for wine, "aged" is an int with years, "quality" is a float
wine: TastingModel[int, float]
# for cheese, "aged" is a bool, "quality" is a str
cheese: TastingModel[bool, str]
# for thing, "aged" is a Any, "quality" is Any
thing: TastingModel
model = Model(
# This wine was aged for 20 years and has a quality of 85.6
wine=TastingModel(name='Cabernet Sauvignon', aged=20, quality=85.6),
# This cheese is aged (is mature) and has "Good" quality
cheese=TastingModel(name='Gouda', aged=True, quality='Good'),
# This Python thing has aged "Not much" and has a quality "Awesome"
thing=TastingModel(name='Python', aged='Not much', quality='Awesome'),
)
print(model)
"""
wine=<types_generics.TastingModel object at 0x7fad44b30ad0>
cheese=<types_generics.TastingModel object at 0x7fad44b1c710>
thing=<types_generics.TastingModel object at 0x7fad44684bd0>
"""
print(model.wine.aged)
#> 20
print(model.wine.quality)
#> 85.6
print(model.cheese.aged)
#> True
print(model.cheese.quality)
#> Good
print(model.thing.aged)
#> Not much
try:
# If the values of the sub-types are invalid, we get an error
Model(
# For wine, aged should be an int with the years, and quality a float
wine=TastingModel(name='Merlot', aged=True, quality='Kinda good'),
# For cheese, aged should be a bool, and quality a str
cheese=TastingModel(name='Gouda', aged='yeah', quality=5),
# For thing, no type parameters are declared, and we skipped validation
# in those cases in the Assessment.validate() function
thing=TastingModel(name='Python', aged='Not much', quality='Awesome'),
)
except ValidationError as e:
print(e)
"""
2 validation errors for Model
wine -> quality
value is not a valid float (type=type_error.float)
cheese -> aged
value could not be parsed to a boolean (type=type_error.bool)
"""
(这个脚本是完整的,它应该“按原样”运行)