I am trying to use pydantic frozen dataclasses to create stable and validated data structures. In the process, it seemed like my code would benefit from using inheritance because a lot of methods were shared between multiple objects.
My question is the following, is there a way to use inheritance to define a parent class that has default fields, while the child class includes non-default ones. Using dataclass(frozen=True)
is important to ensure easy serialisation.
The example is the following, I’m creating a parent class A
and a child B
. A
has both default and non-default arguments. The main interest here is having the field A.y
that is initialised by the validator using other fields that already passed validation. B
requires initialisation of non-default fields.
Note that there are in reality multiple child classes that also share methods with A
, that part not causing any issue with pydantic.
from pydantic import field_validator, Field, ValidationInfo
from pydantic.dataclasses import dataclass
@dataclass(frozen=True)
class A:
x: int
y: int = Field(default=0, init=False, validate_default=True, repr=False)
@field_validator("x")
def validate_x(cls, x):
if x < 0:
raise ValueError("x must be non-negative")
return x
@field_validator("y", mode="after")
def validate_y(cls, y, info=ValidationInfo):
y = info.data["x"] + 1
return y
@dataclass(frozen=True)
class B(A):
name: str
z: int = Field(default=0, init=False, validate_default=True, repr=False)
@field_validator("z")
def validate_z(cls, z, info=ValidationInfo):
z = info.data["x"] + info.data["y"]
return z
b = B(x=1, name="name")
print(b)
This returns the following error,
Traceback (most recent call last):
File "/Users/.../new_file.py", line 21, in <module>
class B(A):
File "/opt/miniconda3/envs/qcd/lib/python3.10/site-packages/pydantic/dataclasses.py", line 225, in create_dataclass
cls = dataclasses.dataclass( # type: ignore[call-overload]
File "/opt/miniconda3/envs/qcd/lib/python3.10/dataclasses.py", line 1184, in dataclass
return wrap(cls)
File "/opt/miniconda3/envs/qcd/lib/python3.10/dataclasses.py", line 1175, in wrap
return _process_class(cls, init, repr, eq, order, unsafe_hash,
File "/opt/miniconda3/envs/qcd/lib/python3.10/dataclasses.py", line 1024, in _process_class
_init_fn(all_init_fields,
File "/opt/miniconda3/envs/qcd/lib/python3.10/dataclasses.py", line 544, in _init_fn
raise TypeError(f'non-default argument {f.name!r} '
TypeError: non-default argument 'name' follows default argument
which I guess is a default python error raised because pydantic automatic __init__
puts arguments in order and a default argument is written before a non-default one.
My main issue until now is that I can’t assign new fields after validation, and hopefully I can also avoid writing validators for each child.
Is there a way to circumvent this issue?