-
-
Notifications
You must be signed in to change notification settings - Fork 120
Description
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:
cattrs/tests/test_converter_inheritance.py
Lines 44 to 88 in 0bb472a
@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
Activity
[test_converter_inheritance] test inheriting from generic collections…
Tinche commentedon Jun 4, 2025
Oh interesting. I probably removed the code since I couldn't figure out how to test it. Here's a way ;)
Fix structuring of attrs class that inherit from typing/collections.a…