跳转至

Pydantic Company

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

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

数据类

如果您不想使用 pydanticBaseModel,您可以在标准 dataclasses 上获得相同的数据验证(在 Python 3.7 中引入) .

from datetime import datetime
from pydantic.dataclasses import dataclass


@dataclass
class User:
    id: int
    name: str = 'John Doe'
    signup_ts: datetime = None


user = User(id='42', signup_ts='2032-06-21T12:00')
print(user)
#> User(id=42, name='John Doe', signup_ts=datetime.datetime(2032, 6, 21, 12, 0))

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

Note

请记住,pydantic.dataclasses.dataclass 是带验证的 dataclasses.dataclass 的直接替代品,不是 pydantic.BaseModel 的替代品(初始化挂钩的工作方式略有不同)。 在某些情况下,子类化 pydantic.BaseModel 是更好的选择。

有关更多信息和讨论,请参阅 pydantic/pydantic#710

您可以使用所有标准 pydantic 字段类型,生成的数据类将与标准库dataclass装饰器创建的数据类相同。

可以通过 __pydantic_model__ 访问底层模型及其模式。 此外,需要 default_factory 的字段可以由 pydantic.Fielddataclasses.field 指定。

import dataclasses
from typing import List, Optional

from pydantic import Field
from pydantic.dataclasses import dataclass


@dataclass
class User:
    id: int
    name: str = 'John Doe'
    friends: List[int] = dataclasses.field(default_factory=lambda: [0])
    age: Optional[int] = dataclasses.field(
        default=None,
        metadata=dict(title='The age of the user', description='do not lie!')
    )
    height: Optional[int] = Field(None, title='The height in cm', ge=50, le=300)


user = User(id='42')
print(user.__pydantic_model__.schema())
"""
{
    'title': 'User',
    'type': 'object',
    'properties': {
        'id': {'title': 'Id', 'type': 'integer'},
        'name': {
            'title': 'Name',
            'default': 'John Doe',
            'type': 'string',
        },
        'friends': {
            'title': 'Friends',
            'type': 'array',
            'items': {'type': 'integer'},
        },
        'age': {
            'title': 'The age of the user',
            'description': 'do not lie!',
            'type': 'integer',
        },
        'height': {
            'title': 'The height in cm',
            'minimum': 50,
            'maximum': 300,
            'type': 'integer',
        },
    },
    'required': ['id'],
}
"""
import dataclasses
from typing import Optional

from pydantic import Field
from pydantic.dataclasses import dataclass


@dataclass
class User:
    id: int
    name: str = 'John Doe'
    friends: list[int] = dataclasses.field(default_factory=lambda: [0])
    age: Optional[int] = dataclasses.field(
        default=None,
        metadata=dict(title='The age of the user', description='do not lie!')
    )
    height: Optional[int] = Field(None, title='The height in cm', ge=50, le=300)


user = User(id='42')
print(user.__pydantic_model__.schema())
"""
{
    'title': 'User',
    'type': 'object',
    'properties': {
        'id': {'title': 'Id', 'type': 'integer'},
        'name': {
            'title': 'Name',
            'default': 'John Doe',
            'type': 'string',
        },
        'friends': {
            'title': 'Friends',
            'type': 'array',
            'items': {'type': 'integer'},
        },
        'age': {
            'title': 'The age of the user',
            'description': 'do not lie!',
            'type': 'integer',
        },
        'height': {
            'title': 'The height in cm',
            'minimum': 50,
            'maximum': 300,
            'type': 'integer',
        },
    },
    'required': ['id'],
}
"""
import dataclasses

from pydantic import Field
from pydantic.dataclasses import dataclass


@dataclass
class User:
    id: int
    name: str = 'John Doe'
    friends: list[int] = dataclasses.field(default_factory=lambda: [0])
    age: int | None = dataclasses.field(
        default=None,
        metadata=dict(title='The age of the user', description='do not lie!')
    )
    height: int | None = Field(None, title='The height in cm', ge=50, le=300)


user = User(id='42')
print(user.__pydantic_model__.schema())
"""
{
    'title': 'User',
    'type': 'object',
    'properties': {
        'id': {'title': 'Id', 'type': 'integer'},
        'name': {
            'title': 'Name',
            'default': 'John Doe',
            'type': 'string',
        },
        'friends': {
            'title': 'Friends',
            'type': 'array',
            'items': {'type': 'integer'},
        },
        'age': {
            'title': 'The age of the user',
            'description': 'do not lie!',
            'type': 'integer',
        },
        'height': {
            'title': 'The height in cm',
            'minimum': 50,
            'maximum': 300,
            'type': 'integer',
        },
    },
    'required': ['id'],
}
"""

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

