Skip to content

Commit 0ec3d72

Browse files
github-actions[bot]P403n1x87gnufede
authored
fix(internal): chained namespace loader [backport 2.7] (#8626)
Backport 8ac9d5f from #8603 to 2.7. We fix the implemention of the support for namespace module imports that caused issues with some standard library modules, such as `importlib`. We make sure that the internal module attribute initialisation can create the correct namespace loader, and then we make sure that we chain it with the custom loader used to trigger import hooks. Addresses #8003 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Gabriele N. Tornetta <[email protected]> Co-authored-by: Federico Mon <[email protected]>
1 parent 29fe8ff commit 0ec3d72

File tree

4 files changed

+39
-5
lines changed

4 files changed

+39
-5
lines changed

ddtrace/internal/module.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,21 @@ def __getattr__(self, name):
140140
# Proxy any other attribute access to the underlying loader.
141141
return getattr(self.loader, name)
142142

143+
def namespace_module(self, spec: ModuleSpec) -> ModuleType:
144+
module = ModuleType(spec.name)
145+
# Pretend that we do not have a loader (this would be self), to
146+
# allow _init_module_attrs to create the appropriate NamespaceLoader
147+
# for the namespace module.
148+
spec.loader = None
149+
150+
_init_module_attrs(spec, module, override=True)
151+
152+
# Chain the loaders
153+
self.loader = spec.loader
154+
module.__loader__ = spec.loader = self # type: ignore[assignment]
155+
156+
return module
157+
143158
def add_callback(self, key: t.Any, callback: t.Callable[[ModuleType], None]) -> None:
144159
self.callbacks[key] = callback
145160

@@ -157,8 +172,7 @@ def load_module(self, fullname: str) -> t.Optional[ModuleType]:
157172
if self.loader is None:
158173
if self.spec is None:
159174
return None
160-
sys.modules[self.spec.name] = module = ModuleType(fullname)
161-
_init_module_attrs(self.spec, module)
175+
sys.modules[self.spec.name] = module = self.namespace_module(self.spec)
162176
else:
163177
module = self.loader.load_module(fullname)
164178

@@ -171,9 +185,7 @@ def _create_module(self, spec):
171185
return self.loader.create_module(spec)
172186

173187
if is_namespace_spec(spec):
174-
module = ModuleType(spec.name)
175-
_init_module_attrs(spec, module)
176-
return module
188+
return self.namespace_module(spec)
177189

178190
return None
179191

docs/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,4 @@ Enablement
285285
hotspot
286286
CMake
287287
libdatadog
288+
importlib
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
fixes:
3+
- |
4+
Fix an incompatibility between the handling of namespace module imports and
5+
parts of the functionalities of the standard library importlib module.

tests/internal/test_module.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,19 @@ def test_module_watchdog_pkg_resources_support_already_imported():
486486
import ddtrace # noqa
487487

488488
p.resource_listdir("namespace_test.ns_module", ".")
489+
490+
491+
@pytest.mark.skipif(
492+
sys.version_info < (3, 10), reason="importlib.resources.files is not available or broken in Python < 3.10"
493+
)
494+
@pytest.mark.subprocess(env=dict(NSPATH=str(Path(__file__).parent)))
495+
def test_module_watchdog_importlib_resources_files():
496+
import os
497+
import sys
498+
499+
sys.path.insert(0, os.getenv("NSPATH"))
500+
501+
from importlib.readers import MultiplexedPath
502+
import importlib.resources as r
503+
504+
assert isinstance(r.files("namespace_test"), MultiplexedPath)

0 commit comments

Comments
 (0)