From 6835ea361325a205c0af69acabc66ca5193156c5 Mon Sep 17 00:00:00 2001 From: Hassan Kibirige Date: Tue, 12 Mar 2024 15:04:41 +0300 Subject: [PATCH] fix: Don't turn items annotated as InitVar into dataclass members PR-252: https://github.com/mkdocstrings/griffe/pull/252 --- src/griffe/extensions/dataclasses.py | 9 ++++++++ tests/test_dataclasses.py | 33 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/griffe/extensions/dataclasses.py b/src/griffe/extensions/dataclasses.py index b679def1..fd51e451 100644 --- a/src/griffe/extensions/dataclasses.py +++ b/src/griffe/extensions/dataclasses.py @@ -176,6 +176,14 @@ def _set_dataclass_init(class_: Class) -> None: class_.set_member("__init__", init) +def _del_members_annotated_as_initvar(class_: Class) -> None: + # Definitions annotated as InitVar are not class members + attributes = [member for member in class_.members.values() if isinstance(member, Attribute)] + for attribute in attributes: + if isinstance(attribute.annotation, Expr) and attribute.annotation.canonical_path == "dataclasses.InitVar": + class_.del_member(attribute.name) + + def _apply_recursively(mod_cls: Module | Class, processed: set[str]) -> None: if mod_cls.canonical_path in processed: return @@ -183,6 +191,7 @@ def _apply_recursively(mod_cls: Module | Class, processed: set[str]) -> None: if isinstance(mod_cls, Class): if "__init__" not in mod_cls.members: _set_dataclass_init(mod_cls) + _del_members_annotated_as_initvar(mod_cls) for member in mod_cls.members.values(): if not member.is_alias and member.is_class: _apply_recursively(member, processed) # type: ignore[arg-type] diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py index ddc80966..71d742fd 100644 --- a/tests/test_dataclasses.py +++ b/tests/test_dataclasses.py @@ -276,3 +276,36 @@ class Reordered(Base): assert [p.name for p in params_base] == ["self", "a", "b"] assert [p.name for p in params_reordered] == ["self", "b", "c", "a"] assert str(params_reordered["b"].annotation) == "float" + + +def test_parameters_annotated_as_initvar() -> None: + """Don't return InitVar annotated fields as class members. + + But if __init__ is defined, InitVar has no effect. + """ + code = """ + from dataclasses import dataclass, InitVar + + @dataclass + class PointA: + x: float + y: float + z: InitVar[float] + + @dataclass + class PointB: + x: float + y: float + z: InitVar[float] + + def __init__(self, r: float): ... + """ + + with temporary_visited_package("package", {"__init__.py": code}) as module: + point_a = module["PointA"] + assert ["self", "x", "y", "z"] == [p.name for p in point_a.parameters] + assert ["x", "y", "__init__"] == list(point_a.members) + + point_b = module["PointB"] + assert ["self", "r"] == [p.name for p in point_b.parameters] + assert ["x", "y", "z", "__init__"] == list(point_b.members)