mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
fix: pydantic dataclass can inherit from stdlib dataclass and arbitrary_types_allowed is supported (#2051)
* fix: pydantic dataclasses can inherit from stdlib dataclasses closes #2042 * docs: add some documentation * fix: support arbitrary_types_allowed with stdlib dataclass closes #2054 * docs: add documentation for custom types
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
fix: _pydantic_ `dataclass` can inherit from stdlib `dataclass`
|
||||
and `Config.arbitrary_types_allowed` is supported
|
||||
@@ -0,0 +1,42 @@
|
||||
import dataclasses
|
||||
|
||||
import pydantic
|
||||
|
||||
|
||||
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(pydantic.BaseModel):
|
||||
dc: DC
|
||||
other: str
|
||||
|
||||
Model(dc=my_dc, other='other')
|
||||
except RuntimeError as e: # invalid as it is now a pydantic dataclass
|
||||
print(e)
|
||||
|
||||
|
||||
class Model(pydantic.BaseModel):
|
||||
dc: DC
|
||||
other: str
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
m = Model(dc=my_dc, other='other')
|
||||
print(repr(m))
|
||||
@@ -0,0 +1,27 @@
|
||||
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)
|
||||
|
||||
try:
|
||||
X(z='pika')
|
||||
except pydantic.ValidationError as e:
|
||||
print(e)
|
||||
@@ -49,6 +49,8 @@ Dataclasses attributes can be populated by tuples, dictionaries or instances of
|
||||
|
||||
## Stdlib dataclasses and _pydantic_ dataclasses
|
||||
|
||||
### Convert stdlib dataclasses into _pydantic_ dataclasses
|
||||
|
||||
Stdlib dataclasses (nested or not) can be easily converted into _pydantic_ dataclasses by just decorating
|
||||
them with `pydantic.dataclasses.dataclass`.
|
||||
|
||||
@@ -57,6 +59,18 @@ them with `pydantic.dataclasses.dataclass`.
|
||||
```
|
||||
_(This script is complete, it should run "as is")_
|
||||
|
||||
### Inherit from stdlib dataclasses
|
||||
|
||||
Stdlib dataclasses (nested or not) can also be inherited and _pydantic_ will automatically validate
|
||||
all the inherited fields.
|
||||
|
||||
```py
|
||||
{!.tmp_examples/dataclasses_stdlib_inheritance.py!}
|
||||
```
|
||||
_(This script is complete, it should run "as is")_
|
||||
|
||||
### Use of stdlib dataclasses with `BaseModel`
|
||||
|
||||
Bear in mind that stdlib dataclasses (nested or not) are **automatically converted** into _pydantic_ dataclasses
|
||||
when mixed with `BaseModel`!
|
||||
|
||||
@@ -65,6 +79,17 @@ when mixed with `BaseModel`!
|
||||
```
|
||||
_(This script is complete, it should run "as is")_
|
||||
|
||||
### Use custom types
|
||||
|
||||
Since stdlib dataclasses are automatically converted to add validation using
|
||||
custom types may cause some unexpected behaviour.
|
||||
In this case you can simply add `arbitrary_types_allowed` in the config!
|
||||
|
||||
```py
|
||||
{!.tmp_examples/dataclasses_arbitrary_types_allowed.py!}
|
||||
```
|
||||
_(This script is complete, it should run "as is")_
|
||||
|
||||
## Initialize hooks
|
||||
|
||||
When you initialize a dataclass, it is possible to execute code *after* validation
|
||||
|
||||
@@ -8,7 +8,7 @@ from .main import create_model, validate_model
|
||||
from .utils import ClassAttribute
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .main import BaseModel # noqa: F401
|
||||
from .main import BaseConfig, BaseModel # noqa: F401
|
||||
from .typing import CallableGenerator
|
||||
|
||||
DataclassT = TypeVar('DataclassT', bound='Dataclass')
|
||||
@@ -120,7 +120,9 @@ def _process_class(
|
||||
# ```
|
||||
# with the exact same fields as the base dataclass
|
||||
if is_builtin_dataclass(_cls):
|
||||
_cls = type(_cls.__name__, (_cls,), {'__post_init__': _pydantic_post_init})
|
||||
_cls = type(
|
||||
_cls.__name__, (_cls,), {'__annotations__': _cls.__annotations__, '__post_init__': _pydantic_post_init}
|
||||
)
|
||||
else:
|
||||
_cls.__post_init__ = _pydantic_post_init
|
||||
cls: Type['Dataclass'] = dataclasses.dataclass( # type: ignore
|
||||
@@ -214,10 +216,10 @@ def dataclass(
|
||||
return wrap(_cls)
|
||||
|
||||
|
||||
def make_dataclass_validator(_cls: Type[Any], **kwargs: Any) -> 'CallableGenerator':
|
||||
def make_dataclass_validator(_cls: Type[Any], config: Type['BaseConfig']) -> 'CallableGenerator':
|
||||
"""
|
||||
Create a pydantic.dataclass from a builtin dataclass to add type validation
|
||||
and yield the validators
|
||||
"""
|
||||
cls = dataclass(_cls, **kwargs)
|
||||
cls = dataclass(_cls, config=config)
|
||||
yield from _get_validators(cls)
|
||||
|
||||
@@ -593,7 +593,7 @@ def find_validators( # noqa: C901 (ignore complexity)
|
||||
yield make_literal_validator(type_)
|
||||
return
|
||||
if is_builtin_dataclass(type_):
|
||||
yield from make_dataclass_validator(type_)
|
||||
yield from make_dataclass_validator(type_, config)
|
||||
return
|
||||
if type_ is Enum:
|
||||
yield enum_validator
|
||||
|
||||
@@ -2,7 +2,7 @@ import dataclasses
|
||||
from collections.abc import Hashable
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import ClassVar, Dict, FrozenSet, Optional
|
||||
from typing import ClassVar, Dict, FrozenSet, List, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -738,3 +738,42 @@ def test_override_builtin_dataclass_nested_schema():
|
||||
'title': 'File',
|
||||
'type': 'object',
|
||||
}
|
||||
|
||||
|
||||
def test_inherit_builtin_dataclass():
|
||||
@dataclasses.dataclass
|
||||
class Z:
|
||||
z: int
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Y(Z):
|
||||
y: int
|
||||
|
||||
@pydantic.dataclasses.dataclass
|
||||
class X(Y):
|
||||
x: int
|
||||
|
||||
pika = X(x='2', y='4', z='3')
|
||||
assert pika.x == 2
|
||||
assert pika.y == 4
|
||||
assert pika.z == 3
|
||||
|
||||
|
||||
def test_dataclass_arbitrary():
|
||||
class ArbitraryType:
|
||||
def __init__(self):
|
||||
...
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Test:
|
||||
foo: ArbitraryType
|
||||
bar: List[ArbitraryType]
|
||||
|
||||
class TestModel(BaseModel):
|
||||
a: ArbitraryType
|
||||
b: Test
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
TestModel(a=ArbitraryType(), b=(ArbitraryType(), [ArbitraryType()]))
|
||||
|
||||
Reference in New Issue
Block a user