Aller au contenu

??? API "Documentation API" pydantic.dataclasses.dataclass

Si vous ne souhaitez pas utiliser BaseModel de Pydantic, vous pouvez obtenir la même validation de données sur les classes de données standard (introduites dans 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))
"""

!!! remarque Gardez à l'esprit que pydantic.dataclasses.dataclass ne remplace pas pydantic.BaseModel . pydantic.dataclasses.dataclass fournit une fonctionnalité similaire à dataclasses.dataclass avec l'ajout de la validation Pydantic. Il existe des cas où le sous-classement pydantic.BaseModel est le meilleur choix.

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

Certaines différences entre les classes de données Pydantic et BaseModel incluent:

Vous pouvez utiliser tous les types de champs Pydantic standard. Notez cependant que les arguments passés au constructeur seront copiés afin d'effectuer la validation et, si nécessaire, la coercition.

Pour effectuer une validation ou générer un schéma JSON sur une classe de données Pydantic, vous devez maintenant envelopper la classe de données avec un TypeAdapter et utiliser ses méthodes.

Les champs qui nécessitent un default_factory peuvent être spécifiés par un pydantic.Field ou un 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 Les arguments de sont les mêmes que ceux du décorateur standard, à l'exception d'une config d'argument de mot-clé supplémentaire qui a la même signification que model_config.

!!! avertissement Après la v1.2, le plugin Mypy doit être installé pour taper check pydantic dataclasses.

Pour plus d'informations sur la combinaison de validateurs avec des classes de données, consultez validateurs de classes de données .

Configuration de la classe de données

Si vous souhaitez modifier la config comme vous le feriez avec un BaseModel , vous avez deux options:

  • Appliquer la configuration au décorateur de classe de données en tant que dict
  • Utilisez ConfigDict comme configuration

    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

  • Vous pouvez en savoir plus sur validate_assignment dans Référence API.

!!! note Les classes de données Pydantic prennent en charge la configuration extra pour ignore , forbid ou allow les champs supplémentaires transmis à l'initialiseur. Cependant, certains comportements par défaut des classes de données stdlib peuvent prévaloir. Par exemple, tous les champs supplémentaires présents sur une classe de données Pydantic utilisant extra='allow' sont omis lors de print de la classe de données.

Classes de données imbriquées

Les classes de données imbriquées sont prises en charge à la fois dans les classes de données et dans les modèles normaux.

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

Lorsqu'elles sont utilisées comme champs, les classes de données (Pydantic ou vanilla) doivent utiliser des dictionnaires comme entrées de validation.

Classes de données génériques

Pydantic prend en charge les classes de données génériques, y compris celles avec des variables de type.

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'

Notez que si vous utilisez la classe de données comme champ d'un BaseModel ou via FastAPI, vous n'avez pas besoin d'un TypeAdapter .

Classes de données Stdlib et classes de données Pydantic

Hériter des classes de données stdlib

Les classes de données Stdlib (imbriquées ou non) peuvent également être héritées et Pydantic validera automatiquement tous les champs hérités.

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

Utilisation des classes de données stdlib avec BaseModel

Gardez à l'esprit que les classes de données stdlib (imbriquées ou non) sont automatiquement converties en classes de données Pydantic lorsqu'elles sont mélangées avec BaseModel ! De plus, la classe de données Pydantic générée aura exactement la même configuration ( order , frozen , ...) que celle d'origine.

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'

Utiliser des types personnalisés

Étant donné que les classes de données stdlib sont automatiquement converties pour ajouter une validation, l'utilisation de types personnalisés peut entraîner un comportement inattendu. Dans ce cas, vous pouvez simplement ajouter arbitrary_types_allowed dans la configuration!

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

Vérifier si une classe de données est une classe de données pydantique

Les classes de données pydantiques sont toujours considérées comme des classes de données, donc l'utilisation de dataclasses.is_dataclass renverra True . Pour vérifier si un type est spécifiquement une classe de données pydantique, vous pouvez utiliser 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

Crochets d'initialisation

Lorsque vous initialisez une dataclass, il est possible d'exécuter du code avant ou après validation à l'aide du paramètre mode décorateur @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}})

Le __post_init__ dans les classes de données Pydantic est appelé au milieu des validateurs. Voici la commande :

  • model_validator(mode='before')
  • field_validator(mode='before')
  • field_validator(mode='after')
  • Validateurs internes. par exemple, validation pour des types comme 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')

Différence avec les classes de données stdlib

Notez que le dataclasses.dataclass de Python stdlib implémente uniquement la méthode __post_init__ puisqu'il n'exécute pas d'étape de validation.

Dump JSON

Les classes de données Pydantic ne comportent pas de fonction .model_dump_json() . Pour les vider au format JSON, vous devrez utiliser le RootModel comme suit:

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

本文总阅读量