##// END OF EJS Templates
demandimport: look for 'mod' suffix as alternative name for module reference...
Yuya Nishihara -
r32447:252d2260 default
parent child Browse files
Show More
@@ -1,302 +1,305 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
69 69 Specify 1 as 'level' argument at construction, to import module
70 70 relatively.
71 71 """
72 72
73 73 def __init__(self, name, globals, locals, level):
74 74 if '.' in name:
75 75 head, rest = name.split('.', 1)
76 76 after = [rest]
77 77 else:
78 78 head = name
79 79 after = []
80 80 object.__setattr__(self, r"_data",
81 81 (head, globals, locals, after, level, set()))
82 82 object.__setattr__(self, r"_module", None)
83 83
84 84 def _extend(self, name):
85 85 """add to the list of submodules to load"""
86 86 self._data[3].append(name)
87 87
88 88 def _addref(self, name):
89 89 """Record that the named module ``name`` imports this module.
90 90
91 91 References to this proxy class having the name of this module will be
92 92 replaced at module load time. We assume the symbol inside the importing
93 93 module is identical to the "head" name of this module. We don't
94 94 actually know if "as X" syntax is being used to change the symbol name
95 95 because this information isn't exposed to __import__.
96 96 """
97 97 self._data[5].add(name)
98 98
99 99 def _load(self):
100 100 if not self._module:
101 101 head, globals, locals, after, level, modrefs = self._data
102 102 mod = _hgextimport(_import, head, globals, locals, None, level)
103 103 if mod is self:
104 104 # In this case, _hgextimport() above should imply
105 105 # _demandimport(). Otherwise, _hgextimport() never
106 106 # returns _demandmod. This isn't intentional behavior,
107 107 # in fact. (see also issue5304 for detail)
108 108 #
109 109 # If self._module is already bound at this point, self
110 110 # should be already _load()-ed while _hgextimport().
111 111 # Otherwise, there is no way to import actual module
112 112 # as expected, because (re-)invoking _hgextimport()
113 113 # should cause same result.
114 114 # This is reason why _load() returns without any more
115 115 # setup but assumes self to be already bound.
116 116 mod = self._module
117 117 assert mod and mod is not self, "%s, %s" % (self, mod)
118 118 return
119 119
120 120 # load submodules
121 121 def subload(mod, p):
122 122 h, t = p, None
123 123 if '.' in p:
124 124 h, t = p.split('.', 1)
125 125 if getattr(mod, h, nothing) is nothing:
126 126 setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__,
127 127 level=1))
128 128 elif t:
129 129 subload(getattr(mod, h), t)
130 130
131 131 for x in after:
132 132 subload(mod, x)
133 133
134 134 # Replace references to this proxy instance with the actual module.
135 if locals and locals.get(head) is self:
136 locals[head] = mod
135 if locals:
136 if locals.get(head) is self:
137 locals[head] = mod
138 elif locals.get(head + r'mod') is self:
139 locals[head + r'mod'] = mod
137 140
138 141 for modname in modrefs:
139 142 modref = sys.modules.get(modname, None)
140 143 if modref and getattr(modref, head, None) is self:
141 144 setattr(modref, head, mod)
142 145
143 146 object.__setattr__(self, r"_module", mod)
144 147
145 148 def __repr__(self):
146 149 if self._module:
147 150 return "<proxied module '%s'>" % self._data[0]
148 151 return "<unloaded module '%s'>" % self._data[0]
149 152
150 153 def __call__(self, *args, **kwargs):
151 154 raise TypeError("%s object is not callable" % repr(self))
152 155
153 156 def __getattribute__(self, attr):
154 157 if attr in ('_data', '_extend', '_load', '_module', '_addref'):
155 158 return object.__getattribute__(self, attr)
156 159 self._load()
157 160 return getattr(self._module, attr)
158 161
159 162 def __setattr__(self, attr, val):
160 163 self._load()
161 164 setattr(self._module, attr, val)
162 165
163 166 _pypy = '__pypy__' in sys.builtin_module_names
164 167
165 168 def _demandimport(name, globals=None, locals=None, fromlist=None, level=level):
166 169 if locals is None or name in ignore or fromlist == ('*',):
167 170 # these cases we can't really delay
168 171 return _hgextimport(_import, name, globals, locals, fromlist, level)
169 172 elif not fromlist:
170 173 # import a [as b]
171 174 if '.' in name: # a.b
172 175 base, rest = name.split('.', 1)
173 176 # email.__init__ loading email.mime
174 177 if globals and globals.get('__name__', None) == base:
175 178 return _import(name, globals, locals, fromlist, level)
176 179 # if a is already demand-loaded, add b to its submodule list
177 180 if base in locals:
178 181 if isinstance(locals[base], _demandmod):
179 182 locals[base]._extend(rest)
180 183 return locals[base]
181 184 return _demandmod(name, globals, locals, level)
182 185 else:
183 186 # There is a fromlist.
184 187 # from a import b,c,d
185 188 # from . import b,c,d
186 189 # from .a import b,c,d
187 190
188 191 # level == -1: relative and absolute attempted (Python 2 only).
189 192 # level >= 0: absolute only (Python 2 w/ absolute_import and Python 3).
190 193 # The modern Mercurial convention is to use absolute_import everywhere,
191 194 # so modern Mercurial code will have level >= 0.
192 195
193 196 # The name of the module the import statement is located in.
194 197 globalname = globals.get('__name__')
195 198
196 199 def processfromitem(mod, attr):
197 200 """Process an imported symbol in the import statement.
198 201
199 202 If the symbol doesn't exist in the parent module, and if the
200 203 parent module is a package, it must be a module. We set missing
201 204 modules up as _demandmod instances.
202 205 """
203 206 symbol = getattr(mod, attr, nothing)
204 207 nonpkg = getattr(mod, '__path__', nothing) is nothing
205 208 if symbol is nothing:
206 209 if nonpkg:
207 210 # do not try relative import, which would raise ValueError,
208 211 # and leave unknown attribute as the default __import__()
209 212 # would do. the missing attribute will be detected later
210 213 # while processing the import statement.
211 214 return
212 215 mn = '%s.%s' % (mod.__name__, attr)
213 216 if mn in ignore:
214 217 importfunc = _origimport
215 218 else:
216 219 importfunc = _demandmod
217 220 symbol = importfunc(attr, mod.__dict__, locals, level=1)
218 221 setattr(mod, attr, symbol)
219 222
220 223 # Record the importing module references this symbol so we can
221 224 # replace the symbol with the actual module instance at load
222 225 # time.
223 226 if globalname and isinstance(symbol, _demandmod):
224 227 symbol._addref(globalname)
225 228
226 229 def chainmodules(rootmod, modname):
227 230 # recurse down the module chain, and return the leaf module
228 231 mod = rootmod
229 232 for comp in modname.split('.')[1:]:
230 233 if getattr(mod, comp, nothing) is nothing:
231 234 setattr(mod, comp, _demandmod(comp, mod.__dict__,
232 235 mod.__dict__, level=1))
233 236 mod = getattr(mod, comp)
234 237 return mod
235 238
236 239 if level >= 0:
237 240 if name:
238 241 # "from a import b" or "from .a import b" style
239 242 rootmod = _hgextimport(_origimport, name, globals, locals,
240 243 level=level)
241 244 mod = chainmodules(rootmod, name)
242 245 elif _pypy:
243 246 # PyPy's __import__ throws an exception if invoked
244 247 # with an empty name and no fromlist. Recreate the
245 248 # desired behaviour by hand.
246 249 mn = globalname
247 250 mod = sys.modules[mn]
248 251 if getattr(mod, '__path__', nothing) is nothing:
249 252 mn = mn.rsplit('.', 1)[0]
250 253 mod = sys.modules[mn]
251 254 if level > 1:
252 255 mn = mn.rsplit('.', level - 1)[0]
253 256 mod = sys.modules[mn]
254 257 else:
255 258 mod = _hgextimport(_origimport, name, globals, locals,
256 259 level=level)
257 260
258 261 for x in fromlist:
259 262 processfromitem(mod, x)
260 263
261 264 return mod
262 265
263 266 # But, we still need to support lazy loading of standard library and 3rd
264 267 # party modules. So handle level == -1.
265 268 mod = _hgextimport(_origimport, name, globals, locals)
266 269 mod = chainmodules(mod, name)
267 270
268 271 for x in fromlist:
269 272 processfromitem(mod, x)
270 273
271 274 return mod
272 275
273 276 ignore = []
274 277
275 278 def init(ignorelist):
276 279 global ignore
277 280 ignore = ignorelist
278 281
279 282 def isenabled():
280 283 return builtins.__import__ == _demandimport
281 284
282 285 def enable():
283 286 "enable global demand-loading of modules"
284 287 if os.environ.get('HGDEMANDIMPORT') != 'disable':
285 288 builtins.__import__ = _demandimport
286 289
287 290 def disable():
288 291 "disable global demand-loading of modules"
289 292 builtins.__import__ = _origimport
290 293
291 294 @contextmanager
292 295 def deactivated():
293 296 "context manager for disabling demandimport in 'with' blocks"
294 297 demandenabled = isenabled()
295 298 if demandenabled:
296 299 disable()
297 300
298 301 try:
299 302 yield
300 303 finally:
301 304 if demandenabled:
302 305 enable()
@@ -1,89 +1,95 b''
1 1 from __future__ import print_function
2 2
3 3 from mercurial import demandimport
4 4 demandimport.enable()
5 5
6 6 import os
7 7 import subprocess
8 8 import sys
9 9
10 10 # Only run if demandimport is allowed
11 11 if subprocess.call(['python', '%s/hghave' % os.environ['TESTDIR'],
12 12 'demandimport']):
13 13 sys.exit(80)
14 14
15 15 if os.name != 'nt':
16 16 try:
17 17 import distutils.msvc9compiler
18 18 print('distutils.msvc9compiler needs to be an immediate '
19 19 'importerror on non-windows platforms')
20 20 distutils.msvc9compiler
21 21 except ImportError:
22 22 pass
23 23
24 24 import re
25 25
26 26 rsub = re.sub
27 27 def f(obj):
28 28 l = repr(obj)
29 29 l = rsub("0x[0-9a-fA-F]+", "0x?", l)
30 30 l = rsub("from '.*'", "from '?'", l)
31 31 l = rsub("'<[a-z]*>'", "'<whatever>'", l)
32 32 return l
33 33
34 34 import os
35 35
36 36 print("os =", f(os))
37 37 print("os.system =", f(os.system))
38 38 print("os =", f(os))
39 39
40 40 from mercurial import util
41 41
42 42 print("util =", f(util))
43 43 print("util.system =", f(util.system))
44 44 print("util =", f(util))
45 45 print("util.system =", f(util.system))
46 46
47 47 from mercurial import hgweb
48 48 print("hgweb =", f(hgweb))
49 49 print("hgweb_mod =", f(hgweb.hgweb_mod))
50 50 print("hgweb =", f(hgweb))
51 51
52 52 import re as fred
53 53 print("fred =", f(fred))
54 54
55 import re as remod
56 print("remod =", f(remod))
57
55 58 import sys as re
56 59 print("re =", f(re))
57 60
58 61 print("fred =", f(fred))
59 62 print("fred.sub =", f(fred.sub))
60 63 print("fred =", f(fred))
61 64
65 remod.escape # use remod
66 print("remod =", f(remod))
67
62 68 print("re =", f(re))
63 69 print("re.stderr =", f(re.stderr))
64 70 print("re =", f(re))
65 71
66 72 import contextlib
67 73 print("contextlib =", f(contextlib))
68 74 try:
69 75 from contextlib import unknownattr
70 76 print('no demandmod should be created for attribute of non-package '
71 77 'module:\ncontextlib.unknownattr =', f(unknownattr))
72 78 except ImportError as inst:
73 79 print('contextlib.unknownattr = ImportError: %s'
74 80 % rsub(r"'", '', str(inst)))
75 81
76 82 # Unlike the import statement, __import__() function should not raise
77 83 # ImportError even if fromlist has an unknown item
78 84 # (see Python/import.c:import_module_level() and ensure_fromlist())
79 85 contextlibimp = __import__('contextlib', globals(), locals(), ['unknownattr'])
80 86 print("__import__('contextlib', ..., ['unknownattr']) =", f(contextlibimp))
81 87 print("hasattr(contextlibimp, 'unknownattr') =",
82 88 util.safehasattr(contextlibimp, 'unknownattr'))
83 89
84 90 demandimport.disable()
85 91 os.environ['HGDEMANDIMPORT'] = 'disable'
86 92 # this enable call should not actually enable demandimport!
87 93 demandimport.enable()
88 94 from mercurial import node
89 95 print("node =", f(node))
@@ -1,23 +1,25 b''
1 1 os = <unloaded module 'os'>
2 2 os.system = <built-in function system>
3 3 os = <module 'os' from '?'>
4 4 util = <unloaded module 'util'>
5 5 util.system = <function system at 0x?>
6 6 util = <module 'mercurial.util' from '?'>
7 7 util.system = <function system at 0x?>
8 8 hgweb = <unloaded module 'hgweb'>
9 9 hgweb_mod = <unloaded module 'hgweb_mod'>
10 10 hgweb = <module 'mercurial.hgweb' from '?'>
11 11 fred = <unloaded module 're'>
12 remod = <unloaded module 're'>
12 13 re = <unloaded module 'sys'>
13 14 fred = <unloaded module 're'>
14 15 fred.sub = <function sub at 0x?>
15 16 fred = <proxied module 're'>
17 remod = <module 're' from '?'>
16 18 re = <unloaded module 'sys'>
17 19 re.stderr = <open file '<whatever>', mode 'w' at 0x?>
18 20 re = <proxied module 'sys'>
19 21 contextlib = <unloaded module 'contextlib'>
20 22 contextlib.unknownattr = ImportError: cannot import name unknownattr
21 23 __import__('contextlib', ..., ['unknownattr']) = <module 'contextlib' from '?'>
22 24 hasattr(contextlibimp, 'unknownattr') = False
23 25 node = <module 'mercurial.node' from '?'>
General Comments 0
You need to be logged in to leave comments. Login now