Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(internal): better support for Python legacy versions in ModuleW…
Browse files Browse the repository at this point in the history
…atchdog

This change improves support for Python legacy versions, like 2.7 and
3.5 by using a common loader wrapper that makes more attributes
available upon request, e.g. is_package. Furthermore, this change also
provides support for dict copy via the dict constructor, which is
required by some pytest fixtures, like testdir.
P403n1x87 committed Aug 16, 2022
1 parent e7ac9ea commit cfdcf67
Showing 1 changed file with 31 additions and 17 deletions.
48 changes: 31 additions & 17 deletions ddtrace/internal/module.py
Original file line number Diff line number Diff line change
@@ -122,26 +122,24 @@ def _resolve(path):
# https://github.com/GrahamDumpleton/wrapt/blob/df0e62c2740143cceb6cafea4c306dae1c559ef8/src/wrapt/importer.py

if PY2:
import pkgutil

find_spec = ModuleSpec = None
Loader = object

find_loader = pkgutil.find_loader

else:
from importlib.abc import Loader
from importlib.machinery import ModuleSpec
from importlib.util import find_spec

def find_loader(fullname):
# type: (str) -> Optional[Loader]
return getattr(find_spec(fullname), "loader", None)

# DEV: This is used by Python 2 only
class _ImportHookLoader(object):
def __init__(self, callback):
# type: (Callable[[ModuleType], None]) -> None
self.callback = callback

def load_module(self, fullname):
# type: (str) -> ModuleType
module = sys.modules[fullname]
self.callback(module)

return module
LEGACY_DICT_COPY = sys.version_info < (3, 6)


class _ImportHookChainedLoader(Loader):
@@ -283,6 +281,18 @@ def __delitem__(self, name):

def __getattribute__(self, name):
# type: (str) -> Any
if LEGACY_DICT_COPY and name == "keys":
# This is a potential attempt to make a copy of sys.modules using
# dict(sys.modules) on a Python version that uses the C API to
# perform the operation. Since we are an instance of a dict, this
# means that we will end up looking like the empty dict, so we take
# this chance to actually look like sys.modules.
# NOTE: This is a potential source of memory leaks. However, we
# expect this to occur only on defunct Python versions, and only
# during special code executions, like test runs.
super(ModuleWatchdog, self).clear()
super(ModuleWatchdog, self).update(self._modules)

try:
return super(ModuleWatchdog, self).__getattribute__("_modules").__getattribute__(name)
except AttributeError:
@@ -308,16 +318,20 @@ def find_module(self, fullname, path=None):
self._finding.add(fullname)

try:
if PY2:
__import__(fullname)
return _ImportHookLoader(self.after_import)

loader = getattr(find_spec(fullname), "loader", None)
loader = find_loader(fullname)
if loader is not None:
if not isinstance(loader, _ImportHookChainedLoader):
loader = _ImportHookChainedLoader(loader)

loader.add_callback(type(self), self.after_import)
if PY2:
# With Python 2 we don't get all the finder invoked, so we
# make sure we register all the callbacks at the earliest
# opportunity.
for finder in sys.meta_path:
if isinstance(finder, ModuleWatchdog):
loader.add_callback(type(finder), finder.after_import)
else:
loader.add_callback(type(self), self.after_import)

return loader

0 comments on commit cfdcf67

Please sign in to comment.