Coverage for tests/test_inspector.py: 98.63%

73 statements  

« prev     ^ index     » next       coverage.py v7.6.2, created at 2024-10-12 01:34 +0200

1"""Test inspection mechanisms.""" 

2 

3from __future__ import annotations 

4 

5import pytest 

6 

7from griffe import inspect, temporary_inspected_module, temporary_inspected_package, temporary_pypackage 

8from tests.helpers import clear_sys_modules 

9 

10 

11def test_annotations_from_builtin_types() -> None: 

12 """Assert builtin types are correctly transformed to annotations.""" 

13 with temporary_inspected_module("def func(a: int) -> str: pass") as module: 

14 func = module["func"] 

15 assert func.parameters[0].name == "a" 

16 assert func.parameters[0].annotation.name == "int" 

17 assert func.returns.name == "str" 

18 

19 

20def test_annotations_from_classes() -> None: 

21 """Assert custom classes are correctly transformed to annotations.""" 

22 with temporary_inspected_module("class A: pass\ndef func(a: A) -> A: pass") as module: 

23 func = module["func"] 

24 assert func.parameters[0].name == "a" 

25 param = func.parameters[0].annotation 

26 assert param.name == "A" 

27 assert param.canonical_path == f"{module.name}.A" 

28 returns = func.returns 

29 assert returns.name == "A" 

30 assert returns.canonical_path == f"{module.name}.A" 

31 

32 

33def test_class_level_imports() -> None: 

34 """Assert annotations using class-level imports are resolved.""" 

35 with temporary_inspected_module( 

36 """ 

37 class A: 

38 from io import StringIO 

39 def method(self, p: StringIO): 

40 pass 

41 """, 

42 ) as module: 

43 method = module["A.method"] 

44 name = method.parameters["p"].annotation 

45 assert name.name == "StringIO" 

46 assert name.canonical_path == "io.StringIO" 

47 

48 

49def test_missing_dependency() -> None: 

50 """Assert missing dependencies are handled during dynamic imports.""" 

51 with ( 

52 pytest.raises(ImportError, match="ModuleNotFoundError: No module named 'missing'"), 

53 temporary_inspected_module("import missing"), 

54 ): 

55 pass 

56 

57 

58def test_inspect_properties_as_attributes() -> None: 

59 """Assert properties are created as attributes and not functions.""" 

60 with temporary_inspected_module( 

61 """ 

62 try: 

63 from functools import cached_property 

64 except ImportError: 

65 from cached_property import cached_property 

66 

67 class C: 

68 @property 

69 def prop(self) -> bool: 

70 return True 

71 @cached_property 

72 def cached_prop(self) -> int: 

73 return 0 

74 """, 

75 ) as module: 

76 assert module["C.prop"].is_attribute 

77 assert "property" in module["C.prop"].labels 

78 assert module["C.cached_prop"].is_attribute 

79 assert "cached" in module["C.cached_prop"].labels 

80 

81 

82def test_inspecting_module_importing_other_module() -> None: 

83 """Assert aliases to modules are correctly inspected and aliased.""" 

84 with temporary_inspected_module("import itertools as it") as module: 

85 assert module["it"].is_alias 

86 assert module["it"].target_path == "itertools" 

87 

88 

89def test_inspecting_parameters_with_functions_as_default_values() -> None: 

90 """Assert functions as default parameter values are serialized with their name.""" 

91 with temporary_inspected_module("def func(): ...\ndef other_func(f=func): ...") as module: 

92 default = module["other_func"].parameters["f"].default 

93 assert default == "func" 

94 

95 

96def test_inspecting_package_and_module_with_same_names() -> None: 

97 """Package and module having same name shouldn't cause issues.""" 

98 with temporary_inspected_package("package", {"package.py": "a = 0"}): 

99 pass 

100 

101 

102def test_inspecting_module_with_submodules() -> None: 

103 """Inspecting a module shouldn't register any of its submodules if they're not imported.""" 

104 with temporary_pypackage("pkg", ["mod.py"]) as tmp_package: 

105 pkg = inspect("pkg", filepath=tmp_package.path / "__init__.py") 

106 assert "mod" not in pkg.members 

107 clear_sys_modules("pkg") 

108 

109 

110def test_inspecting_module_with_imported_submodules() -> None: 

111 """When inspecting a package on the disk, direct submodules should be skipped entirely.""" 

112 with temporary_pypackage( 

113 "pkg", 

114 { 

115 "__init__.py": "from pkg import subpkg\nfrom pkg.subpkg import mod", 

116 "subpkg/__init__.py": "a = 0", 

117 "subpkg/mod.py": "b = 0", 

118 }, 

119 ) as tmp_package: 

120 pkg = inspect("pkg", filepath=tmp_package.path / "__init__.py") 

121 assert "subpkg" not in pkg.members 

122 assert "mod" in pkg.members 

123 assert pkg["mod"].is_alias 

124 assert pkg["mod"].target_path == "pkg.subpkg.mod" 

125 clear_sys_modules("pkg") 

126 

127 

128def test_inspecting_objects_from_private_builtin_stdlib_moduless() -> None: 

129 """Inspect objects from private built-in modules in the standard library.""" 

130 ast = inspect("ast") 

131 assert "Assign" in ast.members 

132 assert not ast["Assign"].is_alias 

133 

134 ast = inspect("_ast") 

135 assert "Assign" in ast.members 

136 assert not ast["Assign"].is_alias 

137 

138 

139def test_inspecting_partials_as_functions() -> None: 

140 """Assert partials are correctly inspected as functions.""" 

141 with temporary_inspected_module( 

142 """ 

143 from functools import partial 

144 def func(a: int, b: int) -> int: pass 

145 partial_func = partial(func, 1) 

146 partial_func.__module__ = __name__ 

147 """, 

148 ) as module: 

149 partial_func = module["partial_func"] 

150 assert partial_func.is_function 

151 assert partial_func.parameters[0].name == "b" 

152 assert partial_func.parameters[0].annotation.name == "int" 

153 assert partial_func.returns.name == "int"