pydantic.dataclasses.dataclass 的参数与标准装饰器相同,除了一个额外的关键字参数 configConfig 具有相同的含义。

Warning

在 v1.2 之后,必须安装 Mypy 插件 来类型检查 pydantic 数据类。

有关将验证器与数据类组合的更多信息,请参阅 数据类验证器

数据类配置(Dataclass Config)

如果您想像修改 BaseModel 一样修改 Config,您有以下三种选择:

from pydantic import ConfigDict
from pydantic.dataclasses import dataclass


# Option 1 - use directly a dict
# Note: `mypy` will still raise typo error
@dataclass(config=dict(validate_assignment=True))
class MyDataclass1:
    a: int


# Option 2 - use `ConfigDict`
# (same as before at runtime since it's a `TypedDict` but with intellisense)
@dataclass(config=ConfigDict(validate_assignment=True))
class MyDataclass2:
    a: int


# Option 3 - use a `Config` class like for a `BaseModel`
class Config:
    validate_assignment = True


@dataclass(config=Config)
class MyDataclass3:
    a: int

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

Warning

在 v1.10 之后,pydantic 数据类支持 Config.extra 但 标准库 数据类的一些默认行为可能会占上风。 例如,当 print时带有允许的额外字段的 pydantic 数据类时,它仍将使用 标准库 数据类的 __str__ 方法并仅显示必需的字段。 未来可能会进一步改进这一点。

嵌套数据类(Nested dataclasses)

数据类和普通模型都支持嵌套数据类。

from pydantic import AnyUrl
from pydantic.dataclasses import dataclass


@dataclass
class NavbarButton:
    href: AnyUrl


@dataclass
class Navbar:
    button: NavbarButton


navbar = Navbar(button=('https://example.com',))
print(navbar)
#> Navbar(button=NavbarButton(href=AnyUrl('https://example.com', scheme='https',
#> host='example.com', tld='com', host_type='domain')))

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

数据类属性可以由元组、字典或数据类本身的实例填充。

标准库数据类和_pydantic_数据类(Stdlib dataclasses and pydantic dataclasses)

转换标准库数据类为_pydantic_数据类(Convert stdlib dataclasses into pydantic dataclasses)

标准库 数据类(嵌套或非嵌套)只需用 pydantic.dataclasses.dataclass 装饰即可轻松转换为 pydantic 数据类。 Pydantic 将增强给定的 标准库 数据类,但不会改变默认行为(即未经验证)。 相反,它将围绕它创建一个包装器来触发验证,就像一个普通代理一样。 仍然可以通过 __dataclass__ 属性访问 标准库 数据类(参见下面的示例)。

import dataclasses
from datetime import datetime
from typing import Optional

import pydantic


@dataclasses.dataclass
class Meta:
    modified_date: Optional[datetime]
    seen_count: int


@dataclasses.dataclass
class File(Meta):
    filename: str


# `ValidatedFile` will be a proxy around `File`
ValidatedFile = pydantic.dataclasses.dataclass(File)

# the original dataclass is the `__dataclass__` attribute
assert ValidatedFile.__dataclass__ is File


validated_file = ValidatedFile(
    filename=b'thefilename',
    modified_date='2020-01-01T00:00',
    seen_count='7',
)
print(validated_file)
#> File(modified_date=datetime.datetime(2020, 1, 1, 0, 0), seen_count=7,
#> filename='thefilename')

try:
    ValidatedFile(
        filename=['not', 'a', 'string'],
        modified_date=None,
        seen_count=3,
    )
except pydantic.ValidationError as e:
    print(e)
    """
    1 validation error for File
    filename
      str type expected (type=type_error.str)
    """

# `File` is not altered and still does no validation by default
print(File(
#> File(modified_date=None, seen_count=3, filename=['not', 'a', 'string'])
    filename=['not', 'a', 'string'],
    modified_date=None,
    seen_count=3,
))
import dataclasses
from datetime import datetime

import pydantic


@dataclasses.dataclass
class Meta:
    modified_date: datetime | None
    seen_count: int


@dataclasses.dataclass
class File(Meta):
    filename: str


# `ValidatedFile` will be a proxy around `File`
ValidatedFile = pydantic.dataclasses.dataclass(File)

# the original dataclass is the `__dataclass__` attribute
assert ValidatedFile.__dataclass__ is File


validated_file = ValidatedFile(
    filename=b'thefilename',
    modified_date='2020-01-01T00:00',
    seen_count='7',
)
print(validated_file)
#> File(modified_date=datetime.datetime(2020, 1, 1, 0, 0), seen_count=7,
#> filename='thefilename')

try:
    ValidatedFile(
        filename=['not', 'a', 'string'],
        modified_date=None,
        seen_count=3,
    )
