##// END OF EJS Templates
demandimport: fix a crash in LazyFinder.__delattr__...
demandimport: fix a crash in LazyFinder.__delattr__ I was tinkering with `with hgdemandimport.deactivated()` wrapped around loading the keyring module, and got spew that seemed to be confirmed by PyCharm. But I can't believe we haven't seen this before (and phabricator uses the same pattern): ** Unknown exception encountered with possibly-broken third-party extension "mercurial_keyring" 1.4.3 (keyring 23.11.0, backend unknown) ** which supports versions unknown of Mercurial. ** Please disable "mercurial_keyring" and try your action again. ** If that fixes the bug please report it to https://foss.heptapod.net/mercurial/mercurial_keyring/issues ** Python 3.9.15 (main, Oct 13 2022, 04:28:25) [GCC 7.5.0] ** Mercurial Distributed SCM (version 6.3.1) ** Extensions loaded: absorb, attorc 20220315, blackbox, eol, extdiff, fastannotate, lfs, mercurial_keyring 1.4.3 (keyring 23.11.0, backend unknown), phabblocker 20220315, phabricator 20220315, purge, rebase, schemes, share, show, strip, uncommit Traceback (most recent call last): File "/usr/local/bin/hg", line 59, in <module> dispatch.run() File "/usr/local/lib/python3.9/site-packages/mercurial/dispatch.py", line 143, in run status = dispatch(req) File "/usr/local/lib/python3.9/site-packages/mercurial/dispatch.py", line 232, in dispatch status = _rundispatch(req) File "/usr/local/lib/python3.9/site-packages/mercurial/dispatch.py", line 276, in _rundispatch ret = _runcatch(req) or 0 File "/usr/local/lib/python3.9/site-packages/mercurial/dispatch.py", line 451, in _runcatch return _callcatch(ui, _runcatchfunc) File "/usr/local/lib/python3.9/site-packages/mercurial/dispatch.py", line 461, in _callcatch return scmutil.callcatch(ui, func) File "/usr/local/lib/python3.9/site-packages/mercurial/scmutil.py", line 153, in callcatch return func() File "/usr/local/lib/python3.9/site-packages/mercurial/dispatch.py", line 441, in _runcatchfunc return _dispatch(req) File "/usr/local/lib/python3.9/site-packages/mercurial/dispatch.py", line 1265, in _dispatch return runcommand( File "/usr/local/lib/python3.9/site-packages/mercurial/dispatch.py", line 899, in runcommand ret = _runcommand(ui, options, cmd, d) File "/usr/local/lib/python3.9/site-packages/mercurial/dispatch.py", line 1277, in _runcommand return cmdfunc() File "/usr/local/lib/python3.9/site-packages/mercurial/dispatch.py", line 1263, in <lambda> d = lambda: util.checksignature(func)(ui, *args, **strcmdopt) File "/usr/local/lib/python3.9/site-packages/mercurial/util.py", line 1880, in check return func(*args, **kwargs) File "/root/mercurial_keyring/mercurial_keyring/mercurial_keyring.py", line 962, in cmd_keyring_check user, pwd, source, final_url = handler.get_credentials( File "/root/mercurial_keyring/mercurial_keyring/mercurial_keyring.py", line 497, in get_credentials keyring_pwd = password_store.get_http_password(keyring_url, actual_user) File "/root/mercurial_keyring/mercurial_keyring/mercurial_keyring.py", line 287, in get_http_password return self._read_password_from_keyring( File "/root/mercurial_keyring/mercurial_keyring/mercurial_keyring.py", line 335, in _read_password_from_keyring keyring = import_keyring() >> `with hgdemandimport.deactivated()` inserted here File "/root/mercurial_keyring/mercurial_keyring/mercurial_keyring.py", line 120, in import_keyring return _import_keyring() File "/root/mercurial_keyring/mercurial_keyring/mercurial_keyring.py", line 133, in _import_keyring mod, was_imported_now = meu.direct_import_ext( File "/usr/lib/python3.9/site-packages/mercurial_extension_utils.py", line 1381, in direct_import_ext __import__(module_name) File "<frozen importlib._bootstrap>", line 1007, in _find_and_load File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 680, in _load_unlocked File "/usr/local/lib/python3.9/site-packages/hgdemandimport/demandimportpy3.py", line 46, in exec_module self.loader.exec_module(module) File "/usr/lib/python3.9/site-packages/keyring/__init__.py", line 1, in <module> from .core import ( File "<frozen importlib._bootstrap>", line 1007, in _find_and_load File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 680, in _load_unlocked File "/usr/local/lib/python3.9/site-packages/hgdemandimport/demandimportpy3.py", line 46, in exec_module self.loader.exec_module(module) File "/usr/lib/python3.9/site-packages/keyring/core.py", line 11, in <module> from . import backend, credentials File "<frozen importlib._bootstrap>", line 1007, in _find_and_load File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 680, in _load_unlocked File "/usr/local/lib/python3.9/site-packages/hgdemandimport/demandimportpy3.py", line 46, in exec_module self.loader.exec_module(module) File "/usr/lib/python3.9/site-packages/keyring/backend.py", line 13, in <module> from .py312compat import metadata File "<frozen importlib._bootstrap>", line 1007, in _find_and_load File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 680, in _load_unlocked File "/usr/local/lib/python3.9/site-packages/hgdemandimport/demandimportpy3.py", line 46, in exec_module self.loader.exec_module(module) File "/usr/lib/python3.9/site-packages/keyring/py312compat.py", line 10, in <module> import importlib_metadata as metadata # type: ignore File "<frozen importlib._bootstrap>", line 1007, in _find_and_load File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 680, in _load_unlocked File "/usr/local/lib/python3.9/site-packages/hgdemandimport/demandimportpy3.py", line 46, in exec_module self.loader.exec_module(module) File "/usr/lib/python3.9/site-packages/importlib_metadata/__init__.py", line 715, in <module> class MetadataPathFinder(NullFinder, DistributionFinder): File "/usr/lib/python3.9/site-packages/importlib_metadata/_compat.py", line 24, in install disable_stdlib_finder() File "/usr/lib/python3.9/site-packages/importlib_metadata/_compat.py", line 43, in disable_stdlib_finder del finder.find_distributions File "/usr/local/lib/python3.9/site-packages/hgdemandimport/demandimportpy3.py", line 88, in __delattr__ return delattr(object.__getattribute__(self, "_finder")) TypeError: delattr expected 2 arguments, got 1

