konradhalas / dacite

Simple creation of data classes from dictionaries.
MIT License
1.72k stars 107 forks source link

Python 3.9 extract_generic fails #118

Closed acepace closed 3 years ago

acepace commented 3 years ago

I seem to have failures when using latest dacite with an object of type Dict.

key_cls, item_cls = extract_generic(target_type)

if target_type is List or Dict I get a zero length tuple rather than the expected two values.

acepace commented 3 years ago

This seems to have been reported in the RedHat bug tracker https://bugzilla.redhat.com/show_bug.cgi?id=1838327

I've tried the latest version 1.5.1 and this still happens in my machine

zcutlip commented 3 years ago

just ran into this bug as well:

>>> from dacite import from_dict
>>> from dataclasses import dataclass, field
>>> from typing import Dict
>>>
>>> @dataclass
... class MyDC:
...     name: str
...     stuff: Dict = field(default_factory=dict)
...
...
>>> stuffdict = {"one": 1, "two": 2}
>>> mydict = {"name": "myname", "stuff": stuffdict}
>>> from_dict(MyDC, mydict)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    from_dict(MyDC, mydict)
  File "/Users/zach/.virtualenvs/dacite_test/lib/python3.9/site-packages/dacite/core.py", line 57, in from_dict
    transformed_value = transform_value(
  File "/Users/zach/.virtualenvs/dacite_test/lib/python3.9/site-packages/dacite/types.py", line 28, in transform_value
    key_cls, item_cls = extract_generic(target_type)
ValueError: not enough values to unpack (expected 2, got 0)
>>>
zcutlip commented 3 years ago

As a workaround, it appears if you type-hint the contents of the collection, even using typing.Any, it works:

>>> from typing import Any
>>> @dataclass
... class MyDC:
...     name: str
...     stuff: Dict[Any, Any] = field(default_factory=dict)
...
...
>>> from_dict(MyDC, mydict)
MyDC(name='myname', stuff={'one': 1, 'two': 2})
zcutlip commented 3 years ago

I think a patch should be pretty straightforward; I'll see if I can do it and submit a PR.

zcutlip commented 3 years ago

I do think though, that if you have a collection of mixed & matched dataclass types, there may be no solution, because for those, dacite needs to know the classes explicitly in order to instantiate them

So if we had:

@dataclass
class MyDC:
    stuff: Dict[Any, Any]

@dataclass
class MyOtherDC:
    pass

@dataclass
class MyOtherOtherDC:
    pass

my_dataclass_thing_1 = MyOtherDC()
my_dataclass_thing_2 = MyOtherOtherDC()

And stuff looked like:

{
    "my_other_dc": my_dataclass_thing_1,
    "my_other_other_dc": my_dataclass_thing_2
}

Then dacite won't be able to work out from the (Any, Any) type tuples what object types to instantiate.

So a collection of mixed types just isn't possible if any of the types are dataclasses, if I'm not mistaken.

konradhalas commented 3 years ago

Thank you @acepace for reporting this issue. It should be fixed in master.

@zcutlip thank you for the simple and clean solution.

zcutlip commented 3 years ago

Thanks for the quick patch. I'm glad I procrastinated and didn't send you a PR because you caught some changes I would have missed.