##// END OF EJS Templates
demandimportpy3: only use lazy extension loader on Python 3.6+...
Gregory Szorc -
r43695:2d31ef3f stable
parent child Browse files
Show More
@@ -1,124 +1,128 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 from __future__ import absolute_import
27 from __future__ import absolute_import
28
28
29 import contextlib
29 import contextlib
30 import importlib.abc
30 import importlib.abc
31 import importlib.machinery
31 import importlib.machinery
32 import importlib.util
32 import importlib.util
33 import sys
33 import sys
34
34
35 from . import tracing
35 from . import tracing
36
36
37 _deactivated = False
37 _deactivated = False
38
38
39
39
40 class _lazyloaderex(importlib.util.LazyLoader):
40 class _lazyloaderex(importlib.util.LazyLoader):
41 """This is a LazyLoader except it also follows the _deactivated global and
41 """This is a LazyLoader except it also follows the _deactivated global and
42 the ignore list.
42 the ignore list.
43 """
43 """
44
44
45 def exec_module(self, module):
45 def exec_module(self, module):
46 """Make the module load lazily."""
46 """Make the module load lazily."""
47 with tracing.log('demandimport %s', module):
47 with tracing.log('demandimport %s', module):
48 if _deactivated or module.__name__ in ignores:
48 if _deactivated or module.__name__ in ignores:
49 self.loader.exec_module(module)
49 self.loader.exec_module(module)
50 else:
50 else:
51 super().exec_module(module)
51 super().exec_module(module)
52
52
53
53
54 # This is 3.6+ because with Python 3.5 it isn't possible to lazily load
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.
55 # extensions. See the discussion in https://bugs.python.org/issue26186 for more.
56 _extensions_loader = _lazyloaderex.factory(
56 if sys.version_info[0:2] >= (3, 6):
57 importlib.machinery.ExtensionFileLoader
57 _extensions_loader = _lazyloaderex.factory(
58 )
58 importlib.machinery.ExtensionFileLoader
59 )
60 else:
61 _extensions_loader = importlib.machinery.ExtensionFileLoader
62
59 _bytecode_loader = _lazyloaderex.factory(
63 _bytecode_loader = _lazyloaderex.factory(
60 importlib.machinery.SourcelessFileLoader
64 importlib.machinery.SourcelessFileLoader
61 )
65 )
62 _source_loader = _lazyloaderex.factory(importlib.machinery.SourceFileLoader)
66 _source_loader = _lazyloaderex.factory(importlib.machinery.SourceFileLoader)
63
67
64
68
65 def _makefinder(path):
69 def _makefinder(path):
66 return importlib.machinery.FileFinder(
70 return importlib.machinery.FileFinder(
67 path,
71 path,
68 # This is the order in which loaders are passed in in core Python.
72 # This is the order in which loaders are passed in in core Python.
69 (_extensions_loader, importlib.machinery.EXTENSION_SUFFIXES),
73 (_extensions_loader, importlib.machinery.EXTENSION_SUFFIXES),
70 (_source_loader, importlib.machinery.SOURCE_SUFFIXES),
74 (_source_loader, importlib.machinery.SOURCE_SUFFIXES),
71 (_bytecode_loader, importlib.machinery.BYTECODE_SUFFIXES),
75 (_bytecode_loader, importlib.machinery.BYTECODE_SUFFIXES),
72 )
76 )
73
77
74
78
75 ignores = set()
79 ignores = set()
76
80
77
81
78 def init(ignoreset):
82 def init(ignoreset):
79 global ignores
83 global ignores
80 ignores = ignoreset
84 ignores = ignoreset
81
85
82
86
83 def isenabled():
87 def isenabled():
84 return _makefinder in sys.path_hooks and not _deactivated
88 return _makefinder in sys.path_hooks and not _deactivated
85
89
86
90
87 def disable():
91 def disable():
88 try:
92 try:
89 while True:
93 while True:
90 sys.path_hooks.remove(_makefinder)
94 sys.path_hooks.remove(_makefinder)
91 except ValueError:
95 except ValueError:
92 pass
96 pass
93
97
94
98
95 def enable():
99 def enable():
96 sys.path_hooks.insert(0, _makefinder)
100 sys.path_hooks.insert(0, _makefinder)
97
101
98
102
99 @contextlib.contextmanager
103 @contextlib.contextmanager
100 def deactivated():
104 def deactivated():
101 # This implementation is a bit different from Python 2's. Python 3
105 # This implementation is a bit different from Python 2's. Python 3
102 # maintains a per-package finder cache in sys.path_importer_cache (see
106 # maintains a per-package finder cache in sys.path_importer_cache (see
103 # PEP 302). This means that we can't just call disable + enable.
107 # PEP 302). This means that we can't just call disable + enable.
104 # If we do that, in situations like:
108 # If we do that, in situations like:
105 #
109 #
106 # demandimport.enable()
110 # demandimport.enable()
107 # ...
111 # ...
108 # from foo.bar import mod1
112 # from foo.bar import mod1
109 # with demandimport.deactivated():
113 # with demandimport.deactivated():
110 # from foo.bar import mod2
114 # from foo.bar import mod2
111 #
115 #
112 # mod2 will be imported lazily. (The converse also holds -- whatever finder
116 # mod2 will be imported lazily. (The converse also holds -- whatever finder
113 # first gets cached will be used.)
117 # first gets cached will be used.)
114 #
118 #
115 # Instead, have a global flag the LazyLoader can use.
119 # Instead, have a global flag the LazyLoader can use.
116 global _deactivated
120 global _deactivated
117 demandenabled = isenabled()
121 demandenabled = isenabled()
118 if demandenabled:
122 if demandenabled:
119 _deactivated = True
123 _deactivated = True
120 try:
124 try:
121 yield
125 yield
122 finally:
126 finally:
123 if demandenabled:
127 if demandenabled:
124 _deactivated = False
128 _deactivated = False
General Comments 0
You need to be logged in to leave comments. Login now