except pydantic.ValidationError as e:
    print(e)
    """
    1 validation error for File
    filename
      str type expected (type=type_error.str)
    """

# `File` is not altered and still does no validation by default
print(File(
#> File(modified_date=None, seen_count=3, filename=['not', 'a', 'string'])
    filename=['not', 'a', 'string'],
    modified_date=None,
    seen_count=3,
))

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

选择何时触发校验(Choose when to trigger validation)

一旦你的 标准库 数据类被 pydantic 数据类装饰器装饰,魔法方法就被添加来验证输入数据。 如果你愿意,你仍然可以继续使用你的数据类并选择何时触发它。

import dataclasses

from pydantic import ValidationError
from pydantic.dataclasses import dataclass as pydantic_dataclass, set_validation


@dataclasses.dataclass
class User:
    id: int
    name: str


# Enhance stdlib dataclass
pydantic_dataclass(User)


user1 = User(id='whatever', name='I want')

# validate data of `user1`
try:
    user1.__pydantic_validate_values__()
except ValidationError as e:
    print(e)
    """
    1 validation error for User
    id
      value is not a valid integer (type=type_error.integer)
    """

# Enforce validation
try:
    with set_validation(User, True):
        User(id='whatever', name='I want')
except ValidationError as e:
    print(e)
    """
    1 validation error for User
    id
      value is not a valid integer (type=type_error.integer)
    """

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

从标准库数据类继承(Inherit from stdlib dataclasses)

标准库 数据类(嵌套或非嵌套)也可以被继承,pydantic 将自动验证所有继承的字段。

import dataclasses

import pydantic


@dataclasses.dataclass
class Z:
    z: int


@dataclasses.dataclass
class Y(Z):
    y: int = 0


@pydantic.dataclasses.dataclass
class X(Y):
    x: int = 0


foo = X(x=b'1', y='2', z='3')
print(foo)
#> X(z=3, y=2, x=1)

try:
    X(z='pika')
except pydantic.ValidationError as e:
    print(e)
    """
    1 validation error for X
    z
      value is not a valid integer (type=type_error.integer)
    """

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

将标准库数据类与 BaseModel 一起使用(Use of stdlib dataclasses with BaseModel)

请记住,标准库 数据类(嵌套或非嵌套)在与 BaseModel 混合时会自动转换为_pydantic_数据类! 此外,生成的 pydantic 数据类将与原始配置具有完全相同的配置orderfrozen、...)。

import dataclasses
from datetime import datetime
from typing import Optional

from pydantic import BaseModel, ValidationError


@dataclasses.dataclass(frozen=True)
class User:
    name: str


@dataclasses.dataclass
class File:
    filename: str
    last_modification_time: Optional[datetime] = None


class Foo(BaseModel):
    file: File
    user: Optional[User] = None


file = File(
    filename=['not', 'a', 'string'],
    last_modification_time='2020-01-01T00:00',
)  # nothing is validated as expected
print(file)
#> File(filename=['not', 'a', 'string'],
#> last_modification_time='2020-01-01T00:00')

try:
    Foo(file=file)
except ValidationError as e:
    print(e)
    """
    1 validation error for Foo
    file -> filename
      str type expected (type=type_error.str)
    """

foo = Foo(file=File(filename='myfile'), user=User(name='pika'))
try:
    foo.user.name = 'bulbi'
except dataclasses.FrozenInstanceError as e:
    print(e)
    #> cannot assign to field 'name'
import dataclasses
from datetime import datetime

from pydantic import BaseModel, ValidationError


@dataclasses.dataclass(frozen=True)
class User:
    name: str


@dataclasses.dataclass
class File:
    filename: str
    last_modification_time: datetime | None = None


class Foo(BaseModel):
    file: File
    user: User | None = None


file = File(
    filename=['not', 'a', 'string'],
    last_modification_time='2020-01-01T00:00',
)  # nothing is validated as expected
print(file)
#> File(filename=['not', 'a', 'string'],
#> last_modification_time='2020-01-01T00:00')

try:
    Foo(file=file)
except ValidationError as e:
    print(e)
    """
    1 validation error for Foo
    file -> filename
      str type expected (type=type_error.str)
    """

foo = Foo(file=File(filename='myfile'), user=User(name='pika'))
try:
    foo.user.name = 'bulbi'
except dataclasses.FrozenInstanceError as e:
    print(e)
    #> cannot assign to field 'name'

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

使用自定义类型(Use custom types)

由于 标准库 数据类会自动转换为使用自定义类型添加验证,因此可能会导致一些意外行为。 在这种情况下,您只需在配置中添加 arbitrary_types_allowed 即可!

