跳转至

Pydantic Company

我们根据我认为导致 Pydantic 成功的原则创办了一家公司。

公司公告中了解更多信息.

字段类型

在可能的情况下 pydantic 使用 标准库类型 来定义字段,从而平滑学习曲线。 然而,对于许多有用的应用程序,不存在标准库类型,因此 pydantic 实现 许多常用类型

如果没有适合您目的的现有类型,您还可以使用自定义属性和验证来实现您的自己的 pydantic 兼容类型

标准库类型(Standard Library Types)

pydantic 支持 Python 标准库中的许多常见类型。 如果您需要更严格的处理,请参阅 严格类型; 如果您需要限制允许的值(例如需要一个正整数),请参阅 Constrained Types

Nonetype(None)Literal[None](等同于 PEP 484) : 只允许 None

bool
请参阅下面的 Booleans,了解有关如何验证布尔值以及允许使用哪些值的详细信息
int
pydantic 使用 int(v) 将类型强制转换为 int; 请参阅 this 关于数据转换期间信息丢失的警告
float
类似地,float(v) 用于将值强制转换为浮点数
str
字符串按原样接受,int floatDecimal 使用 str(v) 强制转换,bytesbytearray 使用 v.decode() 转换,枚举继承自 str 使用 v.value 进行转换,所有其他类型都会导致错误
bytes
bytes 按原样接受,bytearray 使用 bytes(v) 转换,str 使用 v.encode() 转换,intfloatDecimal 是 使用 str(v).encode() 强制
list
允许 listtuplesetfrozensetdeque 或生成器和转换为列表; 有关子类型约束,请参见下面的typing.List
tuple
允许 listtuplesetfrozensetdeque 或生成器和转换为元组; 有关子类型约束,请参见下面的typing.Tuple
dict
dict(v) 用于尝试转换字典; 请参阅下面的 typing.Dict 了解子类型约束
set
允许将 listtuplesetfrozensetdeque 或生成器和强制转换为一个集合; 有关子类型约束,请参见下面的typing.Set
frozenset
允许 listtuplesetfrozensetdeque 或生成器和强制转换为冻结集; 有关子类型约束,请参阅下面的typing.FrozenSet
deque
允许 listtuplesetfrozensetdeque 或生成器和转换为双端队列; 有关子类型约束,请参见下面的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
根据 constraintsbound 限制允许的值,参见 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),对于 bytesbytearray 回退到 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 类型允许模型属性接受不同的类型,例如:

Info

使用 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 将尝试matchUnion 下定义的任何类型,并将使用第一个匹配的类型。 在上面的示例中,user_03id被定义为uuid.UUID类(在属性的 Union 注释下定义),但uuid.UUID可以编组为int 它选择匹配int类型并忽略其他类型。

Warning

typing.Union定义 时也会忽略顺序,所以 Union[int, float] == Union[float, int] 当与基于其他类型定义中的 Union 类型顺序的匹配相结合时,例如 ListDict 类型(因为 Python 将这些定义视为单例)。

例如,Dict[str, Union[int, float]] == Dict[str, Union[float, int]] 的顺序基于第一次定义。

请注意,这也可能 受第三方库影响 及其内部类型定义和导入顺序。

因此,建议在定义 Union 注解时,首先包含最具体的类型,然后是不太具体的类型。

在上面的示例中,UUID 类应该在 intstr 类之前,以排除这样的意外表示:

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

(这个脚本是完整的,它应该“按原样”运行)

Tip

Optional[x] 类型是 Union[x, None] 的简写。

Optional[x] 也可用于指定一个必填字段,该字段可以将 None 作为值。

必填字段 中查看更多详细信息。

区别联合【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 可以方便地重新组合 Uniondiscriminator 信息。 请参阅下面的示例!

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 对象
    • intfloat,假定为 Unix 时间,即自 1970 年 1 月 1 日以来的秒数(如果 >= -2e10 或 <= 2e10)或毫秒(如果 < -2e10 或 > 2e10
    • str, 以下格式有效:

      • YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z or [±]HH[:]MM]
      • intfloat 作为字符串(假定为 Unix 时间)
  • date 字段可以是:

    • date, 现有的 date 对象
    • intfloat, 见 datetime
    • str, 以下格式有效:

      • YYYY-MM-DD
      • intfloat, 见 datetime
  • time 字段可以是:

    • time, 现有的 time 对象
    • str, 以下格式有效:

      • HH:MM[:SS[.ffffff]][Z or [±]HH[:]MM]
  • timedelta 字段可以是:

    • timedelta, 现有的 timedelta 对象
    • intfloat, 假定为秒
    • 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

  • 一个有效的布尔值(即TrueFalse),
  • 整数01
  • 一个 str ,当转换为小写时,它是其中之一 '0', 'off', 'f', 'false', 'n', 'no', '1', 'on', 't', 'true', 'y', 'yes'
  • 解码为 str 时有效(根据前面的规则)的 bytes

Note

如果你想要更严格的布尔逻辑(例如,一个只允许 TrueFalse 的字段)你可以使用 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 对象,它具有两个属性:nameemail

对于 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
允许 IPv4AddressIPv6Address
IPvAnyInterface
允许 IPv4InterfaceIPv6Interface
IPvAnyNetwork
允许 IPv4NetworkIPv6Network
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: 方案 httphttps,不需要 TLD,需要主机地址
  • HttpUrl: 方案 httphttps,需要 TLD,需要主机地址,最大长度 2083
  • FileUrl: 匹配 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: 模式 amqpamqps,不需要用户信息,不需要 TLD,不需要主机地址
  • RedisDsn: 匹配 redisrediss,不需要用户信息,不需要 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 和世界上大多数地方使用的色调、饱和度、亮度HSLnot Python 的colorsys中使用的HLS

保密类型(Secret Types)

您可以使用 SecretStrSecretBytes 数据类型来存储您不希望在日志记录或回溯中可见的敏感信息。 SecretStrSecretBytes 可以幂等地初始化,也可以分别使用 strbytes 进行初始化。 SecretStrSecretBytes 在转换为 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)

您可以使用StrictStrStrictBytesStrictIntStrictFloatStrictBool类型来防止来自兼容类型的强制转换。

只有当验证值属于相应类型或该类型的子类型时,这些类型才会通过验证。

此行为也通过ConstrainedStrConstrainedBytesConstrainedFloatConstrainedInt类的strict字段公开,并且可以与大量复杂的验证规则结合使用。

以下注意事项适用:

  • StrictBytes(以及 ConstrainedBytesstrict 选项)将接受 bytesbytearray 类型。
  • StrictInt(以及 ConstrainedIntstrict 选项)将不接受 bool 类型,即使 bool 是 Python 中 int 的子类。 其他子类将起作用。
  • StrictFloat(以及 ConstrainedFloatstrict 选项)将不接受 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 中相同的语义,您可以声明参数 configfield 等。

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)
    """

(这个脚本是完整的,它应该“按原样”运行)