##// END OF EJS Templates
demandimport: support importing builtins for Python 3...
Gregory Szorc -
r25674:5d0847cd default
parent child Browse files
Show More
@@ -1,197 +1,202 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 import os, sys
28 28 from contextlib import contextmanager
29 import __builtin__ as builtins
29
30 # __builtin__ in Python 2, builtins in Python 3.
31 try:
32 import __builtin__ as builtins
33 except ImportError:
34 import builtins
30 35
31 36 _origimport = __import__
32 37
33 38 nothing = object()
34 39
35 40 try:
36 41 # Python 3 doesn't have relative imports nor level -1.
37 42 level = -1
38 43 if sys.version_info[0] >= 3:
39 44 level = 0
40 45 _origimport(builtins.__name__, {}, {}, None, level)
41 46 except TypeError: # no level argument
42 47 def _import(name, globals, locals, fromlist, level):
43 48 "call _origimport with no level argument"
44 49 return _origimport(name, globals, locals, fromlist)
45 50 else:
46 51 _import = _origimport
47 52
48 53 def _hgextimport(importfunc, name, globals, *args):
49 54 try:
50 55 return importfunc(name, globals, *args)
51 56 except ImportError:
52 57 if not globals:
53 58 raise
54 59 # extensions are loaded with "hgext_" prefix
55 60 hgextname = 'hgext_%s' % name
56 61 nameroot = hgextname.split('.', 1)[0]
57 62 contextroot = globals.get('__name__', '').split('.', 1)[0]
58 63 if nameroot != contextroot:
59 64 raise
60 65 # retry to import with "hgext_" prefix
61 66 return importfunc(hgextname, globals, *args)
62 67
63 68 class _demandmod(object):
64 69 """module demand-loader and proxy"""
65 70 def __init__(self, name, globals, locals, level=level):
66 71 if '.' in name:
67 72 head, rest = name.split('.', 1)
68 73 after = [rest]
69 74 else:
70 75 head = name
71 76 after = []
72 77 object.__setattr__(self, "_data",
73 78 (head, globals, locals, after, level))
74 79 object.__setattr__(self, "_module", None)
75 80 def _extend(self, name):
76 81 """add to the list of submodules to load"""
77 82 self._data[3].append(name)
78 83 def _load(self):
79 84 if not self._module:
80 85 head, globals, locals, after, level = self._data
81 86 mod = _hgextimport(_import, head, globals, locals, None, level)
82 87 # load submodules
83 88 def subload(mod, p):
84 89 h, t = p, None
85 90 if '.' in p:
86 91 h, t = p.split('.', 1)
87 92 if getattr(mod, h, nothing) is nothing:
88 93 setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__))
89 94 elif t:
90 95 subload(getattr(mod, h), t)
91 96
92 97 for x in after:
93 98 subload(mod, x)
94 99
95 100 # are we in the locals dictionary still?
96 101 if locals and locals.get(head) == self:
97 102 locals[head] = mod
98 103 object.__setattr__(self, "_module", mod)
99 104
100 105 def __repr__(self):
101 106 if self._module:
102 107 return "<proxied module '%s'>" % self._data[0]
103 108 return "<unloaded module '%s'>" % self._data[0]
104 109 def __call__(self, *args, **kwargs):
105 110 raise TypeError("%s object is not callable" % repr(self))
106 111 def __getattribute__(self, attr):
107 112 if attr in ('_data', '_extend', '_load', '_module'):
108 113 return object.__getattribute__(self, attr)
109 114 self._load()
110 115 return getattr(self._module, attr)
111 116 def __setattr__(self, attr, val):
112 117 self._load()
113 118 setattr(self._module, attr, val)
114 119
115 120 def _demandimport(name, globals=None, locals=None, fromlist=None, level=level):
116 121 if not locals or name in ignore or fromlist == ('*',):
117 122 # these cases we can't really delay
118 123 return _hgextimport(_import, name, globals, locals, fromlist, level)
119 124 elif not fromlist:
120 125 # import a [as b]
121 126 if '.' in name: # a.b
122 127 base, rest = name.split('.', 1)
123 128 # email.__init__ loading email.mime
124 129 if globals and globals.get('__name__', None) == base:
125 130 return _import(name, globals, locals, fromlist, level)
126 131 # if a is already demand-loaded, add b to its submodule list
127 132 if base in locals:
128 133 if isinstance(locals[base], _demandmod):
129 134 locals[base]._extend(rest)
130 135 return locals[base]
131 136 return _demandmod(name, globals, locals, level)
132 137 else:
133 138 if level != -1:
134 139 # from . import b,c,d or from .a import b,c,d
135 140 return _origimport(name, globals, locals, fromlist, level)
136 141 # from a import b,c,d
137 142 mod = _hgextimport(_origimport, name, globals, locals)
138 143 # recurse down the module chain
139 144 for comp in name.split('.')[1:]:
140 145 if getattr(mod, comp, nothing) is nothing:
141 146 setattr(mod, comp, _demandmod(comp, mod.__dict__, mod.__dict__))
142 147 mod = getattr(mod, comp)
143 148 for x in fromlist:
144 149 # set requested submodules for demand load
145 150 if getattr(mod, x, nothing) is nothing:
146 151 setattr(mod, x, _demandmod(x, mod.__dict__, locals))
147 152 return mod
148 153
149 154 ignore = [
150 155 '_hashlib',
151 156 '_xmlplus',
152 157 'fcntl',
153 158 'win32com.gen_py',
154 159 '_winreg', # 2.7 mimetypes needs immediate ImportError
155 160 'pythoncom',
156 161 # imported by tarfile, not available under Windows
157 162 'pwd',
158 163 'grp',
159 164 # imported by profile, itself imported by hotshot.stats,
160 165 # not available under Windows
161 166 'resource',
162 167 # this trips up many extension authors
163 168 'gtk',
164 169 # setuptools' pkg_resources.py expects "from __main__ import x" to
165 170 # raise ImportError if x not defined
166 171 '__main__',
167 172 '_ssl', # conditional imports in the stdlib, issue1964
168 173 'rfc822',
169 174 'mimetools',
170 175 # setuptools 8 expects this module to explode early when not on windows
171 176 'distutils.msvc9compiler'
172 177 ]
173 178
174 179 def isenabled():
175 180 return builtins.__import__ == _demandimport
176 181
177 182 def enable():
178 183 "enable global demand-loading of modules"
179 184 if os.environ.get('HGDEMANDIMPORT') != 'disable':
180 185 builtins.__import__ = _demandimport
181 186
182 187 def disable():
183 188 "disable global demand-loading of modules"
184 189 builtins.__import__ = _origimport
185 190
186 191 @contextmanager
187 192 def deactivated():
188 193 "context manager for disabling demandimport in 'with' blocks"
189 194 demandenabled = isenabled()
190 195 if demandenabled:
191 196 disable()
192 197
193 198 try:
194 199 yield
195 200 finally:
196 201 if demandenabled:
197 202 enable()
General Comments 0
You need to be logged in to leave comments. Login now