Coverage for tests/test_inspector.py: 100.00%

73 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-15 16:47 +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 pytest.raises(ImportError, match="ModuleNotFoundError: No module named 'missing'"), temporary_inspected_module( 

52 "import missing", 

53 ): 

54 pass 

55 

56 

57def test_inspect_properties_as_attributes() -> None: 

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

59 with temporary_inspected_module( 

60 """ 

61 try: 

62 from functools import cached_property 

63 except ImportError: 

64 from cached_property import cached_property 

65 

66 class C: 

67 @property 

68 def prop(self) -> bool: 

69 return True 

70 @cached_property 

71 def cached_prop(self) -> int: 

72 return 0 

73 """, 

74 ) as module: 

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

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

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

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

79 

80 

81def test_inspecting_module_importing_other_module() -> None: 

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

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

84 assert module["it"].is_alias 

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

86 

87 

88def test_inspecting_parameters_with_functions_as_default_values() -> None: 

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

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

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

92 assert default == "func" 

93 

94 

95def test_inspecting_package_and_module_with_same_names() -> None: 

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

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

98 pass 

99 

100 

101def test_inspecting_module_with_submodules() -> None: 

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

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

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

105 assert "mod" not in pkg.members 

106 clear_sys_modules("pkg") 

107 

108 

109def test_inspecting_module_with_imported_submodules() -> None: 

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

111 with temporary_pypackage( 

112 "pkg", 

113 { 

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

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

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

117 }, 

118 ) as tmp_package: 

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

120 assert "subpkg" not in pkg.members 

121 assert "mod" in pkg.members 

122 assert pkg["mod"].is_alias 

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

124 clear_sys_modules("pkg") 

125 

126 

127def test_inspecting_objects_from_private_builtin_stdlib_moduless() -> None: 

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

129 ast = inspect("ast") 

130 assert "Assign" in ast.members 

131 assert not ast["Assign"].is_alias 

132 

133 ast = inspect("_ast") 

134 assert "Assign" in ast.members 

135 assert not ast["Assign"].is_alias 

136 

137 

138def test_inspecting_partials_as_functions() -> None: 

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

140 with temporary_inspected_module( 

141 """ 

142 from functools import partial 

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

144 partial_func = partial(func, 1) 

145 partial_func.__module__ = __name__ 

146 """, 

147 ) as module: 

148 partial_func = module["partial_func"] 

149 assert partial_func.is_function 

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

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

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