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. 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.

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.

Note

As a side effect of getting pydantic dataclasses to play nicely with mypy, the config argument will show as invalid in IDEs and mypy. Use @dataclass(..., config=Config) # type: ignore as a workaround.

See python/mypy#6239 for an explanation of this issue.

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