Skip to content

AttributeError: no __parameters__ when structuring subclass of generic alias types from collections.abc/typing #654

@anthrotype

Description

@anthrotype

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions