-
Notifications
You must be signed in to change notification settings - Fork 604
Expand file tree
/
Copy pathtest_shadowed_module.py
More file actions
117 lines (98 loc) · 3.89 KB
/
test_shadowed_module.py
File metadata and controls
117 lines (98 loc) · 3.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import sys
import ast
import types
import pkgutil
import importlib
import pathlib
import pytest
from sentry_sdk import integrations
from sentry_sdk.integrations import _DEFAULT_INTEGRATIONS, Integration
def pytest_generate_tests(metafunc):
"""
All submodules of sentry_sdk.integrations are picked up, so modules
without a subclass of sentry_sdk.integrations.Integration are also tested
for poorly gated imports.
This approach was chosen to keep the implementation simple.
"""
if "integration_submodule_name" in metafunc.fixturenames:
submodule_names = {
submodule_name
for _, submodule_name, _ in pkgutil.walk_packages(integrations.__path__)
}
metafunc.parametrize(
"integration_submodule_name",
# Temporarily skip some integrations
submodule_names
- {
"clickhouse_driver",
"grpc",
"litellm",
"opentelemetry",
"pure_eval",
"trytond",
"typer",
},
)
def find_unrecognized_dependencies(tree):
"""
Finds unrecognized imports in the AST for a Python module. In an empty
environment the set of non-standard library modules is returned.
"""
unrecognized_dependencies = set()
package_name = lambda name: name.split(".")[0]
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
root = package_name(alias.name)
try:
if not importlib.util.find_spec(root):
unrecognized_dependencies.add(root)
except ValueError:
continue
elif isinstance(node, ast.ImportFrom):
# if node.level is not 0 the import is relative
if node.level > 0 or node.module is None:
continue
root = package_name(node.module)
try:
if not importlib.util.find_spec(root):
unrecognized_dependencies.add(root)
except ValueError:
continue
return unrecognized_dependencies
@pytest.mark.skipif(
sys.version_info < (3, 7), reason="asyncpg imports __future__.annotations"
)
def test_shadowed_modules_when_importing_integrations(
sentry_init, integration_submodule_name
):
"""
Check that importing integrations for third-party module raises an
DidNotEnable exception when the associated module is shadowed by an empty
module.
An integration is determined to be for a third-party module if it cannot
be imported in the environment in which the tests run.
"""
module_path = f"sentry_sdk.integrations.{integration_submodule_name}"
try:
# If importing the integration succeeds in the current environment, assume
# that the integration has no non-standard imports.
importlib.import_module(module_path)
return
except integrations.DidNotEnable:
spec = importlib.util.find_spec(module_path)
source = pathlib.Path(spec.origin).read_text(encoding="utf-8")
tree = ast.parse(source, filename=spec.origin)
integration_dependencies = find_unrecognized_dependencies(tree)
# For each non-standard import, create an empty shadow module to
# emulate an empty "agents.py" or analogous local module that
# shadows the package.
for dependency in integration_dependencies:
sys.modules[dependency] = types.ModuleType(dependency)
# Importing the integration must raise DidNotEnable, since the
# SDK catches the exception type when attempting to activate
# auto-enabling integrations.
with pytest.raises(integrations.DidNotEnable):
importlib.import_module(module_path)
for dependency in integration_dependencies:
del sys.modules[dependency]