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