??? 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:
- Wie Initialisierungs-Hooks funktionieren
- JSON-Dumping
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 Konfigurationfrom 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
]
}
"""
本文总阅读量次