demandimport.py
230 lines
| 7.6 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): | ||
"""module demand-loader and proxy""" | ||||
Gregory Szorc
|
r21290 | def __init__(self, name, globals, locals, level=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", | ||
(head, globals, locals, after, level)) | ||||
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) | ||||
def _load(self): | ||||
if not self._module: | ||||
FUJIWARA Katsunori
|
r19932 | head, globals, locals, after, level = self._data | ||
FUJIWARA Katsunori
|
r19933 | mod = _hgextimport(_import, head, globals, locals, None, level) | ||
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: | ||
Matt Mackall
|
r12894 | setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__)) | ||
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) | ||
Matt Mackall
|
r3877 | # are we in the locals dictionary still? | ||
if locals and locals.get(head) == self: | ||||
locals[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): | ||
if attr in ('_data', '_extend', '_load', '_module'): | ||||
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) | ||||
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. | ||||
if level >= 0: | ||||
Gregory Szorc
|
r25937 | # Mercurial's enforced import style does not use | ||
# "from a import b,c,d" or "from .a import b,c,d" syntax. In | ||||
# addition, this appears to be giving errors with some modules | ||||
# for unknown reasons. Since we shouldn't be using this syntax | ||||
# much, work around the problems. | ||||
if name: | ||||
return _hgextimport(_origimport, name, globals, locals, | ||||
fromlist, level) | ||||
mod = _hgextimport(_origimport, name, globals, locals, level=level) | ||||
for x in fromlist: | ||||
# Missing symbols mean they weren't defined in the module | ||||
# itself which means they are sub-modules. | ||||
if getattr(mod, x, nothing) is nothing: | ||||
setattr(mod, x, | ||||
_demandmod(x, mod.__dict__, locals, level=level)) | ||||
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) | ||
Matt Mackall
|
r3877 | # recurse down the module chain | ||
for comp in name.split('.')[1:]: | ||||
Augie Fackler
|
r14977 | if getattr(mod, comp, nothing) is nothing: | ||
Gregory Szorc
|
r25935 | setattr(mod, comp, | ||
_demandmod(comp, mod.__dict__, mod.__dict__)) | ||||
Matt Mackall
|
r3877 | mod = getattr(mod, comp) | ||
for x in fromlist: | ||||
# set requested submodules for demand load | ||||
Augie Fackler
|
r14977 | if getattr(mod, x, nothing) is nothing: | ||
Matt Mackall
|
r12894 | setattr(mod, x, _demandmod(x, mod.__dict__, locals)) | ||
Matt Mackall
|
r3877 | return mod | ||
Patrick Mezard
|
r5098 | ignore = [ | ||
Gregory Szorc
|
r25934 | '__future__', | ||
Patrick Mezard
|
r5098 | '_hashlib', | ||
'_xmlplus', | ||||
'fcntl', | ||||
'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 | ||
Augie Fackler
|
r14976 | 'rfc822', | ||
'mimetools', | ||||
Augie Fackler
|
r23643 | # setuptools 8 expects this module to explode early when not on windows | ||
'distutils.msvc9compiler' | ||||
Patrick Mezard
|
r5098 | ] | ||
Matt Mackall
|
r3877 | |||
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() | ||||