hgdemandimport/demandimportpy3.py
changeset 44118 f81c17ec303c
parent 44117 c5e0a9b97b8a
child 44819 a6e12d477595
--- a/hgdemandimport/demandimportpy3.py	Mon Jan 20 23:42:19 2020 -0800
+++ b/hgdemandimport/demandimportpy3.py	Mon Jan 20 23:51:25 2020 -0800
@@ -27,8 +27,6 @@
 from __future__ import absolute_import
 
 import contextlib
-import importlib.abc
-import importlib.machinery
 import importlib.util
 import sys
 
@@ -57,23 +55,61 @@
                 super().exec_module(module)
 
 
-_extensions_loader = _lazyloaderex.factory(
-    importlib.machinery.ExtensionFileLoader
-)
-_bytecode_loader = _lazyloaderex.factory(
-    importlib.machinery.SourcelessFileLoader
-)
-_source_loader = _lazyloaderex.factory(importlib.machinery.SourceFileLoader)
+class LazyFinder(object):
+    """A wrapper around a ``MetaPathFinder`` that makes loaders lazy.
+
+    ``sys.meta_path`` finders have their ``find_spec()`` called to locate a
+    module. This returns a ``ModuleSpec`` if found or ``None``. The
+    ``ModuleSpec`` has a ``loader`` attribute, which is called to actually
+    load a module.
+
+    Our class wraps an existing finder and overloads its ``find_spec()`` to
+    replace the ``loader`` with our lazy loader proxy.
 
+    We have to use __getattribute__ to proxy the instance because some meta
+    path finders don't support monkeypatching.
+    """
+
+    __slots__ = ("_finder",)
+
+    def __init__(self, finder):
+        object.__setattr__(self, "_finder", finder)
+
+    def __repr__(self):
+        return "<LazyFinder for %r>" % object.__getattribute__(self, "_finder")
+
+    # __bool__ is canonical Python 3. But check-code insists on __nonzero__ being
+    # defined via `def`.
+    def __nonzero__(self):
+        return bool(object.__getattribute__(self, "_finder"))
 
-def _makefinder(path):
-    return importlib.machinery.FileFinder(
-        path,
-        # This is the order in which loaders are passed in in core Python.
-        (_extensions_loader, importlib.machinery.EXTENSION_SUFFIXES),
-        (_source_loader, importlib.machinery.SOURCE_SUFFIXES),
-        (_bytecode_loader, importlib.machinery.BYTECODE_SUFFIXES),
-    )
+    __bool__ = __nonzero__
+
+    def __getattribute__(self, name):
+        if name in ("_finder", "find_spec"):
+            return object.__getattribute__(self, name)
+
+        return getattr(object.__getattribute__(self, "_finder"), name)
+
+    def __delattr__(self, name):
+        return delattr(object.__getattribute__(self, "_finder"))
+
+    def __setattr__(self, name, value):
+        return setattr(object.__getattribute__(self, "_finder"), name, value)
+
+    def find_spec(self, *args, **kwargs):
+        finder = object.__getattribute__(self, "_finder")
+        spec = finder.find_spec(*args, **kwargs)
+
+        # Lazy loader requires exec_module().
+        if (
+            spec is not None
+            and spec.loader is not None
+            and getattr(spec.loader, "exec_module")
+        ):
+            spec.loader = _lazyloaderex(spec.loader)
+
+        return spec
 
 
 ignores = set()
@@ -85,22 +121,30 @@
 
 
 def isenabled():
-    return _makefinder in sys.path_hooks and not _deactivated
+    return not _deactivated and any(
+        isinstance(finder, LazyFinder) for finder in sys.meta_path
+    )
 
 
 def disable():
-    try:
-        while True:
-            sys.path_hooks.remove(_makefinder)
-    except ValueError:
-        pass
+    new_finders = []
+    for finder in sys.meta_path:
+        new_finders.append(
+            finder._finder if isinstance(finder, LazyFinder) else finder
+        )
+    sys.meta_path[:] = new_finders
 
 
 def enable():
     if not _supported:
         return
 
-    sys.path_hooks.insert(0, _makefinder)
+    new_finders = []
+    for finder in sys.meta_path:
+        new_finders.append(
+            LazyFinder(finder) if not isinstance(finder, LazyFinder) else finder
+        )
+    sys.meta_path[:] = new_finders
 
 
 @contextlib.contextmanager