##// END OF EJS Templates
demandimport: omit default value of "level" at construction of _demandmod...
FUJIWARA Katsunori -
r29737:ae9a4d6a default
parent child Browse files
Show More
@@ -1,309 +1,313
1 # demandimport.py - global demand-loading of modules for Mercurial
1 # demandimport.py - global demand-loading of modules for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''
8 '''
9 demandimport - automatic demandloading of modules
9 demandimport - automatic demandloading of modules
10
10
11 To enable this module, do:
11 To enable this module, do:
12
12
13 import demandimport; demandimport.enable()
13 import demandimport; demandimport.enable()
14
14
15 Imports of the following forms will be demand-loaded:
15 Imports of the following forms will be demand-loaded:
16
16
17 import a, b.c
17 import a, b.c
18 import a.b as c
18 import a.b as c
19 from a import b,c # a will be loaded immediately
19 from a import b,c # a will be loaded immediately
20
20
21 These imports will not be delayed:
21 These imports will not be delayed:
22
22
23 from a import *
23 from a import *
24 b = __import__(a)
24 b = __import__(a)
25 '''
25 '''
26
26
27 from __future__ import absolute_import
27 from __future__ import absolute_import
28
28
29 import contextlib
29 import contextlib
30 import os
30 import os
31 import sys
31 import sys
32
32
33 # __builtin__ in Python 2, builtins in Python 3.
33 # __builtin__ in Python 2, builtins in Python 3.
34 try:
34 try:
35 import __builtin__ as builtins
35 import __builtin__ as builtins
36 except ImportError:
36 except ImportError:
37 import builtins
37 import builtins
38
38
39 contextmanager = contextlib.contextmanager
39 contextmanager = contextlib.contextmanager
40
40
41 _origimport = __import__
41 _origimport = __import__
42
42
43 nothing = object()
43 nothing = object()
44
44
45 # Python 3 doesn't have relative imports nor level -1.
45 # Python 3 doesn't have relative imports nor level -1.
46 level = -1
46 level = -1
47 if sys.version_info[0] >= 3:
47 if sys.version_info[0] >= 3:
48 level = 0
48 level = 0
49 _import = _origimport
49 _import = _origimport
50
50
51 def _hgextimport(importfunc, name, globals, *args, **kwargs):
51 def _hgextimport(importfunc, name, globals, *args, **kwargs):
52 try:
52 try:
53 return importfunc(name, globals, *args, **kwargs)
53 return importfunc(name, globals, *args, **kwargs)
54 except ImportError:
54 except ImportError:
55 if not globals:
55 if not globals:
56 raise
56 raise
57 # extensions are loaded with "hgext_" prefix
57 # extensions are loaded with "hgext_" prefix
58 hgextname = 'hgext_%s' % name
58 hgextname = 'hgext_%s' % name
59 nameroot = hgextname.split('.', 1)[0]
59 nameroot = hgextname.split('.', 1)[0]
60 contextroot = globals.get('__name__', '').split('.', 1)[0]
60 contextroot = globals.get('__name__', '').split('.', 1)[0]
61 if nameroot != contextroot:
61 if nameroot != contextroot:
62 raise
62 raise
63 # retry to import with "hgext_" prefix
63 # retry to import with "hgext_" prefix
64 return importfunc(hgextname, globals, *args, **kwargs)
64 return importfunc(hgextname, globals, *args, **kwargs)
65
65
66 class _demandmod(object):
66 class _demandmod(object):
67 """module demand-loader and proxy"""
67 """module demand-loader and proxy
68 def __init__(self, name, globals, locals, level=level):
68
69 Specify 1 as 'level' argument at construction, to import module
70 relatively.
71 """
72 def __init__(self, name, globals, locals, level):
69 if '.' in name:
73 if '.' in name:
70 head, rest = name.split('.', 1)
74 head, rest = name.split('.', 1)
71 after = [rest]
75 after = [rest]
72 else:
76 else:
73 head = name
77 head = name
74 after = []
78 after = []
75 object.__setattr__(self, "_data",
79 object.__setattr__(self, "_data",
76 (head, globals, locals, after, level, set()))
80 (head, globals, locals, after, level, set()))
77 object.__setattr__(self, "_module", None)
81 object.__setattr__(self, "_module", None)
78 def _extend(self, name):
82 def _extend(self, name):
79 """add to the list of submodules to load"""
83 """add to the list of submodules to load"""
80 self._data[3].append(name)
84 self._data[3].append(name)
81
85
82 def _addref(self, name):
86 def _addref(self, name):
83 """Record that the named module ``name`` imports this module.
87 """Record that the named module ``name`` imports this module.
84
88
85 References to this proxy class having the name of this module will be
89 References to this proxy class having the name of this module will be
86 replaced at module load time. We assume the symbol inside the importing
90 replaced at module load time. We assume the symbol inside the importing
87 module is identical to the "head" name of this module. We don't
91 module is identical to the "head" name of this module. We don't
88 actually know if "as X" syntax is being used to change the symbol name
92 actually know if "as X" syntax is being used to change the symbol name
89 because this information isn't exposed to __import__.
93 because this information isn't exposed to __import__.
90 """
94 """
91 self._data[5].add(name)
95 self._data[5].add(name)
92
96
93 def _load(self):
97 def _load(self):
94 if not self._module:
98 if not self._module:
95 head, globals, locals, after, level, modrefs = self._data
99 head, globals, locals, after, level, modrefs = self._data
96 mod = _hgextimport(_import, head, globals, locals, None, level)
100 mod = _hgextimport(_import, head, globals, locals, None, level)
97 if mod is self:
101 if mod is self:
98 # In this case, _hgextimport() above should imply
102 # In this case, _hgextimport() above should imply
99 # _demandimport(). Otherwise, _hgextimport() never
103 # _demandimport(). Otherwise, _hgextimport() never
100 # returns _demandmod. This isn't intentional behavior,
104 # returns _demandmod. This isn't intentional behavior,
101 # in fact. (see also issue5304 for detail)
105 # in fact. (see also issue5304 for detail)
102 #
106 #
103 # If self._module is already bound at this point, self
107 # If self._module is already bound at this point, self
104 # should be already _load()-ed while _hgextimport().
108 # should be already _load()-ed while _hgextimport().
105 # Otherwise, there is no way to import actual module
109 # Otherwise, there is no way to import actual module
106 # as expected, because (re-)invoking _hgextimport()
110 # as expected, because (re-)invoking _hgextimport()
107 # should cause same result.
111 # should cause same result.
108 # This is reason why _load() returns without any more
112 # This is reason why _load() returns without any more
109 # setup but assumes self to be already bound.
113 # setup but assumes self to be already bound.
110 mod = self._module
114 mod = self._module
111 assert mod and mod is not self, "%s, %s" % (self, mod)
115 assert mod and mod is not self, "%s, %s" % (self, mod)
112 return
116 return
113
117
114 # load submodules
118 # load submodules
115 def subload(mod, p):
119 def subload(mod, p):
116 h, t = p, None
120 h, t = p, None
117 if '.' in p:
121 if '.' in p:
118 h, t = p.split('.', 1)
122 h, t = p.split('.', 1)
119 if getattr(mod, h, nothing) is nothing:
123 if getattr(mod, h, nothing) is nothing:
120 setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__,
124 setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__,
121 level=1))
125 level=1))
122 elif t:
126 elif t:
123 subload(getattr(mod, h), t)
127 subload(getattr(mod, h), t)
124
128
125 for x in after:
129 for x in after:
126 subload(mod, x)
130 subload(mod, x)
127
131
128 # Replace references to this proxy instance with the actual module.
132 # Replace references to this proxy instance with the actual module.
129 if locals and locals.get(head) == self:
133 if locals and locals.get(head) == self:
130 locals[head] = mod
134 locals[head] = mod
131
135
132 for modname in modrefs:
136 for modname in modrefs:
133 modref = sys.modules.get(modname, None)
137 modref = sys.modules.get(modname, None)
134 if modref and getattr(modref, head, None) == self:
138 if modref and getattr(modref, head, None) == self:
135 setattr(modref, head, mod)
139 setattr(modref, head, mod)
136
140
137 object.__setattr__(self, "_module", mod)
141 object.__setattr__(self, "_module", mod)
138
142
139 def __repr__(self):
143 def __repr__(self):
140 if self._module:
144 if self._module:
141 return "<proxied module '%s'>" % self._data[0]
145 return "<proxied module '%s'>" % self._data[0]
142 return "<unloaded module '%s'>" % self._data[0]
146 return "<unloaded module '%s'>" % self._data[0]
143 def __call__(self, *args, **kwargs):
147 def __call__(self, *args, **kwargs):
144 raise TypeError("%s object is not callable" % repr(self))
148 raise TypeError("%s object is not callable" % repr(self))
145 def __getattribute__(self, attr):
149 def __getattribute__(self, attr):
146 if attr in ('_data', '_extend', '_load', '_module', '_addref'):
150 if attr in ('_data', '_extend', '_load', '_module', '_addref'):
147 return object.__getattribute__(self, attr)
151 return object.__getattribute__(self, attr)
148 self._load()
152 self._load()
149 return getattr(self._module, attr)
153 return getattr(self._module, attr)
150 def __setattr__(self, attr, val):
154 def __setattr__(self, attr, val):
151 self._load()
155 self._load()
152 setattr(self._module, attr, val)
156 setattr(self._module, attr, val)
153
157
154 _pypy = '__pypy__' in sys.builtin_module_names
158 _pypy = '__pypy__' in sys.builtin_module_names
155
159
156 def _demandimport(name, globals=None, locals=None, fromlist=None, level=level):
160 def _demandimport(name, globals=None, locals=None, fromlist=None, level=level):
157 if not locals or name in ignore or fromlist == ('*',):
161 if not locals or name in ignore or fromlist == ('*',):
158 # these cases we can't really delay
162 # these cases we can't really delay
159 return _hgextimport(_import, name, globals, locals, fromlist, level)
163 return _hgextimport(_import, name, globals, locals, fromlist, level)
160 elif not fromlist:
164 elif not fromlist:
161 # import a [as b]
165 # import a [as b]
162 if '.' in name: # a.b
166 if '.' in name: # a.b
163 base, rest = name.split('.', 1)
167 base, rest = name.split('.', 1)
164 # email.__init__ loading email.mime
168 # email.__init__ loading email.mime
165 if globals and globals.get('__name__', None) == base:
169 if globals and globals.get('__name__', None) == base:
166 return _import(name, globals, locals, fromlist, level)
170 return _import(name, globals, locals, fromlist, level)
167 # if a is already demand-loaded, add b to its submodule list
171 # if a is already demand-loaded, add b to its submodule list
168 if base in locals:
172 if base in locals:
169 if isinstance(locals[base], _demandmod):
173 if isinstance(locals[base], _demandmod):
170 locals[base]._extend(rest)
174 locals[base]._extend(rest)
171 return locals[base]
175 return locals[base]
172 return _demandmod(name, globals, locals, level)
176 return _demandmod(name, globals, locals, level)
173 else:
177 else:
174 # There is a fromlist.
178 # There is a fromlist.
175 # from a import b,c,d
179 # from a import b,c,d
176 # from . import b,c,d
180 # from . import b,c,d
177 # from .a import b,c,d
181 # from .a import b,c,d
178
182
179 # level == -1: relative and absolute attempted (Python 2 only).
183 # level == -1: relative and absolute attempted (Python 2 only).
180 # level >= 0: absolute only (Python 2 w/ absolute_import and Python 3).
184 # level >= 0: absolute only (Python 2 w/ absolute_import and Python 3).
181 # The modern Mercurial convention is to use absolute_import everywhere,
185 # The modern Mercurial convention is to use absolute_import everywhere,
182 # so modern Mercurial code will have level >= 0.
186 # so modern Mercurial code will have level >= 0.
183
187
184 # The name of the module the import statement is located in.
188 # The name of the module the import statement is located in.
185 globalname = globals.get('__name__')
189 globalname = globals.get('__name__')
186
190
187 def processfromitem(mod, attr):
191 def processfromitem(mod, attr):
188 """Process an imported symbol in the import statement.
192 """Process an imported symbol in the import statement.
189
193
190 If the symbol doesn't exist in the parent module, it must be a
194 If the symbol doesn't exist in the parent module, it must be a
191 module. We set missing modules up as _demandmod instances.
195 module. We set missing modules up as _demandmod instances.
192 """
196 """
193 symbol = getattr(mod, attr, nothing)
197 symbol = getattr(mod, attr, nothing)
194 if symbol is nothing:
198 if symbol is nothing:
195 mn = '%s.%s' % (mod.__name__, attr)
199 mn = '%s.%s' % (mod.__name__, attr)
196 if mn in ignore:
200 if mn in ignore:
197 importfunc = _origimport
201 importfunc = _origimport
198 else:
202 else:
199 importfunc = _demandmod
203 importfunc = _demandmod
200 symbol = importfunc(attr, mod.__dict__, locals, level=1)
204 symbol = importfunc(attr, mod.__dict__, locals, level=1)
201 setattr(mod, attr, symbol)
205 setattr(mod, attr, symbol)
202
206
203 # Record the importing module references this symbol so we can
207 # Record the importing module references this symbol so we can
204 # replace the symbol with the actual module instance at load
208 # replace the symbol with the actual module instance at load
205 # time.
209 # time.
206 if globalname and isinstance(symbol, _demandmod):
210 if globalname and isinstance(symbol, _demandmod):
207 symbol._addref(globalname)
211 symbol._addref(globalname)
208
212
209 def chainmodules(rootmod, modname):
213 def chainmodules(rootmod, modname):
210 # recurse down the module chain, and return the leaf module
214 # recurse down the module chain, and return the leaf module
211 mod = rootmod
215 mod = rootmod
212 for comp in modname.split('.')[1:]:
216 for comp in modname.split('.')[1:]:
213 if getattr(mod, comp, nothing) is nothing:
217 if getattr(mod, comp, nothing) is nothing:
214 setattr(mod, comp, _demandmod(comp, mod.__dict__,
218 setattr(mod, comp, _demandmod(comp, mod.__dict__,
215 mod.__dict__, level=1))
219 mod.__dict__, level=1))
216 mod = getattr(mod, comp)
220 mod = getattr(mod, comp)
217 return mod
221 return mod
218
222
219 if level >= 0:
223 if level >= 0:
220 if name:
224 if name:
221 # "from a import b" or "from .a import b" style
225 # "from a import b" or "from .a import b" style
222 rootmod = _hgextimport(_origimport, name, globals, locals,
226 rootmod = _hgextimport(_origimport, name, globals, locals,
223 level=level)
227 level=level)
224 mod = chainmodules(rootmod, name)
228 mod = chainmodules(rootmod, name)
225 elif _pypy:
229 elif _pypy:
226 # PyPy's __import__ throws an exception if invoked
230 # PyPy's __import__ throws an exception if invoked
227 # with an empty name and no fromlist. Recreate the
231 # with an empty name and no fromlist. Recreate the
228 # desired behaviour by hand.
232 # desired behaviour by hand.
229 mn = globalname
233 mn = globalname
230 mod = sys.modules[mn]
234 mod = sys.modules[mn]
231 if getattr(mod, '__path__', nothing) is nothing:
235 if getattr(mod, '__path__', nothing) is nothing:
232 mn = mn.rsplit('.', 1)[0]
236 mn = mn.rsplit('.', 1)[0]
233 mod = sys.modules[mn]
237 mod = sys.modules[mn]
234 if level > 1:
238 if level > 1:
235 mn = mn.rsplit('.', level - 1)[0]
239 mn = mn.rsplit('.', level - 1)[0]
236 mod = sys.modules[mn]
240 mod = sys.modules[mn]
237 else:
241 else:
238 mod = _hgextimport(_origimport, name, globals, locals,
242 mod = _hgextimport(_origimport, name, globals, locals,
239 level=level)
243 level=level)
240
244
241 for x in fromlist:
245 for x in fromlist:
242 processfromitem(mod, x)
246 processfromitem(mod, x)
243
247
244 return mod
248 return mod
245
249
246 # But, we still need to support lazy loading of standard library and 3rd
250 # But, we still need to support lazy loading of standard library and 3rd
247 # party modules. So handle level == -1.
251 # party modules. So handle level == -1.
248 mod = _hgextimport(_origimport, name, globals, locals)
252 mod = _hgextimport(_origimport, name, globals, locals)
249 mod = chainmodules(mod, name)
253 mod = chainmodules(mod, name)
250
254
251 for x in fromlist:
255 for x in fromlist:
252 processfromitem(mod, x)
256 processfromitem(mod, x)
253
257
254 return mod
258 return mod
255
259
256 ignore = [
260 ignore = [
257 '__future__',
261 '__future__',
258 '_hashlib',
262 '_hashlib',
259 # ImportError during pkg_resources/__init__.py:fixup_namespace_package
263 # ImportError during pkg_resources/__init__.py:fixup_namespace_package
260 '_imp',
264 '_imp',
261 '_xmlplus',
265 '_xmlplus',
262 'fcntl',
266 'fcntl',
263 'win32com.gen_py',
267 'win32com.gen_py',
264 '_winreg', # 2.7 mimetypes needs immediate ImportError
268 '_winreg', # 2.7 mimetypes needs immediate ImportError
265 'pythoncom',
269 'pythoncom',
266 # imported by tarfile, not available under Windows
270 # imported by tarfile, not available under Windows
267 'pwd',
271 'pwd',
268 'grp',
272 'grp',
269 # imported by profile, itself imported by hotshot.stats,
273 # imported by profile, itself imported by hotshot.stats,
270 # not available under Windows
274 # not available under Windows
271 'resource',
275 'resource',
272 # this trips up many extension authors
276 # this trips up many extension authors
273 'gtk',
277 'gtk',
274 # setuptools' pkg_resources.py expects "from __main__ import x" to
278 # setuptools' pkg_resources.py expects "from __main__ import x" to
275 # raise ImportError if x not defined
279 # raise ImportError if x not defined
276 '__main__',
280 '__main__',
277 '_ssl', # conditional imports in the stdlib, issue1964
281 '_ssl', # conditional imports in the stdlib, issue1964
278 '_sre', # issue4920
282 '_sre', # issue4920
279 'rfc822',
283 'rfc822',
280 'mimetools',
284 'mimetools',
281 'sqlalchemy.events', # has import-time side effects (issue5085)
285 'sqlalchemy.events', # has import-time side effects (issue5085)
282 # setuptools 8 expects this module to explode early when not on windows
286 # setuptools 8 expects this module to explode early when not on windows
283 'distutils.msvc9compiler'
287 'distutils.msvc9compiler'
284 ]
288 ]
285
289
286 def isenabled():
290 def isenabled():
287 return builtins.__import__ == _demandimport
291 return builtins.__import__ == _demandimport
288
292
289 def enable():
293 def enable():
290 "enable global demand-loading of modules"
294 "enable global demand-loading of modules"
291 if os.environ.get('HGDEMANDIMPORT') != 'disable':
295 if os.environ.get('HGDEMANDIMPORT') != 'disable':
292 builtins.__import__ = _demandimport
296 builtins.__import__ = _demandimport
293
297
294 def disable():
298 def disable():
295 "disable global demand-loading of modules"
299 "disable global demand-loading of modules"
296 builtins.__import__ = _origimport
300 builtins.__import__ = _origimport
297
301
298 @contextmanager
302 @contextmanager
299 def deactivated():
303 def deactivated():
300 "context manager for disabling demandimport in 'with' blocks"
304 "context manager for disabling demandimport in 'with' blocks"
301 demandenabled = isenabled()
305 demandenabled = isenabled()
302 if demandenabled:
306 if demandenabled:
303 disable()
307 disable()
304
308
305 try:
309 try:
306 yield
310 yield
307 finally:
311 finally:
308 if demandenabled:
312 if demandenabled:
309 enable()
313 enable()
General Comments 0
You need to be logged in to leave comments. Login now