File last commit:

r50669:48e38b17 stable
r50669:48e38b17 stable
Show More
demandimportpy3.py
174 lines | 5.1 KiB | text/x-python | PythonLexer
# demandimportpy3 - global demand-loading of modules for Mercurial
#
# Copyright 2017 Facebook Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""Lazy loading for Python 3.6 and above.
This uses the new importlib finder/loader functionality available in Python 3.5
and up. The code reuses most of the mechanics implemented inside importlib.util,
but with a few additions:
* Allow excluding certain modules from lazy imports.
* Expose an interface that's substantially the same as demandimport for
Python 2.
This also has some limitations compared to the Python 2 implementation:
* Much of the logic is per-package, not per-module, so any packages loaded
before demandimport is enabled will not be lazily imported in the future. In
practice, we only expect builtins to be loaded before demandimport is
enabled.
"""
# This line is unnecessary, but it satisfies test-check-py3-compat.t.
import contextlib
import importlib.util
import sys
from . import tracing
_deactivated = False
class _lazyloaderex(importlib.util.LazyLoader):
"""This is a LazyLoader except it also follows the _deactivated global and
the ignore list.
"""
def exec_module(self, module):
"""Make the module load lazily."""
with tracing.log('demandimport %s', module):
if _deactivated or module.__name__ in ignores:
self.loader.exec_module(module)
else:
super().exec_module(module)
class LazyFinder:
"""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"))
__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"), name)
def __setattr__(self, name, value):
return setattr(object.__getattribute__(self, "_finder"), name, value)
def find_spec(self, fullname, path, target=None):
finder = object.__getattribute__(self, "_finder")
try:
find_spec = finder.find_spec
except AttributeError:
loader = finder.find_module(fullname, path)
if loader is None:
spec = None
else:
spec = importlib.util.spec_from_loader(fullname, loader)
else:
spec = find_spec(fullname, path, target)
# Lazy loader requires exec_module().
if (
spec is not None
and spec.loader is not None
and getattr(spec.loader, "exec_module", None)
):
spec.loader = _lazyloaderex(spec.loader)
return spec
ignores = set()
def init(ignoreset):
global ignores
ignores = ignoreset
def isenabled():
return not _deactivated and any(
isinstance(finder, LazyFinder) for finder in sys.meta_path
)
def disable():
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():
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
def deactivated():
# This implementation is a bit different from Python 2's. Python 3
# maintains a per-package finder cache in sys.path_importer_cache (see
# PEP 302). This means that we can't just call disable + enable.
# If we do that, in situations like:
#
# demandimport.enable()
# ...
# from foo.bar import mod1
# with demandimport.deactivated():
# from foo.bar import mod2
#
# mod2 will be imported lazily. (The converse also holds -- whatever finder
# first gets cached will be used.)
#
# Instead, have a global flag the LazyLoader can use.
global _deactivated
demandenabled = isenabled()
if demandenabled:
_deactivated = True
try:
yield
finally:
if demandenabled:
_deactivated = False