Description
- cattrs version: 1.10.0
- Python version: 3.7
- Operating System: Linux 5.16.1-1 OpenSUSE Thumbleweed
There is an exception AttributeError: type object 'CLASS' has no attribute '__parameters__'
when you try to structure an attrs Class which inherts typing.CLASS
Reproduce
Have a look at this example where CLASS
is Hashable
:
import attr
import typing
import cattrs
import collections
converter = cattrs.GenConverter()
@attr.define
class A(collections.abc.Hashable): # typing.Hashable should be preferred over collections.abc.Hashable
attr1: str = ''
attr2: int = 0
def __hash__(self) -> int:
return hash(self.attr2)
@attr.define
class B(typing.Hashable):
attr1: str = ''
attr2: int = 0
def __hash__(self) -> int:
return hash(self.attr2)
def main() -> None:
converter.structure(dict(attr1='hello', attr2=42), A) # OK
converter.structure(dict(attr1='hello', attr2=42), B) # AttributeError: type object 'Hashable' has no attribute '__parameters__'
if __name__ == '__main__':
main()
Output
Traceback (most recent call last):
File "/home/raabf/sl/demo.py", line 40, in <module>
main()
File "/home/raabf/sl/demo.py", line 37, in main
converter.structure(dict(attr1='hello', attr2=42), B) # AttributeError: type object 'Hashable' has no attribute '__parameters__'
File "/home/raabf/.pyenv/versions/sl37/lib/python3.7/site-packages/cattr/converters.py", line 300, in structure
return self._structure_func.dispatch(cl)(obj, cl)
File "/home/raabf/.pyenv/versions/sl37/lib/python3.7/site-packages/cattr/dispatch.py", line 49, in _dispatch
return self._function_dispatch.dispatch(cl)
File "/home/raabf/.pyenv/versions/sl37/lib/python3.7/site-packages/cattr/dispatch.py", line 126, in dispatch
return handler(typ)
File "/home/raabf/.pyenv/versions/sl37/lib/python3.7/site-packages/cattr/converters.py", line 745, in gen_structure_attrs_fromdict
**attrib_overrides,
File "/home/raabf/.pyenv/versions/sl37/lib/python3.7/site-packages/cattr/gen.py", line 224, in make_dict_structure_fn
mapping = _generate_mapping(base, mapping)
File "/home/raabf/.pyenv/versions/sl37/lib/python3.7/site-packages/cattr/gen.py", line 195, in _generate_mapping
for p, t in zip(get_origin(cl).__parameters__, get_args(cl)):
AttributeError: type object 'Hashable' has no attribute '__parameters__'
Note that in the above code snippet I use typing.Hashable
just as an example, the same exception occurs if you inherit something other from typing.*
, such as typing.Reversable
or typing.Iterable
.
Offending Part
The offending part in cattrs is:
https://github.com/python-attrs/cattrs/blob/v1.10.0/src/cattr/gen.py#L206-L225
def make_dict_structure_fn(
cl: Type[T],
converter: "Converter",
_cattrs_forbid_extra_keys: bool = False,
_cattrs_use_linecache: bool = True,
_cattrs_prefer_attrib_converters: bool = False,
**kwargs,
) -> Callable[[Mapping[str, Any]], T]:
"""Generate a specialized dict structuring function for an attrs class."""
mapping = {}
if is_generic(cl):
base = get_origin(cl)
mapping = _generate_mapping(cl, mapping)
cl = base
for base in getattr(cl, "__orig_bases__", ()):
if is_generic(base) and not str(base).startswith("typing.Generic"):
mapping = _generate_mapping(base, mapping)
break
which again calls
def _generate_mapping(
cl: Type, old_mapping: Dict[str, type]
) -> Dict[str, type]:
mapping = {}
for p, t in zip(get_origin(cl).__parameters__, get_args(cl)):
if isinstance(t, TypeVar):
continue
mapping[p.__name__] = t
if not mapping:
return old_mapping
return mapping
where __parameters__
is accessed. During the exception cl
is typing.Hashable
, which resolves to collections.abc.Hashable
by get_origin(cl)
.
Reason
The use of typing.Hashable
is correct. As far as I understand, during static analyisis from tools such as mypy
it acts as type checking, during runtime it is resolved to collections.abc.Hashable
where the class is implemented.
I personally rely on inheriting from typing.Hashable
instead of collections.abc.Hashable
, or else mypy would do wrong type checking.
I do not understand what cattrs is doing, so I just can make some guesses:
- The resolving from
typing.Hashable
tocollections.abc.Hashable
at runtime (i.e.typing.Hashable
hascollections.abc.Hashable
as its origin) confuses cattrs. - Some check before fails and
_generate_mapping
should have been never called withtyping.Hashable
- In general you cannot assume that all origins have the
__parameters__
attribute, i.e. usegetattr(get_origin(cl), '__parameters__', ())
instead.
Reference for getting parameters
The library typing_inspect
has an get_parameters
function to get __parameters__
. The function is a bit longer and handles some corner cases , I do not know if this helps with problem reported in this issue here or can be utilized here.