Zum Inhalt

??? API „API-Dokumentation“ pydantic.dataclasses.dataclass

Wenn Sie BaseModel von Pydantic nicht verwenden möchten, können Sie stattdessen die gleiche Datenvalidierung für Standarddatenklassen erhalten (eingeführt 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))
"""

!!! Hinweis Denken Sie daran pydantic.dataclasses.dataclass ist kein Ersatz für pydantic.BaseModel . pydantic.dataclasses.dataclass Bietet eine ähnliche Funktionalität wie dataclasses.dataclass mit zusätzlicher Pydantic-Validierung. Es gibt Fälle, in denen eine Unterklasse pydantic.BaseModel die bessere Wahl ist.

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

Zu den Unterschieden zwischen Pydantic-Datenklassen und BaseModel gehören:

Sie können alle standardmäßigen Pydantic-Feldtypen verwenden. Beachten Sie jedoch, dass an den Konstruktor übergebene Argumente kopiert werden, um eine Validierung und gegebenenfalls einen Zwang durchzuführen.

Um eine Validierung durchzuführen oder ein JSON-Schema für eine Pydantic-Datenklasse zu generieren, sollten Sie die Datenklasse jetzt mit einem TypeAdapter umschließen und seine Methoden nutzen.

Felder, die eine default_factory erfordern, können entweder durch ein pydantic.Field oder ein dataclasses.field angegeben werden.

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 Die Argumente von sind mit denen des Standarddekorators identisch, mit Ausnahme eines zusätzlichen Schlüsselwortarguments config , das dieselbe Bedeutung wie model_config hat.

!!! Warnung Nach v1.2 muss das Mypy-Plugin installiert werden, um pydantische Datenklassen auf den Typ zu überprüfen.

Weitere Informationen zum Kombinieren von Validatoren mit Datenklassen finden Sie unter Datenklassenvalidatoren .

Datenklassenkonfiguration

Wenn Sie die config wie bei einem BaseModel ändern möchten, haben Sie zwei Möglichkeiten:

  • Wenden Sie die Konfiguration als Diktat auf den Datenklassendekorator an
  • Verwenden Sie ConfigDict als Konfiguration

    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

  • Weitere Informationen zu validate_assignment finden Sie in der API-Referenz.

!!! Hinweis: Pydantic-Datenklassen unterstützen die Konfiguration extra, um zusätzliche Felder zu ignore , forbid oder allow , die an den Initialisierer übergeben werden. Allerdings kann ein gewisses Standardverhalten von stdlib-Datenklassen vorherrschen. Beispielsweise werden alle zusätzlichen Felder, die in einer Pydantic-Datenklasse mit extra='allow' vorhanden sind, weggelassen, wenn die Datenklasse print wird.

Verschachtelte Datenklassen

Verschachtelte Datenklassen werden sowohl in Datenklassen als auch in normalen Modellen unterstützt.

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

Bei der Verwendung als Felder sollten Datenklassen (Pydantic oder Vanilla) Diktate als Validierungseingaben verwenden.

Generische Datenklassen

Pydantic unterstützt generische Datenklassen, einschließlich solcher mit Typvariablen.

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'

Beachten Sie, dass Sie keinen TypeAdapter benötigen, wenn Sie die Datenklasse als Feld eines BaseModel oder über FastAPI verwenden.

Stdlib-Datenklassen und Pydantic-Datenklassen

Von stdlib-Datenklassen erben

Stdlib-Datenklassen (verschachtelt oder nicht) können ebenfalls geerbt werden und Pydantic validiert automatisch alle geerbten Felder.

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

Verwendung von stdlib-Datenklassen mit BaseModel

Beachten Sie, dass stdlib-Datenklassen (verschachtelt oder nicht) automatisch in Pydantic-Datenklassen konvertiert werden, wenn sie mit BaseModel gemischt werden! Darüber hinaus hat die generierte Pydantic-Datenklasse genau die gleiche Konfiguration ( order , frozen , ...) wie die ursprüngliche.

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'

Verwenden Sie benutzerdefinierte Typen

Da stdlib-Datenklassen automatisch konvertiert werden, um eine Validierung hinzuzufügen, kann die Verwendung benutzerdefinierter Typen zu unerwartetem Verhalten führen. In diesem Fall können Sie einfach arbitrary_types_allowed in der Konfiguration hinzufügen!

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

Überprüfen, ob eine Datenklasse eine pydantische Datenklasse ist

Pydantic-Datenklassen gelten immer noch als Datenklassen, daher wird die Verwendung von dataclasses.is_dataclass True zurückgeben. Um zu überprüfen, ob es sich bei einem Typ speziell um eine pydantische Datenklasse handelt, können Sie diese verwenden 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

Initialisierungs-Hooks

Wenn Sie eine Datenklasse initialisieren, ist es mit Hilfe des mode @model_validator möglich, Code vor oder nach der Validierung auszuführen.

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

Der __post_init__ in Pydantic-Datenklassen wird inmitten von Validatoren aufgerufen. Hier ist die Reihenfolge:

  • model_validator(mode='before')
  • field_validator(mode='before')
  • field_validator(mode='after')
  • Innere Validatoren. zB Validierung für Typen wie 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')

Unterschied zu stdlib-Datenklassen

Beachten Sie, dass dataclasses.dataclass von Python stdlib nur die Methode __post_init__ implementiert, da sie keinen Validierungsschritt ausführt.

JSON-Dumping

Pydantic-Datenklassen verfügen nicht über die Funktion .model_dump_json() . Um sie als JSON zu sichern, müssen Sie das RootModel wie folgt verwenden:

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

本文总阅读量