Перейти к содержанию

??? API "Документация по API" pydantic.dataclasses.dataclass

Если вы не хотите использовать BaseModel Pydantic, вы можете вместо этого получить ту же проверку данных для стандартных классов данных (представленных в 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))
"""

!!! Примечание. Имейте в виду, что pydantic.dataclasses.dataclass не является заменой pydantic.BaseModel . pydantic.dataclasses.dataclass предоставляет аналогичную функциональность dataclasses.dataclass с добавлением проверки Pydantic. В некоторых случаях лучшим выбором является создание подкласса pydantic.BaseModel .

For more information and discussion see
[pydantic/pydantic#710](https://github.com/pydantic/pydantic/issues/710).

Некоторые различия между классами данных Pydantic и BaseModel включают в себя:

Вы можете использовать все стандартные типы полей Pydantic. Однако обратите внимание, что аргументы, переданные конструктору, будут скопированы для выполнения проверки и, при необходимости, приведения.

Чтобы выполнить проверку или создать схему JSON для класса данных Pydantic, теперь вам следует обернуть класс данных с помощью TypeAdapter и использовать его методы.

Поля, для которых требуется default_factory могут быть указаны либо с помощью pydantic.Field , либо с dataclasses.field .

import dataclasses
from typing import List, Optional

from pydantic import Field, TypeAdapter
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(TypeAdapter(User).json_schema())
"""
{
    'properties': {
        'id': {'title': 'Id', 'type': 'integer'},
        'name': {'default': 'John Doe', 'title': 'Name', 'type': 'string'},
        'friends': {
            'items': {'type': 'integer'},
            'title': 'Friends',
            'type': 'array',
        },
        'age': {
            'anyOf': [{'type': 'integer'}, {'type': 'null'}],
            'default': None,
            'description': 'do not lie!',
            'title': 'The age of the user',
        },
        'height': {
            'anyOf': [
                {'maximum': 300, 'minimum': 50, 'type': 'integer'},
                {'type': 'null'},
            ],
            'default': None,
            'title': 'The height in cm',
        },
    },
    'required': ['id'],
    'title': 'User',
    'type': 'object',
}
"""

pydantic.dataclasses.dataclass Аргументы такие же, как и у стандартного декоратора, за исключением одного дополнительного аргумента ключевого слова config , который имеет то же значение, что и model_config.

!!! предупреждение. После версии 1.2 необходимо установить плагин Mypy для проверки типов классов данных pydantic .

Дополнительные сведения об объединении валидаторов с классами данных см. в разделе Валидаторы классов данных .

Конфигурация класса данных

Если вы хотите изменить config , как если бы вы использовали BaseModel , у вас есть два варианта:

  • Применить конфигурацию к декоратору класса данных как dict
  • Используйте ConfigDict в качестве конфигурации

    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)) # (1)! 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

  • Подробнее о validate_assignment можно прочитать в справке по API.

!!! Примечание. Классы данных Pydantic поддерживают конфигурацию extra для ignore , forbid или allow дополнительных полей, передаваемых инициализатору. Однако некоторое поведение классов данных stdlib по умолчанию может преобладать. Например, любые дополнительные поля, присутствующие в классе данных Pydantic с использованием extra='allow' опускаются при print класса данных.

Вложенные классы данных

Вложенные классы данных поддерживаются как в классах данных, так и в обычных моделях.

from pydantic import AnyUrl
from pydantic.dataclasses import dataclass


@dataclass
class NavbarButton:
    href: AnyUrl


@dataclass
class Navbar:
    button: NavbarButton


navbar = Navbar(button={'href': 'https://example.com'})
print(navbar)
#> Navbar(button=NavbarButton(href=Url('https://example.com/')))

При использовании в качестве полей классы данных (Pydantic или vanilla) должны использовать dicts в качестве входных данных для проверки.

Общие классы данных

Pydantic поддерживает универсальные классы данных, в том числе с переменными типа.

from typing import Generic, TypeVar

from pydantic import TypeAdapter
from pydantic.dataclasses import dataclass

T = TypeVar('T')


@dataclass
class GenericDataclass(Generic[T]):
    x: T


validator = TypeAdapter(GenericDataclass)

assert validator.validate_python({'x': None}).x is None
assert validator.validate_python({'x': 1}).x == 1
assert validator.validate_python({'x': 'a'}).x == 'a'

Обратите внимание: если вы используете класс данных в качестве поля BaseModel или через FastAPI, вам не нужен TypeAdapter .

Классы данных Stdlib и классы данных Pydantic

Наследовать от классов данных stdlib

Классы данных Stdlib (вложенные или нет) также могут быть унаследованы, и 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
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='pika', input_type=str]
    """

