Skip to content

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

@anthrotype

Description

@anthrotype
Contributor

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

      Participants

      @Tinche@anthrotype

      Issue actions

        AttributeError: no __parameters__ when structuring subclass of generic alias types from collections.abc/typing · Issue #654 · python-attrs/cattrs