I am new to dataclasses/Pydantic models and I am running into an issue elaborated below.
I have defined two pydantic models
Role
s and SubRole
s such that Role
model contains a set
of SubRole
s.
"""
Module contains all the models.
"""
# pylint: disable=too-few-public-methods
import typing
import pydantic
class RoleBaseClass(pydantic.BaseModel): # pylint: disable=no-member
"""
Base class for all the models.
"""
name: str = pydantic.Field(regex=r"^\w+$")
def __hash__(self: typing.Self) -> int:
return hash(self.name)
def __eq__(self, other) -> bool:
return self.name == other.name
class SubRole(RoleBaseClass):
"""
SubRole model.
"""
class Role(RoleBaseClass):
"""
Role model.
"""
subroles: set[SubRole] = pydantic.Field(default=set())
def __str__(self) -> str:
base_str: str = super().__str__()
cls_name: str = self.__class__.__name__
return f"{cls_name}({base_str})"
if __name__ == "__main__":
data0 = Role(
name="aws0",
)
data1 = Role(
name="aws1",
subroles=set(
[
SubRole(name="aws1sub1"),
SubRole(name="aws1sub2"),
]
),
)
data2 = Role(
name="aws1",
subroles=set(
[
SubRole(name="aws2sub1"),
SubRole(name="aws2sub2"),
]
),
)
print(data0)
print(data1.subroles)
print(data1 == data2)
# Below line fails with TypeError: unhashable type: 'dict'
print(data2.dict())
everything works apart from calling Role().dict()
method which fails with TypeError: unhashable type: 'dict'
error, traceback
below
Traceback (most recent call last):
File "models.py", line 72, in <module>
print(data2.dict())
^^^^^^^^^^^^
File "pydantic\main.py", line 449, in pydantic.main.BaseModel.dict
File "pydantic\main.py", line 868, in _iter
File "pydantic\main.py", line 794, in pydantic.main.BaseModel._get_value
TypeError: unhashable type: 'dict'
Could anyone suggest the mistake I made, also any further improvement to the code.
Cheers, DD.
In your case, the set()
object is not hashable. Here are 3 solutions of various elegance:
You can enforce deduplication via a validator if needed.
subroles: list[SubRole]
models_as_dict=False
when serializing to json
:Note: this will only work to json, see next solution for serializing to a dict...
print(data2.json(models_as_dict=False))
dict()
, use the self._iter()
method, as follows:Note:
self._iter()
is wrapped by theself.dict()
method, so you are basically accessing the underlying functionality directly.
my_dict = dict(data2._iter(to_dict=False))
print(my_dict)
__getitem__()
This goes a bit beyond my scope of understanding, but this github conversation thread would be a good place to start: https://github.com/pydantic/pydantic/issues/380#issuecomment-459352718. (Note, this would probably be the best solution for you.)
default_factory=set
instead, since default=list|set
will create problems for you because of mutable default arguments:class Role(RoleBaseClass):
"""
Role model.
"""
subroles: list[SubRole] = pydantic.Field(default_factory=list)
Role.json(models_as_dict=False)
will give you a list of dictionary objects.{"name": "aws1", "subroles": [{"name": "aws2sub2"}, {"name": "aws2sub1"}]}
If you want a list of attributes, use a custom json_encoder
like so:
class Role(RoleBaseClass):
"""
Role model.
"""
subroles: typing.Set[SubRole] = pydantic.Field(default_factory=set)
class Config:
json_encoders = {
SubRole: lambda x: x.name,
}
This will give you a list of names:
{"name": "aws1", "subroles": ["aws2sub1", "aws2sub2"]}