Coverage for tests / test_inheritance.py: 100.00%
62 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-11 11:48 +0100
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-11 11:48 +0100
1"""Tests for class inheritance."""
3from __future__ import annotations
5from typing import TYPE_CHECKING
7import pytest
9from griffe import ModulesCollection, temporary_inspected_module, temporary_visited_module
11if TYPE_CHECKING:
12 from collections.abc import Callable
14 from griffe import Class
17def _mro_paths(cls: Class) -> list[str]:
18 return [base.path for base in cls.mro()]
21@pytest.mark.parametrize("agent1", [temporary_visited_module, temporary_inspected_module])
22@pytest.mark.parametrize("agent2", [temporary_visited_module, temporary_inspected_module])
23def test_loading_inherited_members(agent1: Callable, agent2: Callable) -> None:
24 """Test basic class inheritance.
26 Parameters:
27 agent1: A parametrized agent to load a module.
28 agent2: A parametrized agent to load a module.
29 """
30 code1 = """
31 class A:
32 attr_from_a = 0
33 def method_from_a(self): ...
35 class B(A):
36 attr_from_a = 1
37 attr_from_b = 1
38 def method_from_b(self): ...
39 """
40 code2 = """
41 from module1 import B
43 class C(B):
44 attr_from_c = 2
45 def method_from_c(self): ...
47 class D(C):
48 attr_from_a = 3
49 attr_from_d = 3
50 def method_from_d(self): ...
51 """
52 inspection_options = {}
53 collection = ModulesCollection()
55 with agent1(code1, module_name="module1", modules_collection=collection) as module1:
56 if agent2 is temporary_inspected_module:
57 inspection_options["import_paths"] = [module1.filepath.parent]
59 with agent2(code2, module_name="module2", modules_collection=collection, **inspection_options) as module2:
60 classa = module1["A"]
61 classb = module1["B"]
62 classc = module2["C"]
63 classd = module2["D"]
65 assert classa in classb.resolved_bases
66 assert classb in classc.resolved_bases
67 assert classc in classd.resolved_bases
69 classd_mro = classd.mro()
70 assert classa in classd_mro
71 assert classb in classd_mro
72 assert classc in classd_mro
74 inherited_members = classd.inherited_members
75 assert "attr_from_a" not in inherited_members # Overwritten.
76 assert "attr_from_b" in inherited_members
77 assert "attr_from_c" in inherited_members
78 assert "attr_from_d" not in inherited_members # Own-declared.
80 assert "method_from_a" in inherited_members
81 assert "method_from_b" in inherited_members
82 assert "method_from_c" in inherited_members
83 assert "method_from_d" not in inherited_members # Own-declared.
85 assert "attr_from_b" in classd.all_members
88@pytest.mark.parametrize("agent", [temporary_visited_module, temporary_inspected_module])
89def test_nested_class_inheritance(agent: Callable) -> None:
90 """Test nested class inheritance.
92 Parameters:
93 agent: A parametrized agent to load a module.
94 """
95 code = """
96 class A:
97 class B:
98 attr_from_b = 0
100 class C(A.B):
101 attr_from_c = 1
102 """
103 with agent(code) as module:
104 assert "attr_from_b" in module["C"].inherited_members
106 code = """
107 class OuterA:
108 class Inner: ...
109 class OuterB(OuterA):
110 class Inner(OuterA.Inner): ...
111 class OuterC(OuterA):
112 class Inner(OuterA.Inner): ...
113 class OuterD(OuterC):
114 class Inner(OuterC.Inner, OuterB.Inner): ...
115 """
116 with temporary_visited_module(code) as module:
117 assert _mro_paths(module["OuterD.Inner"]) == [
118 "module.OuterC.Inner",
119 "module.OuterB.Inner",
120 "module.OuterA.Inner",
121 ]
124@pytest.mark.parametrize(
125 ("classes", "cls", "expected_mro"),
126 [
127 (["A", "B(A)"], "B", ["A"]),
128 (["A", "B(A)", "C(A)", "D(B, C)"], "D", ["B", "C", "A"]),
129 (["A", "B(A)", "C(A)", "D(C, B)"], "D", ["C", "B", "A"]),
130 (["A(Z)"], "A", []),
131 (["A(str)"], "A", []),
132 (["A", "B(A)", "C(B)", "D(C)"], "D", ["C", "B", "A"]),
133 (["A", "B(A)", "C(B)", "D(C)", "E(A)", "F(B)", "G(F, E)", "H(G, D)"], "H", ["G", "F", "D", "C", "B", "E", "A"]),
134 (["A", "B(A[T])", "C(B[T])"], "C", ["B", "A"]),
135 ],
136)
137def test_computing_mro(classes: list[str], cls: str, expected_mro: list[str]) -> None:
138 """Test computing MRO.
140 Parameters:
141 classes: A list of classes inheriting from each other.
142 cls: The class to compute the MRO of.
143 expected_mro: The expected computed MRO.
144 """
145 code = "class " + ": ...\nclass ".join(classes) + ": ..."
146 with temporary_visited_module(code) as module:
147 assert _mro_paths(module[cls]) == [f"module.{base}" for base in expected_mro]
150@pytest.mark.parametrize(
151 ("classes", "cls"),
152 [
153 (["A", "B(A, A)"], "B"),
154 (["A(D)", "B", "C(A, B)", "D(C)"], "D"),
155 ],
156)
157def test_uncomputable_mro(classes: list[str], cls: str) -> None:
158 """Test uncomputable MRO.
160 Parameters:
161 classes: A list of classes inheriting from each other.
162 cls: The class to compute the MRO of.
163 """
164 code = "class " + ": ...\nclass ".join(classes) + ": ..."
165 with temporary_visited_module(code) as module, pytest.raises(ValueError, match="Cannot compute C3 linearization"):
166 _mro_paths(module[cls])
169def test_dynamic_base_classes() -> None:
170 """Test dynamic base classes."""
171 code = """
172 from collections import namedtuple
173 class A(namedtuple("B", "attrb")):
174 attra = 0
175 """
176 with temporary_visited_module(code) as module:
177 assert _mro_paths(module["A"]) == [] # Not supported.
179 with temporary_inspected_module(code) as module:
180 assert _mro_paths(module["A"]) == [] # Not supported either.