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