Dataclasses
If you don't want to use pydantic's BaseModel
you can instead get the same data validation on standard
dataclasses (introduced in python 3.7).
Dataclasses work in python 3.6 using the dataclasses backport package.
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))
(This script is complete, it should run "as is")
Note
Keep in mind that pydantic.dataclasses.dataclass
is a drop-in replacement for dataclasses.dataclass
with validation, not a replacement for pydantic.BaseModel
(with a small difference in how initialization hooks work). There are cases where subclassing
pydantic.BaseModel
is the better choice.
For more information and discussion see samuelcolvin/pydantic#710.
You can use all the standard pydantic field types, and the resulting dataclass will be identical to the one
created by the standard library dataclass
decorator.
The underlying model and its schema can be accessed through __pydantic_model__
.
Also, fields that require a default_factory
can be specified by a dataclasses.field
.
import dataclasses from typing import List 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(user.__pydantic_model__.schema()) """ { 'title': 'User', 'type': 'object', 'properties': { 'id': {'title': 'Id', 'type': 'integer'}, 'name': { 'title': 'Name', 'default': 'John Doe', 'type': 'string', }, 'friends': { 'title': 'Friends', 'default': [0], 'type': 'array', 'items': {'type': 'integer'}, }, }, 'required': ['id'], } """
(This script is complete, it should run "as is")
pydantic.dataclasses.dataclass
's arguments are the same as the standard decorator, except one extra
keyword argument config
which has the same meaning as Config.
Warning
After v1.2, The Mypy plugin must be installed to type check pydantic dataclasses.
For more information about combining validators with dataclasses, see dataclass validators.
Nested dataclasses🔗
Nested dataclasses are supported both in dataclasses and normal models.
from pydantic import AnyUrl from pydantic.dataclasses import dataclass @dataclass class NavbarButton: href: AnyUrl @dataclass class Navbar: button: NavbarButton navbar = Navbar(button=('https://example.com',)) print(navbar) #> Navbar(button=NavbarButton(href=AnyUrl('https://example.com', scheme='https', #> host='example.com', tld='com', host_type='domain')))
(This script is complete, it should run "as is")
Dataclasses attributes can be populated by tuples, dictionaries or instances of the dataclass itself.
Initialize hooks🔗
When you initialize a dataclass, it is possible to execute code after validation
with the help of __post_init_post_parse__
. This is not the same as __post_init__
, which executes
code before validation.
from datetime import datetime from pydantic.dataclasses import dataclass @dataclass class Birth: year: int month: int day: int @dataclass class User: birth: Birth def __post_init__(self): print(self.birth) #> {'year': 1995, 'month': 3, 'day': 2} def __post_init_post_parse__(self): print(self.birth) #> Birth(year=1995, month=3, day=2) user = User(**{'birth': {'year': 1995, 'month': 3, 'day': 2}})
(This script is complete, it should run "as is")
Since version v1.0, any fields annotated with dataclasses.InitVar
are passed to both __post_init__
and
__post_init_post_parse__
.
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='world', base_path='/hello' def __post_init_post_parse__(self, base_path): 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')
(This script is complete, it should run "as is")
Difference with stdlib dataclasses🔗
Note that the dataclasses.dataclass
from python stdlib implements only the __post_init__
method since it doesn't run a validation step.
When substituting usage of dataclasses.dataclass
with pydantic.dataclasses.dataclass
, it is recommended to move the code executed in the __post_init__
method to the __post_init_post_parse__
method, and only leave behind part of code which needs to be executed before validation.