??? 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
включают в себя:
- Как работают хуки инициализации
- Сброс JSON
Вы можете использовать все стандартные типы полей 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
]
}
"""
本文总阅读量次