##// END OF EJS Templates
demandimport: fix a crash in LazyFinder.__delattr__...
Matt Harbison -
r51054:48e38b17 stable
parent child Browse files
Show More
@@ -1,174 +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
36
37 class _lazyloaderex(importlib.util.LazyLoader):
37 class _lazyloaderex(importlib.util.LazyLoader):
38 """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
39 the ignore list.
39 the ignore list.
40 """
40 """
41
41
42 def exec_module(self, module):
42 def exec_module(self, module):
43 """Make the module load lazily."""
43 """Make the module load lazily."""
44 with tracing.log('demandimport %s', module):
44 with tracing.log('demandimport %s', module):
45 if _deactivated or module.__name__ in ignores:
45 if _deactivated or module.__name__ in ignores:
46 self.loader.exec_module(module)
46 self.loader.exec_module(module)
47 else:
47 else:
48 super().exec_module(module)
48 super().exec_module(module)
49
49
50
50
51 class LazyFinder:
51 class LazyFinder:
52 """A wrapper around a ``MetaPathFinder`` that makes loaders lazy.
52 """A wrapper around a ``MetaPathFinder`` that makes loaders lazy.
53
53
54 ``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
55 module. This returns a ``ModuleSpec`` if found or ``None``. The
55 module. This returns a ``ModuleSpec`` if found or ``None``. The
56 ``ModuleSpec`` has a ``loader`` attribute, which is called to actually
56 ``ModuleSpec`` has a ``loader`` attribute, which is called to actually
57 load a module.
57 load a module.
58
58
59 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
60 replace the ``loader`` with our lazy loader proxy.
60 replace the ``loader`` with our lazy loader proxy.
61
61
62 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
63 path finders don't support monkeypatching.
63 path finders don't support monkeypatching.
64 """
64 """
65
65
66 __slots__ = ("_finder",)
66 __slots__ = ("_finder",)
67
67
68 def __init__(self, finder):
68 def __init__(self, finder):
69 object.__setattr__(self, "_finder", finder)
69 object.__setattr__(self, "_finder", finder)
70
70
71 def __repr__(self):
71 def __repr__(self):
72 return "<LazyFinder for %r>" % object.__getattribute__(self, "_finder")
72 return "<LazyFinder for %r>" % object.__getattribute__(self, "_finder")
73
73
74 # __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
75 # defined via `def`.
75 # defined via `def`.
76 def __nonzero__(self):
76 def __nonzero__(self):
77 return bool(object.__getattribute__(self, "_finder"))
77 return bool(object.__getattribute__(self, "_finder"))
78
78
79 __bool__ = __nonzero__
79 __bool__ = __nonzero__
80
80
81 def __getattribute__(self, name):
81 def __getattribute__(self, name):
82 if name in ("_finder", "find_spec"):
82 if name in ("_finder", "find_spec"):
83 return object.__getattribute__(self, name)
83 return object.__getattribute__(self, name)
84
84
85 return getattr(object.__getattribute__(self, "_finder"), name)
85 return getattr(object.__getattribute__(self, "_finder"), name)
86
86
87 def __delattr__(self, name):
87 def __delattr__(self, name):
88 return delattr(object.__getattribute__(self, "_finder"))
88 return delattr(object.__getattribute__(self, "_finder"), name)
89
89
90 def __setattr__(self, name, value):
90 def __setattr__(self, name, value):
91 return setattr(object.__getattribute__(self, "_finder"), name, value)
91 return setattr(object.__getattribute__(self, "_finder"), name, value)
92
92
93 def find_spec(self, fullname, path, target=None):
93 def find_spec(self, fullname, path, target=None):
94 finder = object.__getattribute__(self, "_finder")
94 finder = object.__getattribute__(self, "_finder")
95 try:
95 try:
96 find_spec = finder.find_spec
96 find_spec = finder.find_spec
97 except AttributeError:
97 except AttributeError:
98 loader = finder.find_module(fullname, path)
98 loader = finder.find_module(fullname, path)
99 if loader is None:
99 if loader is None:
100 spec = None
100 spec = None
101 else:
101 else:
102 spec = importlib.util.spec_from_loader(fullname, loader)
102 spec = importlib.util.spec_from_loader(fullname, loader)
103 else:
103 else:
104 spec = find_spec(fullname, path, target)
104 spec = find_spec(fullname, path, target)
105
105
106 # Lazy loader requires exec_module().
106 # Lazy loader requires exec_module().
107 if (
107 if (
108 spec is not None
108 spec is not None
109 and spec.loader is not None
109 and spec.loader is not None
110 and getattr(spec.loader, "exec_module", None)
110 and getattr(spec.loader, "exec_module", None)
111 ):
111 ):
112 spec.loader = _lazyloaderex(spec.loader)
112 spec.loader = _lazyloaderex(spec.loader)
113
113
114 return spec
114 return spec
115
115
116
116
117 ignores = set()
117 ignores = set()
118
118
119
119
120 def init(ignoreset):
120 def init(ignoreset):
121 global ignores
121 global ignores
122 ignores = ignoreset
122 ignores = ignoreset
123
123
124
124
125 def isenabled():
125 def isenabled():
126 return not _deactivated and any(
126 return not _deactivated and any(
127 isinstance(finder, LazyFinder) for finder in sys.meta_path
127 isinstance(finder, LazyFinder) for finder in sys.meta_path
128 )
128 )
129
129
130
130
131 def disable():
131 def disable():
132 new_finders = []
132 new_finders = []
133 for finder in sys.meta_path:
133 for finder in sys.meta_path:
134 new_finders.append(
134 new_finders.append(
135 finder._finder if isinstance(finder, LazyFinder) else finder
135 finder._finder if isinstance(finder, LazyFinder) else finder
136 )
136 )
137 sys.meta_path[:] = new_finders
137 sys.meta_path[:] = new_finders
138
138
139
139
140 def enable():
140 def enable():
141 new_finders = []
141 new_finders = []
142 for finder in sys.meta_path:
142 for finder in sys.meta_path:
143 new_finders.append(
143 new_finders.append(
144 LazyFinder(finder) if not isinstance(finder, LazyFinder) else finder
144 LazyFinder(finder) if not isinstance(finder, LazyFinder) else finder
145 )
145 )
146 sys.meta_path[:] = new_finders
146 sys.meta_path[:] = new_finders
147
147
148
148
149 @contextlib.contextmanager
149 @contextlib.contextmanager
150 def deactivated():
150 def deactivated():
151 # 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
152 # 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
153 # 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.
154 # If we do that, in situations like:
154 # If we do that, in situations like:
155 #
155 #
156 # demandimport.enable()
156 # demandimport.enable()
157 # ...
157 # ...
158 # from foo.bar import mod1
158 # from foo.bar import mod1
159 # with demandimport.deactivated():
159 # with demandimport.deactivated():
160 # from foo.bar import mod2
160 # from foo.bar import mod2
161 #
161 #
162 # mod2 will be imported lazily. (The converse also holds -- whatever finder
162 # mod2 will be imported lazily. (The converse also holds -- whatever finder
163 # first gets cached will be used.)
163 # first gets cached will be used.)
164 #
164 #
165 # Instead, have a global flag the LazyLoader can use.
165 # Instead, have a global flag the LazyLoader can use.
166 global _deactivated
166 global _deactivated
167 demandenabled = isenabled()
167 demandenabled = isenabled()
168 if demandenabled:
168 if demandenabled:
169 _deactivated = True
169 _deactivated = True
170 try:
170 try:
171 yield
171 yield
172 finally:
172 finally:
173 if demandenabled:
173 if demandenabled:
174 _deactivated = False
174 _deactivated = False
General Comments 0
You need to be logged in to leave comments. Login now