demandimport.py
325 lines
| 11.4 KiB
| text/x-python
|
PythonLexer
/ mercurial / demandimport.py
Matt Mackall
|
r3877 | # demandimport.py - global demand-loading of modules for Mercurial | ||
# | ||||
Thomas Arendsen Hein
|
r4635 | # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com> | ||
Matt Mackall
|
r3877 | # | ||
Martin Geisler
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Matt Mackall
|
r3877 | |||
''' | ||||
demandimport - automatic demandloading of modules | ||||
To enable this module, do: | ||||
import demandimport; demandimport.enable() | ||||
Imports of the following forms will be demand-loaded: | ||||
import a, b.c | ||||
import a.b as c | ||||
from a import b,c # a will be loaded immediately | ||||
These imports will not be delayed: | ||||
from a import * | ||||
b = __import__(a) | ||||
''' | ||||
Gregory Szorc
|
r25943 | from __future__ import absolute_import | ||
import contextlib | ||||
import os | ||||
import sys | ||||
Gregory Szorc
|
r25674 | |||
# __builtin__ in Python 2, builtins in Python 3. | ||||
try: | ||||
import __builtin__ as builtins | ||||
except ImportError: | ||||
import builtins | ||||
Jordi GutiƩrrez Hermoso
|
r25327 | |||
Gregory Szorc
|
r25943 | contextmanager = contextlib.contextmanager | ||
Matt Mackall
|
r3877 | _origimport = __import__ | ||
Augie Fackler
|
r14977 | nothing = object() | ||
Gregory Szorc
|
r25933 | # Python 3 doesn't have relative imports nor level -1. | ||
level = -1 | ||||
if sys.version_info[0] >= 3: | ||||
level = 0 | ||||
_import = _origimport | ||||
Simon Heimberg
|
r15096 | |||
Gregory Szorc
|
r25936 | def _hgextimport(importfunc, name, globals, *args, **kwargs): | ||
FUJIWARA Katsunori
|
r19933 | try: | ||
Gregory Szorc
|
r25936 | return importfunc(name, globals, *args, **kwargs) | ||
FUJIWARA Katsunori
|
r19933 | except ImportError: | ||
if not globals: | ||||
raise | ||||
# extensions are loaded with "hgext_" prefix | ||||
hgextname = 'hgext_%s' % name | ||||
nameroot = hgextname.split('.', 1)[0] | ||||
contextroot = globals.get('__name__', '').split('.', 1)[0] | ||||
if nameroot != contextroot: | ||||
raise | ||||
# retry to import with "hgext_" prefix | ||||
Gregory Szorc
|
r25936 | return importfunc(hgextname, globals, *args, **kwargs) | ||
FUJIWARA Katsunori
|
r19933 | |||
Matt Mackall
|
r3877 | class _demandmod(object): | ||
FUJIWARA Katsunori
|
r29737 | """module demand-loader and proxy | ||
Specify 1 as 'level' argument at construction, to import module | ||||
relatively. | ||||
""" | ||||
def __init__(self, name, globals, locals, level): | ||||
Matt Mackall
|
r3877 | if '.' in name: | ||
head, rest = name.split('.', 1) | ||||
after = [rest] | ||||
else: | ||||
head = name | ||||
after = [] | ||||
FUJIWARA Katsunori
|
r19932 | object.__setattr__(self, "_data", | ||
Gregory Szorc
|
r26457 | (head, globals, locals, after, level, set())) | ||
Benoit Boissinot
|
r3896 | object.__setattr__(self, "_module", None) | ||
Matt Mackall
|
r3877 | def _extend(self, name): | ||
"""add to the list of submodules to load""" | ||||
self._data[3].append(name) | ||||
Gregory Szorc
|
r26457 | |||
def _addref(self, name): | ||||
"""Record that the named module ``name`` imports this module. | ||||
References to this proxy class having the name of this module will be | ||||
replaced at module load time. We assume the symbol inside the importing | ||||
module is identical to the "head" name of this module. We don't | ||||
actually know if "as X" syntax is being used to change the symbol name | ||||
because this information isn't exposed to __import__. | ||||
""" | ||||
self._data[5].add(name) | ||||
Matt Mackall
|
r3877 | def _load(self): | ||
if not self._module: | ||||
Gregory Szorc
|
r26457 | head, globals, locals, after, level, modrefs = self._data | ||
FUJIWARA Katsunori
|
r19933 | mod = _hgextimport(_import, head, globals, locals, None, level) | ||
FUJIWARA Katsunori
|
r29642 | if mod is self: | ||
# In this case, _hgextimport() above should imply | ||||
# _demandimport(). Otherwise, _hgextimport() never | ||||
# returns _demandmod. This isn't intentional behavior, | ||||
# in fact. (see also issue5304 for detail) | ||||
# | ||||
# If self._module is already bound at this point, self | ||||
# should be already _load()-ed while _hgextimport(). | ||||
# Otherwise, there is no way to import actual module | ||||
# as expected, because (re-)invoking _hgextimport() | ||||
# should cause same result. | ||||
# This is reason why _load() returns without any more | ||||
# setup but assumes self to be already bound. | ||||
mod = self._module | ||||
assert mod and mod is not self, "%s, %s" % (self, mod) | ||||
return | ||||
Matt Mackall
|
r3877 | # load submodules | ||
Matt Mackall
|
r3921 | def subload(mod, p): | ||
h, t = p, None | ||||
if '.' in p: | ||||
h, t = p.split('.', 1) | ||||
Augie Fackler
|
r14977 | if getattr(mod, h, nothing) is nothing: | ||
FUJIWARA Katsunori
|
r29736 | setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__, | ||
level=1)) | ||||
Brendan Cully
|
r3926 | elif t: | ||
Matt Mackall
|
r3921 | subload(getattr(mod, h), t) | ||
Matt Mackall
|
r3877 | for x in after: | ||
Matt Mackall
|
r3921 | subload(mod, x) | ||
Gregory Szorc
|
r26457 | # Replace references to this proxy instance with the actual module. | ||
Matt Mackall
|
r3877 | if locals and locals.get(head) == self: | ||
locals[head] = mod | ||||
Gregory Szorc
|
r26457 | |||
for modname in modrefs: | ||||
modref = sys.modules.get(modname, None) | ||||
if modref and getattr(modref, head, None) == self: | ||||
setattr(modref, head, mod) | ||||
Benoit Boissinot
|
r3896 | object.__setattr__(self, "_module", mod) | ||
Matt Mackall
|
r4631 | |||
Matt Mackall
|
r3877 | def __repr__(self): | ||
Matt Mackall
|
r4631 | if self._module: | ||
return "<proxied module '%s'>" % self._data[0] | ||||
Matt Mackall
|
r3877 | return "<unloaded module '%s'>" % self._data[0] | ||
def __call__(self, *args, **kwargs): | ||||
Matt Mackall
|
r5639 | raise TypeError("%s object is not callable" % repr(self)) | ||
Brendan Cully
|
r3903 | def __getattribute__(self, attr): | ||
Gregory Szorc
|
r26457 | if attr in ('_data', '_extend', '_load', '_module', '_addref'): | ||
Brendan Cully
|
r3903 | return object.__getattribute__(self, attr) | ||
Matt Mackall
|
r3877 | self._load() | ||
return getattr(self._module, attr) | ||||
def __setattr__(self, attr, val): | ||||
self._load() | ||||
setattr(self._module, attr, val) | ||||
Bryan O'Sullivan
|
r27536 | _pypy = '__pypy__' in sys.builtin_module_names | ||
Gregory Szorc
|
r21290 | def _demandimport(name, globals=None, locals=None, fromlist=None, level=level): | ||
Matt Mackall
|
r3877 | if not locals or name in ignore or fromlist == ('*',): | ||
# these cases we can't really delay | ||||
FUJIWARA Katsunori
|
r19933 | return _hgextimport(_import, name, globals, locals, fromlist, level) | ||
Matt Mackall
|
r3877 | elif not fromlist: | ||
# import a [as b] | ||||
if '.' in name: # a.b | ||||
base, rest = name.split('.', 1) | ||||
Brendan Cully
|
r3903 | # email.__init__ loading email.mime | ||
if globals and globals.get('__name__', None) == base: | ||||
Simon Heimberg
|
r15096 | return _import(name, globals, locals, fromlist, level) | ||
Matt Mackall
|
r3877 | # if a is already demand-loaded, add b to its submodule list | ||
if base in locals: | ||||
if isinstance(locals[base], _demandmod): | ||||
locals[base]._extend(rest) | ||||
return locals[base] | ||||
FUJIWARA Katsunori
|
r19932 | return _demandmod(name, globals, locals, level) | ||
Matt Mackall
|
r3877 | else: | ||
Gregory Szorc
|
r25935 | # There is a fromlist. | ||
# from a import b,c,d | ||||
# from . import b,c,d | ||||
# from .a import b,c,d | ||||
# level == -1: relative and absolute attempted (Python 2 only). | ||||
# level >= 0: absolute only (Python 2 w/ absolute_import and Python 3). | ||||
# The modern Mercurial convention is to use absolute_import everywhere, | ||||
# so modern Mercurial code will have level >= 0. | ||||
Gregory Szorc
|
r26457 | # The name of the module the import statement is located in. | ||
globalname = globals.get('__name__') | ||||
Yuya Nishihara
|
r26873 | def processfromitem(mod, attr): | ||
Gregory Szorc
|
r26455 | """Process an imported symbol in the import statement. | ||
Yuya Nishihara
|
r30022 | If the symbol doesn't exist in the parent module, and if the | ||
parent module is a package, it must be a module. We set missing | ||||
modules up as _demandmod instances. | ||||
Gregory Szorc
|
r26455 | """ | ||
Gregory Szorc
|
r26456 | symbol = getattr(mod, attr, nothing) | ||
Yuya Nishihara
|
r30022 | nonpkg = getattr(mod, '__path__', nothing) is nothing | ||
Gregory Szorc
|
r26456 | if symbol is nothing: | ||
Yuya Nishihara
|
r30022 | if nonpkg: | ||
# do not try relative import, which would raise ValueError | ||||
raise ImportError('cannot import name %s' % attr) | ||||
Yuya Nishihara
|
r28175 | mn = '%s.%s' % (mod.__name__, attr) | ||
if mn in ignore: | ||||
importfunc = _origimport | ||||
else: | ||||
importfunc = _demandmod | ||||
symbol = importfunc(attr, mod.__dict__, locals, level=1) | ||||
Gregory Szorc
|
r26456 | setattr(mod, attr, symbol) | ||
Gregory Szorc
|
r26455 | |||
Gregory Szorc
|
r26457 | # Record the importing module references this symbol so we can | ||
# replace the symbol with the actual module instance at load | ||||
# time. | ||||
if globalname and isinstance(symbol, _demandmod): | ||||
symbol._addref(globalname) | ||||
FUJIWARA Katsunori
|
r29375 | def chainmodules(rootmod, modname): | ||
# recurse down the module chain, and return the leaf module | ||||
mod = rootmod | ||||
for comp in modname.split('.')[1:]: | ||||
if getattr(mod, comp, nothing) is nothing: | ||||
FUJIWARA Katsunori
|
r29736 | setattr(mod, comp, _demandmod(comp, mod.__dict__, | ||
mod.__dict__, level=1)) | ||||
FUJIWARA Katsunori
|
r29375 | mod = getattr(mod, comp) | ||
return mod | ||||
Gregory Szorc
|
r25935 | if level >= 0: | ||
Gregory Szorc
|
r25937 | if name: | ||
FUJIWARA Katsunori
|
r29375 | # "from a import b" or "from .a import b" style | ||
rootmod = _hgextimport(_origimport, name, globals, locals, | ||||
level=level) | ||||
mod = chainmodules(rootmod, name) | ||||
elif _pypy: | ||||
Bryan O'Sullivan
|
r27536 | # PyPy's __import__ throws an exception if invoked | ||
# with an empty name and no fromlist. Recreate the | ||||
# desired behaviour by hand. | ||||
mn = globalname | ||||
mod = sys.modules[mn] | ||||
if getattr(mod, '__path__', nothing) is nothing: | ||||
mn = mn.rsplit('.', 1)[0] | ||||
mod = sys.modules[mn] | ||||
if level > 1: | ||||
mn = mn.rsplit('.', level - 1)[0] | ||||
mod = sys.modules[mn] | ||||
else: | ||||
mod = _hgextimport(_origimport, name, globals, locals, | ||||
level=level) | ||||
Gregory Szorc
|
r26455 | |||
Gregory Szorc
|
r25937 | for x in fromlist: | ||
Yuya Nishihara
|
r26873 | processfromitem(mod, x) | ||
Gregory Szorc
|
r25937 | |||
return mod | ||||
Gregory Szorc
|
r25935 | |||
# But, we still need to support lazy loading of standard library and 3rd | ||||
# party modules. So handle level == -1. | ||||
FUJIWARA Katsunori
|
r19933 | mod = _hgextimport(_origimport, name, globals, locals) | ||
FUJIWARA Katsunori
|
r29375 | mod = chainmodules(mod, name) | ||
Gregory Szorc
|
r26455 | |||
Matt Mackall
|
r3877 | for x in fromlist: | ||
Gregory Szorc
|
r26455 | processfromitem(mod, x) | ||
Matt Mackall
|
r3877 | return mod | ||
Patrick Mezard
|
r5098 | ignore = [ | ||
Gregory Szorc
|
r25934 | '__future__', | ||
Patrick Mezard
|
r5098 | '_hashlib', | ||
Gregory Szorc
|
r28252 | # ImportError during pkg_resources/__init__.py:fixup_namespace_package | ||
'_imp', | ||||
Patrick Mezard
|
r5098 | '_xmlplus', | ||
'fcntl', | ||||
Yuya Nishihara
|
r30021 | 'nt', # pathlib2 tests the existence of built-in 'nt' module | ||
Patrick Mezard
|
r5098 | 'win32com.gen_py', | ||
Dirkjan Ochtman
|
r10242 | '_winreg', # 2.7 mimetypes needs immediate ImportError | ||
Steve Borho
|
r7861 | 'pythoncom', | ||
Patrick Mezard
|
r5098 | # imported by tarfile, not available under Windows | ||
'pwd', | ||||
'grp', | ||||
# imported by profile, itself imported by hotshot.stats, | ||||
# not available under Windows | ||||
'resource', | ||||
Steve Borho
|
r9458 | # this trips up many extension authors | ||
'gtk', | ||||
Greg Ward
|
r10598 | # setuptools' pkg_resources.py expects "from __main__ import x" to | ||
# raise ImportError if x not defined | ||||
'__main__', | ||||
Dirkjan Ochtman
|
r10612 | '_ssl', # conditional imports in the stdlib, issue1964 | ||
GƔbor Stefanik
|
r26830 | '_sre', # issue4920 | ||
Augie Fackler
|
r14976 | 'rfc822', | ||
'mimetools', | ||||
Yuya Nishihara
|
r28176 | 'sqlalchemy.events', # has import-time side effects (issue5085) | ||
Augie Fackler
|
r23643 | # setuptools 8 expects this module to explode early when not on windows | ||
timeless
|
r29980 | 'distutils.msvc9compiler', | ||
Patrick Mezard
|
r5098 | ] | ||
Matt Mackall
|
r3877 | |||
Yuya Nishihara
|
r30020 | if _pypy: | ||
ignore.extend([ | ||||
# _ctypes.pointer is shadowed by "from ... import pointer" (PyPy 5) | ||||
'_ctypes.pointer', | ||||
]) | ||||
Brodie Rao
|
r20422 | def isenabled(): | ||
Gregory Szorc
|
r25673 | return builtins.__import__ == _demandimport | ||
Brodie Rao
|
r20422 | |||
Matt Mackall
|
r3877 | def enable(): | ||
"enable global demand-loading of modules" | ||||
Mads Kiilerich
|
r21025 | if os.environ.get('HGDEMANDIMPORT') != 'disable': | ||
Gregory Szorc
|
r25673 | builtins.__import__ = _demandimport | ||
Matt Mackall
|
r3877 | |||
def disable(): | ||||
"disable global demand-loading of modules" | ||||
Gregory Szorc
|
r25673 | builtins.__import__ = _origimport | ||
Jordi GutiƩrrez Hermoso
|
r25327 | |||
@contextmanager | ||||
def deactivated(): | ||||
"context manager for disabling demandimport in 'with' blocks" | ||||
demandenabled = isenabled() | ||||
if demandenabled: | ||||
disable() | ||||
try: | ||||
yield | ||||
finally: | ||||
if demandenabled: | ||||
enable() | ||||