??? 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:
- Comment fonctionnent les hooks d'initialisation
- Dump JSON
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 configurationfrom 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
]
}
"""
本文总阅读量次