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

1"""Tests for class inheritance.""" 

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING 

6 

7import pytest 

8 

9from griffe import ModulesCollection, temporary_inspected_module, temporary_visited_module 

10 

11if TYPE_CHECKING: 

12 from collections.abc import Callable 

13 

14 from griffe import Class 

15 

16 

17def _mro_paths(cls: Class) -> list[str]: 

18 return [base.path for base in cls.mro()] 

19 

20 

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. 

25 

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): ... 

34 

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 

42 

43 class C(B): 

44 attr_from_c = 2 

45 def method_from_c(self): ... 

46 

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() 

54 

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] 

58 

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"] 

64 

65 assert classa in classb.resolved_bases 

66 assert classb in classc.resolved_bases 

67 assert classc in classd.resolved_bases 

68 

69 classd_mro = classd.mro() 

70 assert classa in classd_mro 

71 assert classb in classd_mro 

72 assert classc in classd_mro 

73 

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. 

79 

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. 

84 

85 assert "attr_from_b" in classd.all_members 

86 

87 

88@pytest.mark.parametrize("agent", [temporary_visited_module, temporary_inspected_module]) 

89def test_nested_class_inheritance(agent: Callable) -> None: 

90 """Test nested class inheritance. 

91 

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 

99 

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 

105 

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 ] 

122 

123 

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. 

139 

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] 

148 

149 

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. 

159 

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]) 

167 

168 

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. 

178 

179 with temporary_inspected_module(code) as module: 

180 assert _mro_paths(module["A"]) == [] # Not supported either.