##// END OF EJS Templates
hgdemandimport: disable on Python 3.5...
Gregory Szorc -
r44576:c5e0a9b9 default
parent child Browse files
Show More
@@ -1,128 +1,131 b''
1 1 # demandimportpy3 - global demand-loading of modules for Mercurial
2 2 #
3 3 # Copyright 2017 Facebook Inc.
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 """Lazy loading for Python 3.6 and above.
9 9
10 10 This uses the new importlib finder/loader functionality available in Python 3.5
11 11 and up. The code reuses most of the mechanics implemented inside importlib.util,
12 12 but with a few additions:
13 13
14 14 * Allow excluding certain modules from lazy imports.
15 15 * Expose an interface that's substantially the same as demandimport for
16 16 Python 2.
17 17
18 18 This also has some limitations compared to the Python 2 implementation:
19 19
20 20 * Much of the logic is per-package, not per-module, so any packages loaded
21 21 before demandimport is enabled will not be lazily imported in the future. In
22 22 practice, we only expect builtins to be loaded before demandimport is
23 23 enabled.
24 24 """
25 25
26 26 # This line is unnecessary, but it satisfies test-check-py3-compat.t.
27 27 from __future__ import absolute_import
28 28
29 29 import contextlib
30 30 import importlib.abc
31 31 import importlib.machinery
32 32 import importlib.util
33 33 import sys
34 34
35 35 from . import tracing
36 36
37 37 _deactivated = False
38 38
39 # Python 3.5's LazyLoader doesn't work for some reason.
40 # https://bugs.python.org/issue26186 is a known issue with extension
41 # importing. But it appears to not have a meaningful effect with
42 # Mercurial.
43 _supported = sys.version_info[0:2] >= (3, 6)
44
39 45
40 46 class _lazyloaderex(importlib.util.LazyLoader):
41 47 """This is a LazyLoader except it also follows the _deactivated global and
42 48 the ignore list.
43 49 """
44 50
45 51 def exec_module(self, module):
46 52 """Make the module load lazily."""
47 53 with tracing.log('demandimport %s', module):
48 54 if _deactivated or module.__name__ in ignores:
49 55 self.loader.exec_module(module)
50 56 else:
51 57 super().exec_module(module)
52 58
53 59
54 # This is 3.6+ because with Python 3.5 it isn't possible to lazily load
55 # extensions. See the discussion in https://bugs.python.org/issue26186 for more.
56 if sys.version_info[0:2] >= (3, 6):
57 _extensions_loader = _lazyloaderex.factory(
58 importlib.machinery.ExtensionFileLoader
59 )
60 else:
61 _extensions_loader = importlib.machinery.ExtensionFileLoader
62
60 _extensions_loader = _lazyloaderex.factory(
61 importlib.machinery.ExtensionFileLoader
62 )
63 63 _bytecode_loader = _lazyloaderex.factory(
64 64 importlib.machinery.SourcelessFileLoader
65 65 )
66 66 _source_loader = _lazyloaderex.factory(importlib.machinery.SourceFileLoader)
67 67
68 68
69 69 def _makefinder(path):
70 70 return importlib.machinery.FileFinder(
71 71 path,
72 72 # This is the order in which loaders are passed in in core Python.
73 73 (_extensions_loader, importlib.machinery.EXTENSION_SUFFIXES),
74 74 (_source_loader, importlib.machinery.SOURCE_SUFFIXES),
75 75 (_bytecode_loader, importlib.machinery.BYTECODE_SUFFIXES),
76 76 )
77 77
78 78
79 79 ignores = set()
80 80
81 81
82 82 def init(ignoreset):
83 83 global ignores
84 84 ignores = ignoreset
85 85
86 86
87 87 def isenabled():
88 88 return _makefinder in sys.path_hooks and not _deactivated
89 89
90 90
91 91 def disable():
92 92 try:
93 93 while True:
94 94 sys.path_hooks.remove(_makefinder)
95 95 except ValueError:
96 96 pass
97 97
98 98
99 99 def enable():
100 if not _supported:
101 return
102
100 103 sys.path_hooks.insert(0, _makefinder)
101 104
102 105
103 106 @contextlib.contextmanager
104 107 def deactivated():
105 108 # This implementation is a bit different from Python 2's. Python 3
106 109 # maintains a per-package finder cache in sys.path_importer_cache (see
107 110 # PEP 302). This means that we can't just call disable + enable.
108 111 # If we do that, in situations like:
109 112 #
110 113 # demandimport.enable()
111 114 # ...
112 115 # from foo.bar import mod1
113 116 # with demandimport.deactivated():
114 117 # from foo.bar import mod2
115 118 #
116 119 # mod2 will be imported lazily. (The converse also holds -- whatever finder
117 120 # first gets cached will be used.)
118 121 #
119 122 # Instead, have a global flag the LazyLoader can use.
120 123 global _deactivated
121 124 demandenabled = isenabled()
122 125 if demandenabled:
123 126 _deactivated = True
124 127 try:
125 128 yield
126 129 finally:
127 130 if demandenabled:
128 131 _deactivated = False
@@ -1,234 +1,238 b''
1 1 from __future__ import absolute_import, print_function
2 2
3 3 from mercurial import demandimport
4 4
5 5 demandimport.enable()
6 6
7 7 import os
8 8 import subprocess
9 9 import sys
10 10 import types
11 11
12 12 # Don't import pycompat because it has too many side-effects.
13 13 ispy3 = sys.version_info[0] >= 3
14 14
15 15 # Only run if demandimport is allowed
16 16 if subprocess.call(
17 17 ['python', '%s/hghave' % os.environ['TESTDIR'], 'demandimport']
18 18 ):
19 19 sys.exit(80)
20 20
21 21 # We rely on assert, which gets optimized out.
22 22 if sys.flags.optimize:
23 23 sys.exit(80)
24 24
25 # The demand importer doesn't work on Python 3.5.
26 if sys.version_info[0:2] == (3, 5):
27 sys.exit(80)
28
25 29 if ispy3:
26 30 from importlib.util import _LazyModule
27 31
28 32 try:
29 33 from importlib.util import _Module as moduletype
30 34 except ImportError:
31 35 moduletype = types.ModuleType
32 36 else:
33 37 moduletype = types.ModuleType
34 38
35 39 if os.name != 'nt':
36 40 try:
37 41 import distutils.msvc9compiler
38 42
39 43 print(
40 44 'distutils.msvc9compiler needs to be an immediate '
41 45 'importerror on non-windows platforms'
42 46 )
43 47 distutils.msvc9compiler
44 48 except ImportError:
45 49 pass
46 50
47 51 import re
48 52
49 53 rsub = re.sub
50 54
51 55
52 56 def f(obj):
53 57 l = repr(obj)
54 58 l = rsub("0x[0-9a-fA-F]+", "0x?", l)
55 59 l = rsub("from '.*'", "from '?'", l)
56 60 l = rsub("'<[a-z]*>'", "'<whatever>'", l)
57 61 return l
58 62
59 63
60 64 demandimport.disable()
61 65 os.environ['HGDEMANDIMPORT'] = 'disable'
62 66 # this enable call should not actually enable demandimport!
63 67 demandimport.enable()
64 68 from mercurial import node
65 69
66 70 # We use assert instead of a unittest test case because having imports inside
67 71 # functions changes behavior of the demand importer.
68 72 if ispy3:
69 73 assert not isinstance(node, _LazyModule)
70 74 else:
71 75 assert f(node) == "<module 'mercurial.node' from '?'>", f(node)
72 76
73 77 # now enable it for real
74 78 del os.environ['HGDEMANDIMPORT']
75 79 demandimport.enable()
76 80
77 81 # Test access to special attributes through demandmod proxy
78 82 assert 'mercurial.error' not in sys.modules
79 83 from mercurial import error as errorproxy
80 84
81 85 if ispy3:
82 86 # unsure why this isn't lazy.
83 87 assert not isinstance(f, _LazyModule)
84 88 assert f(errorproxy) == "<module 'mercurial.error' from '?'>", f(errorproxy)
85 89 else:
86 90 assert f(errorproxy) == "<unloaded module 'error'>", f(errorproxy)
87 91
88 92 doc = ' '.join(errorproxy.__doc__.split()[:3])
89 93 assert doc == 'Mercurial exceptions. This', doc
90 94 assert errorproxy.__name__ == 'mercurial.error', errorproxy.__name__
91 95
92 96 # __name__ must be accessible via __dict__ so the relative imports can be
93 97 # resolved
94 98 name = errorproxy.__dict__['__name__']
95 99 assert name == 'mercurial.error', name
96 100
97 101 if ispy3:
98 102 assert not isinstance(errorproxy, _LazyModule)
99 103 assert f(errorproxy) == "<module 'mercurial.error' from '?'>", f(errorproxy)
100 104 else:
101 105 assert f(errorproxy) == "<proxied module 'error'>", f(errorproxy)
102 106
103 107 import os
104 108
105 109 if ispy3:
106 110 assert not isinstance(os, _LazyModule)
107 111 assert f(os) == "<module 'os' from '?'>", f(os)
108 112 else:
109 113 assert f(os) == "<unloaded module 'os'>", f(os)
110 114
111 115 assert f(os.system) == '<built-in function system>', f(os.system)
112 116 assert f(os) == "<module 'os' from '?'>", f(os)
113 117
114 118 assert 'mercurial.utils.procutil' not in sys.modules
115 119 from mercurial.utils import procutil
116 120
117 121 if ispy3:
118 122 assert isinstance(procutil, _LazyModule)
119 123 assert f(procutil) == "<module 'mercurial.utils.procutil' from '?'>", f(
120 124 procutil
121 125 )
122 126 else:
123 127 assert f(procutil) == "<unloaded module 'procutil'>", f(procutil)
124 128
125 129 assert f(procutil.system) == '<function system at 0x?>', f(procutil.system)
126 130 assert procutil.__class__ == moduletype, procutil.__class__
127 131 assert f(procutil) == "<module 'mercurial.utils.procutil' from '?'>", f(
128 132 procutil
129 133 )
130 134 assert f(procutil.system) == '<function system at 0x?>', f(procutil.system)
131 135
132 136 assert 'mercurial.hgweb' not in sys.modules
133 137 from mercurial import hgweb
134 138
135 139 if ispy3:
136 140 assert not isinstance(hgweb, _LazyModule)
137 141 assert f(hgweb) == "<module 'mercurial.hgweb' from '?'>", f(hgweb)
138 142 assert isinstance(hgweb.hgweb_mod, _LazyModule)
139 143 assert (
140 144 f(hgweb.hgweb_mod) == "<module 'mercurial.hgweb.hgweb_mod' from '?'>"
141 145 ), f(hgweb.hgweb_mod)
142 146 else:
143 147 assert f(hgweb) == "<unloaded module 'hgweb'>", f(hgweb)
144 148 assert f(hgweb.hgweb_mod) == "<unloaded module 'hgweb_mod'>", f(
145 149 hgweb.hgweb_mod
146 150 )
147 151
148 152 assert f(hgweb) == "<module 'mercurial.hgweb' from '?'>", f(hgweb)
149 153
150 154 import re as fred
151 155
152 156 if ispy3:
153 157 assert not isinstance(fred, _LazyModule)
154 158 assert f(fred) == "<module 're' from '?'>"
155 159 else:
156 160 assert f(fred) == "<unloaded module 're'>", f(fred)
157 161
158 162 import re as remod
159 163
160 164 if ispy3:
161 165 assert not isinstance(remod, _LazyModule)
162 166 assert f(remod) == "<module 're' from '?'>"
163 167 else:
164 168 assert f(remod) == "<unloaded module 're'>", f(remod)
165 169
166 170 import sys as re
167 171
168 172 if ispy3:
169 173 assert not isinstance(re, _LazyModule)
170 174 assert f(re) == "<module 'sys' (built-in)>"
171 175 else:
172 176 assert f(re) == "<unloaded module 'sys'>", f(re)
173 177
174 178 if ispy3:
175 179 assert not isinstance(fred, _LazyModule)
176 180 assert f(fred) == "<module 're' from '?'>", f(fred)
177 181 else:
178 182 assert f(fred) == "<unloaded module 're'>", f(fred)
179 183
180 184 assert f(fred.sub) == '<function sub at 0x?>', f(fred.sub)
181 185
182 186 if ispy3:
183 187 assert not isinstance(fred, _LazyModule)
184 188 assert f(fred) == "<module 're' from '?'>", f(fred)
185 189 else:
186 190 assert f(fred) == "<proxied module 're'>", f(fred)
187 191
188 192 remod.escape # use remod
189 193 assert f(remod) == "<module 're' from '?'>", f(remod)
190 194
191 195 if ispy3:
192 196 assert not isinstance(re, _LazyModule)
193 197 assert f(re) == "<module 'sys' (built-in)>"
194 198 assert f(type(re.stderr)) == "<class '_io.TextIOWrapper'>", f(
195 199 type(re.stderr)
196 200 )
197 201 assert f(re) == "<module 'sys' (built-in)>"
198 202 else:
199 203 assert f(re) == "<unloaded module 'sys'>", f(re)
200 204 assert f(re.stderr) == "<open file '<whatever>', mode 'w' at 0x?>", f(
201 205 re.stderr
202 206 )
203 207 assert f(re) == "<proxied module 'sys'>", f(re)
204 208
205 209 assert 'telnetlib' not in sys.modules
206 210 import telnetlib
207 211
208 212 if ispy3:
209 213 assert not isinstance(telnetlib, _LazyModule)
210 214 assert f(telnetlib) == "<module 'telnetlib' from '?'>"
211 215 else:
212 216 assert f(telnetlib) == "<unloaded module 'telnetlib'>", f(telnetlib)
213 217
214 218 try:
215 219 from telnetlib import unknownattr
216 220
217 221 assert False, (
218 222 'no demandmod should be created for attribute of non-package '
219 223 'module:\ntelnetlib.unknownattr = %s' % f(unknownattr)
220 224 )
221 225 except ImportError as inst:
222 226 assert rsub(r"'", '', str(inst)).startswith(
223 227 'cannot import name unknownattr'
224 228 )
225 229
226 230 from mercurial import util
227 231
228 232 # Unlike the import statement, __import__() function should not raise
229 233 # ImportError even if fromlist has an unknown item
230 234 # (see Python/import.c:import_module_level() and ensure_fromlist())
231 235 assert 'zipfile' not in sys.modules
232 236 zipfileimp = __import__('zipfile', globals(), locals(), ['unknownattr'])
233 237 assert f(zipfileimp) == "<module 'zipfile' from '?'>", f(zipfileimp)
234 238 assert not util.safehasattr(zipfileimp, 'unknownattr')
General Comments 0
You need to be logged in to leave comments. Login now