Hello,
since the latest cattrs v25.1.0, I am experiencing an AttributeError: type object CLASS has no attribute '__parameters__' when trying to structure attrs classes that are subclasses of one of collections.abc or typing generic alias types (e.g. Mapping[str, int])
For example:
from collections.abc import Mapping
from attrs import define, fields
from cattrs import Converter
from cattrs.gen import make_dict_structure_fn
@define
class Point(Mapping[str, int]):
x: int = 0
y: int = 0
def __getitem__(self, key):
try:
return getattr(self, key)
except AttributeError:
raise KeyError(key) from None
def __iter__(self):
return (f.name for f in fields(self.__class__))
def __len__(self):
return len(fields(self.__class__))
converter = Converter()
converter.register_structure_hook(
Point,
make_dict_structure_fn(Point, converter, _cattrs_forbid_extra_keys=True),
)
converter.structure({"x": 2, "y": 3}, Point)
I get the following error:
Traceback (most recent call last):
File "<python-input-3>", line 23, in <module>
make_dict_structure_fn(Point, converter, _cattrs_forbid_extra_keys=True),
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/clupo/oss/cattrs/src/cattrs/gen/__init__.py", line 728, in make_dict_structure_fn
mapping = generate_mapping(base, mapping)
File "/Users/clupo/oss/cattrs/src/cattrs/gen/_generics.py", line 39, in generate_mapping
parameters = origin.__parameters__
^^^^^^^^^^^^^^^^^^^^^
AttributeError: type object 'Mapping' has no attribute '__parameters__
If instead of using a generic base class, I define my example Point class above simply as class Point(Mapping) without subscripting Mapping[str, int] then the cattrs structuring works.
The error did not occur with the previous cattrs release (v24.1.3) so it looks like a regression.
Note this is exactly the same error as the one identified in this old issue:
#217
That issue was fixed with this PR #221
However it looks like the current code in cattrs.gen._generics.generate_mapping no longer handles the absence of __parameters__ attribute which is not present in classes from collections.abc (generic alias types).
You do have a test that confirms structuring attrs-classes that in turn subclass from typing/collections.abc classes works, see:
|
@pytest.mark.parametrize("typing_cls", [Hashable, Iterable, Reversible]) |
|
def test_inherit_typing(converter: BaseConverter, typing_cls): |
|
"""Stuff from typing.* resolves to runtime to collections.abc.*. |
|
|
|
Hence, typing.* are of a special alias type which we want to check if |
|
cattrs handles them correctly. |
|
""" |
|
|
|
@define |
|
class A(typing_cls): # pragma: nocover |
|
i: int = 0 |
|
|
|
def __hash__(self): |
|
return hash(self.i) |
|
|
|
def __iter__(self): |
|
return iter([self.i]) |
|
|
|
def __reversed__(self): |
|
return iter([self.i]) |
|
|
|
assert converter.structure({"i": 1}, A) == A(i=1) |
|
|
|
|
|
@pytest.mark.parametrize( |
|
"collections_abc_cls", |
|
[collections.abc.Hashable, collections.abc.Iterable, collections.abc.Reversible], |
|
) |
|
def test_inherit_collections_abc(converter: BaseConverter, collections_abc_cls): |
|
"""As extension of test_inherit_typing, check if collections.abc.* work.""" |
|
|
|
@define |
|
class A(collections_abc_cls): # pragma: nocover |
|
i: int = 0 |
|
|
|
def __hash__(self): |
|
return hash(self.i) |
|
|
|
def __iter__(self): |
|
return iter([self.i]) |
|
|
|
def __reversed__(self): |
|
return iter([self.i]) |
|
|
|
assert converter.structure({"i": 1}, A) == A(i=1) |
However in those tests you are only testing with non-generic types.
If I modify the test case by adding e.g. Iterable[int] to the parametrized typing_cls, then the test fails with the same AttributeError: type object 'Iterable' has no attribute '__parameters__'.
I bisected the regression in this specific commit that was meant to "drop dead code", but it turns out this wasn't really "dead":
2fe721e
Hello,
since the latest cattrs v25.1.0, I am experiencing an
AttributeError: type object CLASS has no attribute '__parameters__'when trying to structure attrs classes that are subclasses of one ofcollections.abcortypinggeneric alias types (e.g.Mapping[str, int])For example:
I get the following error:
If instead of using a generic base class, I define my example
Pointclass above simply asclass Point(Mapping)without subscriptingMapping[str, int]then the cattrs structuring works.The error did not occur with the previous cattrs release (v24.1.3) so it looks like a regression.
Note this is exactly the same error as the one identified in this old issue:
#217
That issue was fixed with this PR #221
However it looks like the current code in
cattrs.gen._generics.generate_mappingno longer handles the absence of__parameters__attribute which is not present in classes from collections.abc (generic alias types).You do have a test that confirms structuring attrs-classes that in turn subclass from typing/collections.abc classes works, see:
cattrs/tests/test_converter_inheritance.py
Lines 44 to 88 in 0bb472a
However in those tests you are only testing with non-generic types.
If I modify the test case by adding e.g.
Iterable[int]to the parametrizedtyping_cls, then the test fails with the sameAttributeError: type object 'Iterable' has no attribute '__parameters__'.I bisected the regression in this specific commit that was meant to "drop dead code", but it turns out this wasn't really "dead":
2fe721e