demandimportpy3.py
180 lines
| 5.4 KiB
| text/x-python
|
PythonLexer
/ hgdemandimport / demandimportpy3.py
Siddharth Agarwal
|
r32423 | # 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. | ||||
""" | ||||
Matt Harbison
|
r52756 | from __future__ import annotations | ||
Siddharth Agarwal
|
r32423 | import contextlib | ||
import importlib.util | ||||
Augie Fackler
|
r33899 | import sys | ||
Siddharth Agarwal
|
r32423 | |||
Augie Fackler
|
r42674 | from . import tracing | ||
Siddharth Agarwal
|
r32423 | _deactivated = False | ||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r32423 | class _lazyloaderex(importlib.util.LazyLoader): | ||
"""This is a LazyLoader except it also follows the _deactivated global and | ||||
the ignore list. | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r50742 | _HAS_DYNAMIC_ATTRIBUTES = True # help pytype not flag self.loader | ||
Siddharth Agarwal
|
r32423 | def exec_module(self, module): | ||
"""Make the module load lazily.""" | ||||
Augie Fackler
|
r42674 | with tracing.log('demandimport %s', module): | ||
if _deactivated or module.__name__ in ignores: | ||||
Jason R. Coombs
|
r50483 | # Reset the loader on the module as super() does (issue6725) | ||
module.__spec__.loader = self.loader | ||||
module.__loader__ = self.loader | ||||
Augie Fackler
|
r42674 | self.loader.exec_module(module) | ||
else: | ||||
super().exec_module(module) | ||||
Siddharth Agarwal
|
r32423 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class LazyFinder: | ||
Gregory Szorc
|
r44577 | """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. | ||||
Siddharth Agarwal
|
r32423 | |||
Gregory Szorc
|
r44577 | 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")) | ||||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r44577 | __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): | ||||
Matt Harbison
|
r50669 | return delattr(object.__getattribute__(self, "_finder"), name) | ||
Gregory Szorc
|
r44577 | |||
def __setattr__(self, name, value): | ||||
return setattr(object.__getattribute__(self, "_finder"), name, value) | ||||
Manuel Jacob
|
r45337 | def find_spec(self, fullname, path, target=None): | ||
Gregory Szorc
|
r44577 | finder = object.__getattribute__(self, "_finder") | ||
Manuel Jacob
|
r45337 | 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) | ||||
Gregory Szorc
|
r44577 | |||
# Lazy loader requires exec_module(). | ||||
if ( | ||||
spec is not None | ||||
and spec.loader is not None | ||||
Matt Harbison
|
r46347 | and getattr(spec.loader, "exec_module", None) | ||
Gregory Szorc
|
r44577 | ): | ||
spec.loader = _lazyloaderex(spec.loader) | ||||
return spec | ||||
Siddharth Agarwal
|
r32423 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r37862 | ignores = set() | ||
Siddharth Agarwal
|
r32423 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r37862 | def init(ignoreset): | ||
global ignores | ||||
ignores = ignoreset | ||||
Siddharth Agarwal
|
r32423 | |||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r32423 | def isenabled(): | ||
Gregory Szorc
|
r44577 | return not _deactivated and any( | ||
isinstance(finder, LazyFinder) for finder in sys.meta_path | ||||
) | ||||
Siddharth Agarwal
|
r32423 | |||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r32423 | def disable(): | ||
Gregory Szorc
|
r44577 | new_finders = [] | ||
for finder in sys.meta_path: | ||||
new_finders.append( | ||||
finder._finder if isinstance(finder, LazyFinder) else finder | ||||
) | ||||
sys.meta_path[:] = new_finders | ||||
Siddharth Agarwal
|
r32423 | |||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r32423 | def enable(): | ||
Gregory Szorc
|
r44577 | 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 | ||||
Siddharth Agarwal
|
r32423 | |||
Augie Fackler
|
r43346 | |||
Siddharth Agarwal
|
r32423 | @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 | ||||