Ga naar inhoud

??? api "API-documentatie" pydantic.dataclasses.dataclass

Als u BaseModel van Pydantic niet wilt gebruiken, kunt u in plaats daarvan dezelfde gegevensvalidatie krijgen op standaard dataklassen (geïntroduceerd in 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))
"""

!!! opmerking Houd daar rekening mee pydantic.dataclasses.dataclass is geen vervanging voor pydantic.BaseModel . pydantic.dataclasses.dataclass biedt een vergelijkbare functionaliteit als dataclasses.dataclass met de toevoeging van Pydantic-validatie. Er zijn gevallen waarin subclassificatie van pydantic.BaseModel de betere keuze is.

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

Enkele verschillen tussen Pydantic-dataklassen en BaseModel zijn onder meer:

U kunt alle standaard Pydantic-veldtypen gebruiken. Houd er echter rekening mee dat argumenten die aan de constructor worden doorgegeven, worden gekopieerd om validatie en, waar nodig, dwang uit te voeren.

Om validatie uit te voeren of een JSON-schema te genereren op een Pydantic-dataklasse, moet u de dataklasse nu inpakken met een TypeAdapter en gebruik maken van de methoden ervan.

Velden waarvoor een default_factory vereist is, kunnen worden opgegeven met een pydantic.Field of een 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 's argumenten zijn hetzelfde als die van de standaard decorateur, behalve één extra trefwoordargument config dat dezelfde betekenis heeft als model_config.

!!! waarschuwing Na v1.2 moet de Mypy-plug-in worden geïnstalleerd om pydantic dataclasses te kunnen controleren.

Zie dataclass validators voor meer informatie over het combineren van validators met dataclasses.

Dataclass-configuratie

Als u de config wilt wijzigen zoals u zou doen met een BaseModel , heeft u twee opties:

  • Pas config als dictaat toe op de dataclass-decorateur
  • Gebruik ConfigDict als configuratie

    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

  • U kunt meer lezen over validate_assignment in API-referentie.

!!! opmerking Pydantic dataclasses ondersteunen extra configuratie om extra velden ignore , forbid of allow die aan de initialisatie worden doorgegeven. Een bepaald standaardgedrag van stdlib-dataklassen kan echter de overhand hebben. Eventuele extra velden die aanwezig zijn in een Pydantic-dataklasse die extra='allow' gebruiken, worden bijvoorbeeld weggelaten wanneer de dataklasse wordt print .

Geneste dataklassen

Geneste dataklassen worden zowel in dataklassen als in normale modellen ondersteund.

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

Bij gebruik als velden moeten dataklassen (Pydantic of vanilla) dictaten gebruiken als validatie-invoer.

Generieke dataklassen

Pydantic ondersteunt generieke dataklassen, inclusief die met typevariabelen.

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'

Houd er rekening mee dat als u de dataclass als veld van een BaseModel of via FastAPI gebruikt, u geen TypeAdapter nodig heeft.

Stdlib-dataklassen en Pydantic-dataklassen

Erven van stdlib-dataklassen

Stdlib-dataklassen (al dan niet genest) kunnen ook worden overgenomen en Pydantic valideert automatisch alle overgenomen velden.

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

Gebruik van stdlib-dataklassen met BaseModel

Houd er rekening mee dat stdlib-dataklassen (al dan niet genest) automatisch worden geconverteerd naar Pydantic-dataklassen wanneer ze worden gemengd met BaseModel ! Bovendien zal de gegenereerde Pydantic-dataklasse exact dezelfde configuratie hebben ( order , frozen , ...) als de originele.

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'

Gebruik aangepaste typen

Omdat stdlib-dataklassen automatisch worden geconverteerd om validatie toe te voegen, kan het gebruik van aangepaste typen onverwacht gedrag veroorzaken. In dit geval kun je eenvoudigweg arbitrary_types_allowed toevoegen in de configuratie!

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

Controleren of een dataklasse een pydantische dataklasse is

Pydantic dataclasses worden nog steeds beschouwd als dataclasses, dus het gebruik van dataclasses.is_dataclass zal True retourneren. Om te controleren of een type specifiek een pydantische dataklasse is, kunt u gebruiken 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

Initialisatie haken

Wanneer u een dataklasse initialiseert, is het mogelijk om code voor of na validatie uit te voeren met behulp van de 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}})

De __post_init__ in Pydantic-dataklassen wordt midden in de validators aangeroepen. Hier is de volgorde:

  • model_validator(mode='before')
  • field_validator(mode='before')
  • field_validator(mode='after')
  • Innerlijke validatoren. bijvoorbeeld validatie voor typen als 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')

Verschil met stdlib-dataklassen

Houd er rekening mee dat de dataclasses.dataclass van Python stdlib alleen de methode __post_init__ implementeert, omdat er geen validatiestap wordt uitgevoerd.

JSON-dumping

Pydantic-dataklassen beschikken niet over een .model_dump_json() -functie. Om ze als JSON te dumpen, moet je als volgt gebruik maken van het 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
    ]
}
"""

本文总阅读量