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