Lewati ke isi

??? api "Dokumentasi API" pydantic.dataclasses.dataclass

Jika Anda tidak ingin menggunakan BaseModel Pydantic, Anda bisa mendapatkan validasi data yang sama pada kelas data standar (diperkenalkan dengan 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))
"""

!!! catatan Ingatlah itu pydantic.dataclasses.dataclass bukan pengganti pydantic.BaseModel . pydantic.dataclasses.dataclass menyediakan fungsionalitas serupa dengan dataclasses.dataclass dengan tambahan validasi Pydantic. Ada beberapa kasus di mana subkelas pydantic.BaseModel adalah pilihan yang lebih baik.

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

Beberapa perbedaan antara kelas data Pydantic dan BaseModel meliputi:

Anda dapat menggunakan semua tipe bidang Pydantic standar. Namun, perhatikan bahwa argumen yang diteruskan ke konstruktor akan disalin untuk melakukan validasi dan, jika diperlukan, paksaan.

Untuk melakukan validasi atau menghasilkan skema JSON pada kelas data Pydantic, Anda sekarang harus menggabungkan kelas data dengan TypeAdapter dan menggunakan metodenya.

Bidang yang memerlukan default_factory dapat ditentukan dengan pydantic.Field atau 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 Argumennya sama dengan dekorator standar, kecuali satu config argumen kata kunci tambahan yang memiliki arti yang sama dengan model_config.

!!! peringatan Setelah v1.2, Plugin Mypy harus diinstal untuk mengetik kelas data pydantic .

Untuk informasi selengkapnya tentang menggabungkan validator dengan kelas data, lihat validator kelas data .

Konfigurasi kelas data

Jika Anda ingin mengubah config seperti yang Anda lakukan dengan BaseModel , Anda memiliki dua opsi:

  • Terapkan konfigurasi ke dekorator kelas data sebagai dict
  • Gunakan ConfigDict sebagai konfigurasinya

    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

  • Anda dapat membaca lebih lanjut tentang validate_assignment di referensi API.

!!! catatan Kelas data Pydantic mendukung konfigurasi extra untuk ignore , forbid , atau allow bidang tambahan diteruskan ke penginisialisasi. Namun, beberapa perilaku default kelas data stdlib mungkin berlaku. Misalnya, bidang tambahan apa pun yang ada pada kelas data Pydantic yang menggunakan extra='allow' dihilangkan saat kelas data tersebut print .

Kelas data bersarang

Kelas data bertumpuk didukung dalam kelas data dan model normal.

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/')))

Saat digunakan sebagai kolom, kelas data (Pydantic atau Vanilla) harus menggunakan dicts sebagai input validasi.

Kelas data generik

Pydantic mendukung kelas data generik, termasuk kelas data dengan variabel tipe.

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'

Perhatikan bahwa, jika Anda menggunakan dataclass sebagai bidang BaseModel atau melalui FastAPI Anda tidak memerlukan TypeAdapter .

Kelas data Stdlib dan kelas data Pydantic

Mewarisi dari kelas data stdlib

Kelas data Stdlib (bersarang atau tidak) juga dapat diwarisi dan Pydantic akan secara otomatis memvalidasi semua bidang yang diwarisi.

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

Penggunaan kelas data stdlib dengan BaseModel

Ingatlah bahwa kelas data stdlib (bersarang atau tidak) secara otomatis diubah menjadi kelas data Pydantic ketika dicampur dengan BaseModel ! Selanjutnya kelas data Pydantic yang dihasilkan akan memiliki konfigurasi yang sama persis ( order , frozen , ...) dengan yang asli.

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'

Gunakan tipe khusus

Karena kelas data stdlib secara otomatis dikonversi untuk menambahkan validasi, penggunaan tipe khusus dapat menyebabkan beberapa perilaku yang tidak terduga. Dalam hal ini Anda cukup menambahkan arbitrary_types_allowed di konfigurasi!

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

Memeriksa apakah kelas data adalah kelas data yang pydantic

Kelas data Pydantic masih dianggap sebagai kelas data, jadi penggunaan dataclasses.is_dataclass akan mengembalikan True . Untuk memeriksa apakah suatu tipe secara khusus merupakan kelas data pydantic yang dapat Anda gunakan 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

Kait inisialisasi

Saat Anda menginisialisasi kelas data, Anda dapat mengeksekusi kode sebelum atau sesudah validasi dengan bantuan parameter mode dekorator @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__ di kelas data Pydantic dipanggil di tengah-tengah validator. Berikut urutannya:

  • model_validator(mode='before')
  • field_validator(mode='before')
  • field_validator(mode='after')
  • Validator batin. misalnya validasi untuk tipe seperti 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')

Perbedaan dengan kelas data stdlib

Perhatikan bahwa dataclasses.dataclass dari Python stdlib hanya mengimplementasikan metode __post_init__ karena tidak menjalankan langkah validasi.

pembuangan JSON

Kelas data Pydantic tidak menampilkan fungsi .model_dump_json() . Untuk membuangnya sebagai JSON, Anda perlu menggunakan RootModel sebagai berikut:

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
    ]
}
"""

本文总阅读量