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