Skip to content

Commit ea4c311

Browse files
authored
Support generic parents in include_subclasses strategy (#650)
Resolves #648 The strategy was using parent_cls.__subclasses__() to get the list of subclasses. In case of generics, this method is unavailable. The fix applies sanitizing the cl and getting its origin class for getting the sublcasses tree. The class itself remains generic in the tree.
1 parent c95a0a5 commit ea4c311

File tree

3 files changed

+28
-1
lines changed

3 files changed

+28
-1
lines changed

HISTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ Our backwards-compatibility policy can be found [here](https://github.com/python
4646
([#599](https://github.com/python-attrs/cattrs/pull/599))
4747
- Structuring TypedDicts from invalid inputs now properly raises a {class}`ClassValidationError`.
4848
([#615](https://github.com/python-attrs/cattrs/issues/615) [#616](https://github.com/python-attrs/cattrs/pull/616))
49+
- {func} `cattrs.strategies.include_subclasses` now properly working with generic parent classes.
50+
([#649](https://github.com/python-attrs/cattrs/pull/650))
4951
- Replace `cattrs.gen.MappingStructureFn` with {class}`cattrs.SimpleStructureHook`.
5052
- Python 3.13 is now supported.
5153
([#543](https://github.com/python-attrs/cattrs/pull/543) [#547](https://github.com/python-attrs/cattrs/issues/547))

src/cattrs/strategies/_subclasses.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import typing
56
from gc import collect
67
from typing import Any, Callable, TypeVar, Union
78

@@ -11,8 +12,12 @@
1112

1213

1314
def _make_subclasses_tree(cl: type) -> list[type]:
15+
# get class origin for accessing subclasses (see #648 for more info)
16+
cls_origin = typing.get_origin(cl) or cl
1417
return [cl] + [
15-
sscl for scl in cl.__subclasses__() for sscl in _make_subclasses_tree(scl)
18+
sscl
19+
for scl in cls_origin.__subclasses__()
20+
for sscl in _make_subclasses_tree(scl)
1621
]
1722

1823

tests/strategies/test_include_subclasses.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from cattrs.errors import ClassValidationError, StructureHandlerNotFoundError
1010
from cattrs.strategies import configure_tagged_union, include_subclasses
1111

12+
T = typing.TypeVar("T")
13+
1214

1315
@define
1416
class Parent:
@@ -412,3 +414,21 @@ class NewChild2(NewParent):
412414

413415
with pytest.raises(StructureHandlerNotFoundError):
414416
include_subclasses(NewParent, genconverter)
417+
418+
419+
def test_parents_with_generics(genconverter: Converter):
420+
"""Ensure proper handling of generic parents #648."""
421+
422+
@define
423+
class GenericParent(typing.Generic[T]):
424+
p: T
425+
426+
@define
427+
class Child1G(GenericParent[str]):
428+
c: str
429+
430+
include_subclasses(GenericParent[str], genconverter)
431+
432+
assert genconverter.structure({"p": 5, "c": 5}, GenericParent[str]) == Child1G(
433+
"5", "5"
434+
)

0 commit comments

Comments
 (0)