Использование классов данных stdlib с BaseModel

Имейте в виду, что классы данных stdlib (вложенные или нет) автоматически преобразуются в классы данных Pydantic при смешивании с BaseModel ! Более того, сгенерированный класс данных Pydantic будет иметь ту же конфигурацию ( order , frozen ,...), что и исходный.

import dataclasses
from datetime import datetime
from typing import Optional

from pydantic import BaseModel, ConfigDict, ValidationError


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


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


class Foo(BaseModel):
    # Required so that pydantic revalidates the model attributes
    model_config = ConfigDict(revalidate_instances='always')

    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
      Input should be a valid string [type=string_type, input_value=['not', 'a', 'string'], input_type=list]
    """

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'

Используйте пользовательские типы

Поскольку классы данных stdlib автоматически преобразуются для дополнительной проверки, использование пользовательских типов может привести к неожиданному поведению. В этом случае вы можете просто добавить в конфиг arbitrary_types_allowed !

import dataclasses

from pydantic import BaseModel, ConfigDict
from pydantic.errors import PydanticSchemaGenerationError


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(BaseModel):
        dc: DC
        other: str

    # invalid as it is now a pydantic dataclass
    Model(dc=my_dc, other='other')
except PydanticSchemaGenerationError as e:
    print(e.message)
    """
    Unable to generate pydantic-core schema for <class '__main__.ArbitraryType'>. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.

    If you got this error by calling handler(<some type>) within `__get_pydantic_core_schema__` then you likely need to call `handler.generate_schema(<some type>)` since we do not call `__get_pydantic_core_schema__` on `<some type>` otherwise to avoid infinite recursion.
    """


class Model(BaseModel):
    model_config = ConfigDict(arbitrary_types_allowed=True)

    dc: DC
    other: str


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

Проверка того, является ли класс данных классом данных pydantic

Классы данных Pydantic по-прежнему считаются классами данных, поэтому использование dataclasses.is_dataclass вернет True . Чтобы проверить, является ли тип конкретно классом данных pydantic, вы можете использовать pydantic.dataclasses.is_pydantic_dataclass .

import dataclasses

import pydantic


@dataclasses.dataclass
class StdLibDataclass:
    id: int


PydanticDataclass = pydantic.dataclasses.dataclass(StdLibDataclass)

print(dataclasses.is_dataclass(StdLibDataclass))
#> True
print(pydantic.dataclasses.is_pydantic_dataclass(StdLibDataclass))
#> False

print(dataclasses.is_dataclass(PydanticDataclass))
#> True
print(pydantic.dataclasses.is_pydantic_dataclass(PydanticDataclass))
#> True

Хуки инициализации

Когда вы инициализируете класс данных, можно выполнить код до или после проверки с помощью параметра mode декоратора @model_validator .

from typing import Any, Dict

from typing_extensions import Self

from pydantic import model_validator
from pydantic.dataclasses import dataclass


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


@dataclass
class User:
    birth: Birth

    @model_validator(mode='before')
    @classmethod
    def pre_root(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        print(f'First: {values}')
        """
        First: ArgsKwargs((), {'birth': {'year': 1995, 'month': 3, 'day': 2}})
        """
        return values

    @model_validator(mode='after')
    def post_root(self) -> Self:
        print(f'Third: {self}')
        #> Third: User(birth=Birth(year=1995, month=3, day=2))
        return self

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


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

__post_init__ в классах данных Pydantic вызывается в середине валидаторов. Вот порядок:

  • model_validator(mode='before')
  • field_validator(mode='before')
  • field_validator(mode='after')
  • Внутренние валидаторы. например проверка для таких типов, как int , str ,...
  • __post_init__ .
  • model_validator(mode='after')

    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=PosixPath('world'), base_path=PosixPath('/hello')
        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')

Разница с классами данных stdlib

Обратите внимание, что класс dataclasses.dataclass из Python stdlib реализует только метод __post_init__ , поскольку он не выполняет этап проверки.

Сброс JSON

Классы данных Pydantic не имеют функции .model_dump_json() . Чтобы сбросить их в формате JSON, вам нужно будет использовать RootModel следующим образом:

import dataclasses
from typing import List

from pydantic import RootModel
from pydantic.dataclasses import dataclass


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


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

本文总阅读量