##// END OF EJS Templates
merge: default into stable for release candidate
merge: default into stable for release candidate

File last commit:

r39292:574e1d3b default
r42322:4a8d9ed8 merge 5.0rc0 stable
Show More
demandimportpy2.py
309 lines | 10.9 KiB | text/x-python | PythonLexer
Siddharth Agarwal
demandimport: move to separate package...
r32420 # demandimport.py - global demand-loading of modules for Mercurial
#
# Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
'''
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)
'''
from __future__ import absolute_import
Yuya Nishihara
demandimport: drop Py3 workarounds from Py2 implementation
r33530 import __builtin__ as builtins
Siddharth Agarwal
demandimport: move to separate package...
r32420 import contextlib
import sys
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 from . import tracing
Siddharth Agarwal
demandimport: move to separate package...
r32420 contextmanager = contextlib.contextmanager
_origimport = __import__
nothing = object()
def _hgextimport(importfunc, name, globals, *args, **kwargs):
try:
return importfunc(name, globals, *args, **kwargs)
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
return importfunc(hgextname, globals, *args, **kwargs)
class _demandmod(object):
"""module demand-loader and proxy
Specify 1 as 'level' argument at construction, to import module
relatively.
"""
Yuya Nishihara
demandimport: insert empty line per method...
r32446
Siddharth Agarwal
demandimport: move to separate package...
r32420 def __init__(self, name, globals, locals, level):
if '.' in name:
head, rest = name.split('.', 1)
after = [rest]
else:
head = name
after = []
object.__setattr__(self, r"_data",
(head, globals, locals, after, level, set()))
object.__setattr__(self, r"_module", None)
Yuya Nishihara
demandimport: insert empty line per method...
r32446
Siddharth Agarwal
demandimport: move to separate package...
r32420 def _extend(self, name):
"""add to the list of submodules to load"""
self._data[3].append(name)
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)
def _load(self):
if not self._module:
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 with tracing.log('demandimport %s', self._data[0]):
head, globals, locals, after, level, modrefs = self._data
mod = _hgextimport(
_origimport, head, globals, locals, None, level)
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
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 # load submodules
def subload(mod, p):
h, t = p, None
if '.' in p:
h, t = p.split('.', 1)
if getattr(mod, h, nothing) is nothing:
setattr(mod, h, _demandmod(
p, mod.__dict__, mod.__dict__, level=1))
elif t:
subload(getattr(mod, h), t)
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 for x in after:
subload(mod, x)
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 # Replace references to this proxy instance with the
# actual module.
if locals:
if locals.get(head) is self:
locals[head] = mod
elif locals.get(head + r'mod') is self:
locals[head + r'mod'] = mod
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 for modname in modrefs:
modref = sys.modules.get(modname, None)
if modref and getattr(modref, head, None) is self:
setattr(modref, head, mod)
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 object.__setattr__(self, r"_module", mod)
Siddharth Agarwal
demandimport: move to separate package...
r32420
def __repr__(self):
if self._module:
return "<proxied module '%s'>" % self._data[0]
return "<unloaded module '%s'>" % self._data[0]
Yuya Nishihara
demandimport: insert empty line per method...
r32446
Siddharth Agarwal
demandimport: move to separate package...
r32420 def __call__(self, *args, **kwargs):
raise TypeError("%s object is not callable" % repr(self))
Yuya Nishihara
demandimport: insert empty line per method...
r32446
Yuya Nishihara
demandimport: stop overriding __getattribute__()...
r32448 def __getattr__(self, attr):
Siddharth Agarwal
demandimport: move to separate package...
r32420 self._load()
return getattr(self._module, attr)
Yuya Nishihara
demandimport: insert empty line per method...
r32446
Siddharth Agarwal
demandimport: move to separate package...
r32420 def __setattr__(self, attr, val):
self._load()
setattr(self._module, attr, val)
Yuya Nishihara
demandimport: stop overriding __getattribute__()...
r32448 @property
def __dict__(self):
self._load()
return self._module.__dict__
@property
def __doc__(self):
self._load()
return self._module.__doc__
Siddharth Agarwal
demandimport: move to separate package...
r32420 _pypy = '__pypy__' in sys.builtin_module_names
Yuya Nishihara
demandimport: drop Py3 workarounds from Py2 implementation
r33530 def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1):
Gregory Szorc
demandimport: make module ignores a set (API)...
r37862 if locals is None or name in ignores or fromlist == ('*',):
Siddharth Agarwal
demandimport: move to separate package...
r32420 # these cases we can't really delay
Yuya Nishihara
demandimport: drop hack for old Pythons which had no level argument...
r33529 return _hgextimport(_origimport, name, globals, locals, fromlist, level)
Siddharth Agarwal
demandimport: move to separate package...
r32420 elif not fromlist:
# import a [as b]
if '.' in name: # a.b
base, rest = name.split('.', 1)
# email.__init__ loading email.mime
if globals and globals.get('__name__', None) == base:
Yuya Nishihara
demandimport: drop hack for old Pythons which had no level argument...
r33529 return _origimport(name, globals, locals, fromlist, level)
Siddharth Agarwal
demandimport: move to separate package...
r32420 # 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]
return _demandmod(name, globals, locals, level)
else:
# 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.
# The name of the module the import statement is located in.
globalname = globals.get('__name__')
def processfromitem(mod, attr):
"""Process an imported symbol in the import statement.
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.
"""
symbol = getattr(mod, attr, nothing)
nonpkg = getattr(mod, '__path__', nothing) is nothing
if symbol is nothing:
if nonpkg:
# do not try relative import, which would raise ValueError,
# and leave unknown attribute as the default __import__()
# would do. the missing attribute will be detected later
# while processing the import statement.
return
mn = '%s.%s' % (mod.__name__, attr)
Gregory Szorc
demandimport: make module ignores a set (API)...
r37862 if mn in ignores:
Siddharth Agarwal
demandimport: move to separate package...
r32420 importfunc = _origimport
else:
importfunc = _demandmod
symbol = importfunc(attr, mod.__dict__, locals, level=1)
setattr(mod, attr, symbol)
# 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)
def chainmodules(rootmod, modname):
# recurse down the module chain, and return the leaf module
mod = rootmod
for comp in modname.split('.')[1:]:
Yuya Nishihara
demandimport: prefer loaded module over package attribute (issue5617)...
r33531 obj = getattr(mod, comp, nothing)
if obj is nothing:
obj = _demandmod(comp, mod.__dict__, mod.__dict__, level=1)
setattr(mod, comp, obj)
elif mod.__name__ + '.' + comp in sys.modules:
# prefer loaded module over attribute (issue5617)
obj = sys.modules[mod.__name__ + '.' + comp]
mod = obj
Siddharth Agarwal
demandimport: move to separate package...
r32420 return mod
if level >= 0:
if name:
# "from a import b" or "from .a import b" style
rootmod = _hgextimport(_origimport, name, globals, locals,
level=level)
mod = chainmodules(rootmod, name)
elif _pypy:
# 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)
for x in fromlist:
processfromitem(mod, x)
return mod
# But, we still need to support lazy loading of standard library and 3rd
# party modules. So handle level == -1.
mod = _hgextimport(_origimport, name, globals, locals)
mod = chainmodules(mod, name)
for x in fromlist:
processfromitem(mod, x)
return mod
Gregory Szorc
demandimport: make module ignores a set (API)...
r37862 ignores = set()
Siddharth Agarwal
demandimport: move to separate package...
r32420
Gregory Szorc
demandimport: make module ignores a set (API)...
r37862 def init(ignoreset):
global ignores
ignores = ignoreset
Siddharth Agarwal
demandimport: move to separate package...
r32420
def isenabled():
return builtins.__import__ == _demandimport
def enable():
"enable global demand-loading of modules"
Jun Wu
demandimport: move HGDEMANDIMPORT test to __init__.py...
r33861 builtins.__import__ = _demandimport
Siddharth Agarwal
demandimport: move to separate package...
r32420
def disable():
"disable global demand-loading of modules"
builtins.__import__ = _origimport
@contextmanager
def deactivated():
"context manager for disabling demandimport in 'with' blocks"
demandenabled = isenabled()
if demandenabled:
disable()
try:
yield
finally:
if demandenabled:
enable()