##// END OF EJS Templates
hgdemandimport: delete check for Python 3.5...
Gregory Szorc -
r49813:b8eb29ab default
parent child Browse files
Show More
@@ -1,183 +1,174 b''
1 # demandimportpy3 - global demand-loading of modules for Mercurial
1 # demandimportpy3 - global demand-loading of modules for Mercurial
2 #
2 #
3 # Copyright 2017 Facebook Inc.
3 # Copyright 2017 Facebook Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Lazy loading for Python 3.6 and above.
8 """Lazy loading for Python 3.6 and above.
9
9
10 This uses the new importlib finder/loader functionality available in Python 3.5
10 This uses the new importlib finder/loader functionality available in Python 3.5
11 and up. The code reuses most of the mechanics implemented inside importlib.util,
11 and up. The code reuses most of the mechanics implemented inside importlib.util,
12 but with a few additions:
12 but with a few additions:
13
13
14 * Allow excluding certain modules from lazy imports.
14 * Allow excluding certain modules from lazy imports.
15 * Expose an interface that's substantially the same as demandimport for
15 * Expose an interface that's substantially the same as demandimport for
16 Python 2.
16 Python 2.
17
17
18 This also has some limitations compared to the Python 2 implementation:
18 This also has some limitations compared to the Python 2 implementation:
19
19
20 * Much of the logic is per-package, not per-module, so any packages loaded
20 * Much of the logic is per-package, not per-module, so any packages loaded
21 before demandimport is enabled will not be lazily imported in the future. In
21 before demandimport is enabled will not be lazily imported in the future. In
22 practice, we only expect builtins to be loaded before demandimport is
22 practice, we only expect builtins to be loaded before demandimport is
23 enabled.
23 enabled.
24 """
24 """
25
25
26 # This line is unnecessary, but it satisfies test-check-py3-compat.t.
26 # This line is unnecessary, but it satisfies test-check-py3-compat.t.
27
27
28 import contextlib
28 import contextlib
29 import importlib.util
29 import importlib.util
30 import sys
30 import sys
31
31
32 from . import tracing
32 from . import tracing
33
33
34 _deactivated = False
34 _deactivated = False
35
35
36 # Python 3.5's LazyLoader doesn't work for some reason.
37 # https://bugs.python.org/issue26186 is a known issue with extension
38 # importing. But it appears to not have a meaningful effect with
39 # Mercurial.
40 _supported = sys.version_info[0:2] >= (3, 6)
41
42
36
43 class _lazyloaderex(importlib.util.LazyLoader):
37 class _lazyloaderex(importlib.util.LazyLoader):
44 """This is a LazyLoader except it also follows the _deactivated global and
38 """This is a LazyLoader except it also follows the _deactivated global and
45 the ignore list.
39 the ignore list.
46 """
40 """
47
41
48 def exec_module(self, module):
42 def exec_module(self, module):
49 """Make the module load lazily."""
43 """Make the module load lazily."""
50 with tracing.log('demandimport %s', module):
44 with tracing.log('demandimport %s', module):
51 if _deactivated or module.__name__ in ignores:
45 if _deactivated or module.__name__ in ignores:
52 self.loader.exec_module(module)
46 self.loader.exec_module(module)
53 else:
47 else:
54 super().exec_module(module)
48 super().exec_module(module)
55
49
56
50
57 class LazyFinder:
51 class LazyFinder:
58 """A wrapper around a ``MetaPathFinder`` that makes loaders lazy.
52 """A wrapper around a ``MetaPathFinder`` that makes loaders lazy.
59
53
60 ``sys.meta_path`` finders have their ``find_spec()`` called to locate a
54 ``sys.meta_path`` finders have their ``find_spec()`` called to locate a
61 module. This returns a ``ModuleSpec`` if found or ``None``. The
55 module. This returns a ``ModuleSpec`` if found or ``None``. The
62 ``ModuleSpec`` has a ``loader`` attribute, which is called to actually
56 ``ModuleSpec`` has a ``loader`` attribute, which is called to actually
63 load a module.
57 load a module.
64
58
65 Our class wraps an existing finder and overloads its ``find_spec()`` to
59 Our class wraps an existing finder and overloads its ``find_spec()`` to
66 replace the ``loader`` with our lazy loader proxy.
60 replace the ``loader`` with our lazy loader proxy.
67
61
68 We have to use __getattribute__ to proxy the instance because some meta
62 We have to use __getattribute__ to proxy the instance because some meta
69 path finders don't support monkeypatching.
63 path finders don't support monkeypatching.
70 """
64 """
71
65
72 __slots__ = ("_finder",)
66 __slots__ = ("_finder",)
73
67
74 def __init__(self, finder):
68 def __init__(self, finder):
75 object.__setattr__(self, "_finder", finder)
69 object.__setattr__(self, "_finder", finder)
76
70
77 def __repr__(self):
71 def __repr__(self):
78 return "<LazyFinder for %r>" % object.__getattribute__(self, "_finder")
72 return "<LazyFinder for %r>" % object.__getattribute__(self, "_finder")
79
73
80 # __bool__ is canonical Python 3. But check-code insists on __nonzero__ being
74 # __bool__ is canonical Python 3. But check-code insists on __nonzero__ being
81 # defined via `def`.
75 # defined via `def`.
82 def __nonzero__(self):
76 def __nonzero__(self):
83 return bool(object.__getattribute__(self, "_finder"))
77 return bool(object.__getattribute__(self, "_finder"))
84
78
85 __bool__ = __nonzero__
79 __bool__ = __nonzero__
86
80
87 def __getattribute__(self, name):
81 def __getattribute__(self, name):
88 if name in ("_finder", "find_spec"):
82 if name in ("_finder", "find_spec"):
89 return object.__getattribute__(self, name)
83 return object.__getattribute__(self, name)
90
84
91 return getattr(object.__getattribute__(self, "_finder"), name)
85 return getattr(object.__getattribute__(self, "_finder"), name)
92
86
93 def __delattr__(self, name):
87 def __delattr__(self, name):
94 return delattr(object.__getattribute__(self, "_finder"))
88 return delattr(object.__getattribute__(self, "_finder"))
95
89
96 def __setattr__(self, name, value):
90 def __setattr__(self, name, value):
97 return setattr(object.__getattribute__(self, "_finder"), name, value)
91 return setattr(object.__getattribute__(self, "_finder"), name, value)
98
92
99 def find_spec(self, fullname, path, target=None):
93 def find_spec(self, fullname, path, target=None):
100 finder = object.__getattribute__(self, "_finder")
94 finder = object.__getattribute__(self, "_finder")
101 try:
95 try:
102 find_spec = finder.find_spec
96 find_spec = finder.find_spec
103 except AttributeError:
97 except AttributeError:
104 loader = finder.find_module(fullname, path)
98 loader = finder.find_module(fullname, path)
105 if loader is None:
99 if loader is None:
106 spec = None
100 spec = None
107 else:
101 else:
108 spec = importlib.util.spec_from_loader(fullname, loader)
102 spec = importlib.util.spec_from_loader(fullname, loader)
109 else:
103 else:
110 spec = find_spec(fullname, path, target)
104 spec = find_spec(fullname, path, target)
111
105
112 # Lazy loader requires exec_module().
106 # Lazy loader requires exec_module().
113 if (
107 if (
114 spec is not None
108 spec is not None
115 and spec.loader is not None
109 and spec.loader is not None
116 and getattr(spec.loader, "exec_module", None)
110 and getattr(spec.loader, "exec_module", None)
117 ):
111 ):
118 spec.loader = _lazyloaderex(spec.loader)
112 spec.loader = _lazyloaderex(spec.loader)
119
113
120 return spec
114 return spec
121
115
122
116
123 ignores = set()
117 ignores = set()
124
118
125
119
126 def init(ignoreset):
120 def init(ignoreset):
127 global ignores
121 global ignores
128 ignores = ignoreset
122 ignores = ignoreset
129
123
130
124
131 def isenabled():
125 def isenabled():
132 return not _deactivated and any(
126 return not _deactivated and any(
133 isinstance(finder, LazyFinder) for finder in sys.meta_path
127 isinstance(finder, LazyFinder) for finder in sys.meta_path
134 )
128 )
135
129
136
130
137 def disable():
131 def disable():
138 new_finders = []
132 new_finders = []
139 for finder in sys.meta_path:
133 for finder in sys.meta_path:
140 new_finders.append(
134 new_finders.append(
141 finder._finder if isinstance(finder, LazyFinder) else finder
135 finder._finder if isinstance(finder, LazyFinder) else finder
142 )
136 )
143 sys.meta_path[:] = new_finders
137 sys.meta_path[:] = new_finders
144
138
145
139
146 def enable():
140 def enable():
147 if not _supported:
148 return
149
150 new_finders = []
141 new_finders = []
151 for finder in sys.meta_path:
142 for finder in sys.meta_path:
152 new_finders.append(
143 new_finders.append(
153 LazyFinder(finder) if not isinstance(finder, LazyFinder) else finder
144 LazyFinder(finder) if not isinstance(finder, LazyFinder) else finder
154 )
145 )
155 sys.meta_path[:] = new_finders
146 sys.meta_path[:] = new_finders
156
147
157
148
158 @contextlib.contextmanager
149 @contextlib.contextmanager
159 def deactivated():
150 def deactivated():
160 # This implementation is a bit different from Python 2's. Python 3
151 # This implementation is a bit different from Python 2's. Python 3
161 # maintains a per-package finder cache in sys.path_importer_cache (see
152 # maintains a per-package finder cache in sys.path_importer_cache (see
162 # PEP 302). This means that we can't just call disable + enable.
153 # PEP 302). This means that we can't just call disable + enable.
163 # If we do that, in situations like:
154 # If we do that, in situations like:
164 #
155 #
165 # demandimport.enable()
156 # demandimport.enable()
166 # ...
157 # ...
167 # from foo.bar import mod1
158 # from foo.bar import mod1
168 # with demandimport.deactivated():
159 # with demandimport.deactivated():
169 # from foo.bar import mod2
160 # from foo.bar import mod2
170 #
161 #
171 # mod2 will be imported lazily. (The converse also holds -- whatever finder
162 # mod2 will be imported lazily. (The converse also holds -- whatever finder
172 # first gets cached will be used.)
163 # first gets cached will be used.)
173 #
164 #
174 # Instead, have a global flag the LazyLoader can use.
165 # Instead, have a global flag the LazyLoader can use.
175 global _deactivated
166 global _deactivated
176 demandenabled = isenabled()
167 demandenabled = isenabled()
177 if demandenabled:
168 if demandenabled:
178 _deactivated = True
169 _deactivated = True
179 try:
170 try:
180 yield
171 yield
181 finally:
172 finally:
182 if demandenabled:
173 if demandenabled:
183 _deactivated = False
174 _deactivated = False
General Comments 0
You need to be logged in to leave comments. Login now