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