##// END OF EJS Templates
demandimport: add support for PyPy...
Bryan O'Sullivan -
r27536:f7d890bc default
parent child Browse files
Show More
@@ -1,270 +1,282
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 97 # load submodules
98 98 def subload(mod, p):
99 99 h, t = p, None
100 100 if '.' in p:
101 101 h, t = p.split('.', 1)
102 102 if getattr(mod, h, nothing) is nothing:
103 103 setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__))
104 104 elif t:
105 105 subload(getattr(mod, h), t)
106 106
107 107 for x in after:
108 108 subload(mod, x)
109 109
110 110 # Replace references to this proxy instance with the actual module.
111 111 if locals and locals.get(head) == self:
112 112 locals[head] = mod
113 113
114 114 for modname in modrefs:
115 115 modref = sys.modules.get(modname, None)
116 116 if modref and getattr(modref, head, None) == self:
117 117 setattr(modref, head, mod)
118 118
119 119 object.__setattr__(self, "_module", mod)
120 120
121 121 def __repr__(self):
122 122 if self._module:
123 123 return "<proxied module '%s'>" % self._data[0]
124 124 return "<unloaded module '%s'>" % self._data[0]
125 125 def __call__(self, *args, **kwargs):
126 126 raise TypeError("%s object is not callable" % repr(self))
127 127 def __getattribute__(self, attr):
128 128 if attr in ('_data', '_extend', '_load', '_module', '_addref'):
129 129 return object.__getattribute__(self, attr)
130 130 self._load()
131 131 return getattr(self._module, attr)
132 132 def __setattr__(self, attr, val):
133 133 self._load()
134 134 setattr(self._module, attr, val)
135 135
136 _pypy = '__pypy__' in sys.builtin_module_names
137
136 138 def _demandimport(name, globals=None, locals=None, fromlist=None, level=level):
137 139 if not locals or name in ignore or fromlist == ('*',):
138 140 # these cases we can't really delay
139 141 return _hgextimport(_import, name, globals, locals, fromlist, level)
140 142 elif not fromlist:
141 143 # import a [as b]
142 144 if '.' in name: # a.b
143 145 base, rest = name.split('.', 1)
144 146 # email.__init__ loading email.mime
145 147 if globals and globals.get('__name__', None) == base:
146 148 return _import(name, globals, locals, fromlist, level)
147 149 # if a is already demand-loaded, add b to its submodule list
148 150 if base in locals:
149 151 if isinstance(locals[base], _demandmod):
150 152 locals[base]._extend(rest)
151 153 return locals[base]
152 154 return _demandmod(name, globals, locals, level)
153 155 else:
154 156 # There is a fromlist.
155 157 # from a import b,c,d
156 158 # from . import b,c,d
157 159 # from .a import b,c,d
158 160
159 161 # level == -1: relative and absolute attempted (Python 2 only).
160 162 # level >= 0: absolute only (Python 2 w/ absolute_import and Python 3).
161 163 # The modern Mercurial convention is to use absolute_import everywhere,
162 164 # so modern Mercurial code will have level >= 0.
163 165
164 166 # The name of the module the import statement is located in.
165 167 globalname = globals.get('__name__')
166 168
167 169 def processfromitem(mod, attr):
168 170 """Process an imported symbol in the import statement.
169 171
170 172 If the symbol doesn't exist in the parent module, it must be a
171 173 module. We set missing modules up as _demandmod instances.
172 174 """
173 175 symbol = getattr(mod, attr, nothing)
174 176 if symbol is nothing:
175 177 symbol = _demandmod(attr, mod.__dict__, locals, level=1)
176 178 setattr(mod, attr, symbol)
177 179
178 180 # Record the importing module references this symbol so we can
179 181 # replace the symbol with the actual module instance at load
180 182 # time.
181 183 if globalname and isinstance(symbol, _demandmod):
182 184 symbol._addref(globalname)
183 185
184 186 if level >= 0:
185 187 # Mercurial's enforced import style does not use
186 188 # "from a import b,c,d" or "from .a import b,c,d" syntax. In
187 189 # addition, this appears to be giving errors with some modules
188 190 # for unknown reasons. Since we shouldn't be using this syntax
189 191 # much, work around the problems.
190 192 if name:
191 193 return _hgextimport(_origimport, name, globals, locals,
192 194 fromlist, level)
193 195
194 mod = _hgextimport(_origimport, name, globals, locals, level=level)
196 if _pypy:
197 # PyPy's __import__ throws an exception if invoked
198 # with an empty name and no fromlist. Recreate the
199 # desired behaviour by hand.
200 mn = globalname
201 mod = sys.modules[mn]
202 if getattr(mod, '__path__', nothing) is nothing:
203 mn = mn.rsplit('.', 1)[0]
204 mod = sys.modules[mn]
205 if level > 1:
206 mn = mn.rsplit('.', level - 1)[0]
207 mod = sys.modules[mn]
208 else:
209 mod = _hgextimport(_origimport, name, globals, locals,
210 level=level)
195 211
196 212 for x in fromlist:
197 213 processfromitem(mod, x)
198 214
199 215 return mod
200 216
201 217 # But, we still need to support lazy loading of standard library and 3rd
202 218 # party modules. So handle level == -1.
203 219 mod = _hgextimport(_origimport, name, globals, locals)
204 220 # recurse down the module chain
205 221 for comp in name.split('.')[1:]:
206 222 if getattr(mod, comp, nothing) is nothing:
207 223 setattr(mod, comp,
208 224 _demandmod(comp, mod.__dict__, mod.__dict__))
209 225 mod = getattr(mod, comp)
210 226
211 227 for x in fromlist:
212 228 processfromitem(mod, x)
213 229
214 230 return mod
215 231
216 232 ignore = [
217 233 '__future__',
218 234 '_hashlib',
219 235 '_xmlplus',
220 236 'fcntl',
221 237 'win32com.gen_py',
222 238 '_winreg', # 2.7 mimetypes needs immediate ImportError
223 239 'pythoncom',
224 240 # imported by tarfile, not available under Windows
225 241 'pwd',
226 242 'grp',
227 243 # imported by profile, itself imported by hotshot.stats,
228 244 # not available under Windows
229 245 'resource',
230 246 # this trips up many extension authors
231 247 'gtk',
232 248 # setuptools' pkg_resources.py expects "from __main__ import x" to
233 249 # raise ImportError if x not defined
234 250 '__main__',
235 251 '_ssl', # conditional imports in the stdlib, issue1964
236 252 '_sre', # issue4920
237 253 'rfc822',
238 254 'mimetools',
239 255 # setuptools 8 expects this module to explode early when not on windows
240 256 'distutils.msvc9compiler'
241 257 ]
242 258
243 259 def isenabled():
244 260 return builtins.__import__ == _demandimport
245 261
246 262 def enable():
247 263 "enable global demand-loading of modules"
248 # PyPy doesn't work with demand import.
249 if '__pypy__' in sys.builtin_module_names:
250 return
251
252 264 if os.environ.get('HGDEMANDIMPORT') != 'disable':
253 265 builtins.__import__ = _demandimport
254 266
255 267 def disable():
256 268 "disable global demand-loading of modules"
257 269 builtins.__import__ = _origimport
258 270
259 271 @contextmanager
260 272 def deactivated():
261 273 "context manager for disabling demandimport in 'with' blocks"
262 274 demandenabled = isenabled()
263 275 if demandenabled:
264 276 disable()
265 277
266 278 try:
267 279 yield
268 280 finally:
269 281 if demandenabled:
270 282 enable()
General Comments 0
You need to be logged in to leave comments. Login now