import dataclasses

import pydantic


class ArbitraryType:
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return f'ArbitraryType(value={self.value!r})'


@dataclasses.dataclass
class DC:
    a: ArbitraryType
    b: str


# valid as it is a builtin dataclass without validation
my_dc = DC(a=ArbitraryType(value=3), b='qwe')

try:
    class Model(pydantic.BaseModel):
        dc: DC
        other: str

    Model(dc=my_dc, other='other')
except RuntimeError as e:  # invalid as it is now a pydantic dataclass
    print(e)
    """
    no validator found for <class
    'dataclasses_arbitrary_types_allowed.ArbitraryType'>, see
    `arbitrary_types_allowed` in Config
    """


class Model(pydantic.BaseModel):
    dc: DC
    other: str

    class Config:
        arbitrary_types_allowed = True


m = Model(dc=my_dc, other='other')
print(repr(m))
#> Model(dc=DC(a=ArbitraryType(value=3), b='qwe'), other='other')

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

初始化钩子(Initialize hooks)

初始化数据类时,可以在 __post_init_post_parse__ 的帮助下在 验证后 执行代码。 这与 __post_init__ 不同,它在验证之前执行代码。

Tip

如果您使用 标准库 dataclass,您可能只有 __post_init__ 可用,并希望在之前完成验证。 在这种情况下,您可以设置 Config.post_init_call = 'after_validation'

from pydantic.dataclasses import dataclass


@dataclass
class Birth:
    year: int
    month: int
    day: int


@dataclass
class User:
    birth: Birth

    def __post_init__(self):
        print(self.birth)
        #> {'year': 1995, 'month': 3, 'day': 2}

    def __post_init_post_parse__(self):
        print(self.birth)
        #> Birth(year=1995, month=3, day=2)


user = User(**{'birth': {'year': 1995, 'month': 3, 'day': 2}})

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

从版本 v1.0 开始,任何用 dataclasses.InitVar 注释的字段都会传递给 __post_init__ __post_init_post_parse__

from dataclasses import InitVar
from pathlib import Path
from typing import Optional

from pydantic.dataclasses import dataclass


@dataclass
class PathData:
    path: Path
    base_path: InitVar[Optional[Path]]

    def __post_init__(self, base_path):
        print(f'Received path={self.path!r}, base_path={base_path!r}')
        #> Received path='world', base_path='/hello'

    def __post_init_post_parse__(self, base_path):
        if base_path is not None:
            self.path = base_path / self.path


path_data = PathData('world', base_path='/hello')
# Received path='world', base_path='/hello'
assert path_data.path == Path('/hello/world')
from dataclasses import InitVar
from pathlib import Path

from pydantic.dataclasses import dataclass


@dataclass
class PathData:
    path: Path
    base_path: InitVar[Path | None]

    def __post_init__(self, base_path):
        print(f'Received path={self.path!r}, base_path={base_path!r}')
        #> Received path='world', base_path='/hello'

    def __post_init_post_parse__(self, base_path):
        if base_path is not None:
            self.path = base_path / self.path


path_data = PathData('world', base_path='/hello')
# Received path='world', base_path='/hello'
assert path_data.path == Path('/hello/world')

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

与标准库数据类的区别(Difference with stdlib dataclasses)

请注意,Python 标准库中的dataclasses.dataclass仅实现了__post_init__方法,因为它不运行验证步骤。

当用 pydantic.dataclasses.dataclass 替换 dataclasses.dataclass 的用法时,建议将 __post_init__ 方法中执行的代码移动到 __post_init_post_parse__ 方法中,只留下需要的部分代码 在验证之前执行。

JSON Dumping

Pydantic 数据类没有 .json() 函数。 要将它们转储为 JSON,您需要按如下方式使用 pydantic_encoder

import dataclasses
import json
from typing import List

from pydantic.dataclasses import dataclass
from pydantic.json import pydantic_encoder


@dataclass
class User:
    id: int
    name: str = 'John Doe'
    friends: List[int] = dataclasses.field(default_factory=lambda: [0])


user = User(id='42')
print(json.dumps(user, indent=4, default=pydantic_encoder))
"""
{
    "id": 42,
    "name": "John Doe",
    "friends": [
        0
    ]
}
"""
import dataclasses
import json

from pydantic.dataclasses import dataclass
from pydantic.json import pydantic_encoder


@dataclass
class User:
    id: int
    name: str = 'John Doe'
    friends: list[int] = dataclasses.field(default_factory=lambda: [0])


user = User(id='42')
print(json.dumps(user, indent=4, default=pydantic_encoder))
"""
{
    "id": 42,
    "name": "John Doe",
    "friends": [
        0
    ]
}
"""

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