|
1 # demandimportpy3 - global demand-loading of modules for Mercurial |
|
2 # |
|
3 # Copyright 2017 Facebook Inc. |
|
4 # |
|
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. |
|
7 |
|
8 """Lazy loading for Python 3.6 and above. |
|
9 |
|
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, |
|
12 but with a few additions: |
|
13 |
|
14 * Allow excluding certain modules from lazy imports. |
|
15 * Expose an interface that's substantially the same as demandimport for |
|
16 Python 2. |
|
17 |
|
18 This also has some limitations compared to the Python 2 implementation: |
|
19 |
|
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 |
|
22 practice, we only expect builtins to be loaded before demandimport is |
|
23 enabled. |
|
24 """ |
|
25 |
|
26 # This line is unnecessary, but it satisfies test-check-py3-compat.t. |
|
27 from __future__ import absolute_import |
|
28 |
|
29 import contextlib |
|
30 import os |
|
31 import sys |
|
32 |
|
33 import importlib.abc |
|
34 import importlib.machinery |
|
35 import importlib.util |
|
36 |
|
37 _deactivated = False |
|
38 |
|
39 class _lazyloaderex(importlib.util.LazyLoader): |
|
40 """This is a LazyLoader except it also follows the _deactivated global and |
|
41 the ignore list. |
|
42 """ |
|
43 def exec_module(self, module): |
|
44 """Make the module load lazily.""" |
|
45 if _deactivated or module.__name__ in ignore: |
|
46 self.loader.exec_module(module) |
|
47 else: |
|
48 super().exec_module(module) |
|
49 |
|
50 # This is 3.6+ because with Python 3.5 it isn't possible to lazily load |
|
51 # extensions. See the discussion in https://python.org/sf/26186 for more. |
|
52 _extensions_loader = _lazyloaderex.factory( |
|
53 importlib.machinery.ExtensionFileLoader) |
|
54 _bytecode_loader = _lazyloaderex.factory( |
|
55 importlib.machinery.SourcelessFileLoader) |
|
56 _source_loader = _lazyloaderex.factory(importlib.machinery.SourceFileLoader) |
|
57 |
|
58 def _makefinder(path): |
|
59 return importlib.machinery.FileFinder( |
|
60 path, |
|
61 # This is the order in which loaders are passed in in core Python. |
|
62 (_extensions_loader, importlib.machinery.EXTENSION_SUFFIXES), |
|
63 (_source_loader, importlib.machinery.SOURCE_SUFFIXES), |
|
64 (_bytecode_loader, importlib.machinery.BYTECODE_SUFFIXES), |
|
65 ) |
|
66 |
|
67 ignore = [] |
|
68 |
|
69 def init(ignorelist): |
|
70 global ignore |
|
71 ignore = ignorelist |
|
72 |
|
73 def isenabled(): |
|
74 return _makefinder in sys.path_hooks and not _deactivated |
|
75 |
|
76 def disable(): |
|
77 try: |
|
78 while True: |
|
79 sys.path_hooks.remove(_makefinder) |
|
80 except ValueError: |
|
81 pass |
|
82 |
|
83 def enable(): |
|
84 if os.environ.get('HGDEMANDIMPORT') != 'disable': |
|
85 sys.path_hooks.insert(0, _makefinder) |
|
86 |
|
87 @contextlib.contextmanager |
|
88 def deactivated(): |
|
89 # This implementation is a bit different from Python 2's. Python 3 |
|
90 # maintains a per-package finder cache in sys.path_importer_cache (see |
|
91 # PEP 302). This means that we can't just call disable + enable. |
|
92 # If we do that, in situations like: |
|
93 # |
|
94 # demandimport.enable() |
|
95 # ... |
|
96 # from foo.bar import mod1 |
|
97 # with demandimport.deactivated(): |
|
98 # from foo.bar import mod2 |
|
99 # |
|
100 # mod2 will be imported lazily. (The converse also holds -- whatever finder |
|
101 # first gets cached will be used.) |
|
102 # |
|
103 # Instead, have a global flag the LazyLoader can use. |
|
104 global _deactivated |
|
105 demandenabled = isenabled() |
|
106 if demandenabled: |
|
107 _deactivated = True |
|
108 try: |
|
109 yield |
|
110 finally: |
|
111 if demandenabled: |
|
112 _deactivated = False |