hgdemandimport/demandimportpy3.py
changeset 32423 859496bb6db3
child 33859 8fb5212652ec
equal deleted inserted replaced
32422:f37f9499fea8 32423:859496bb6db3
       
     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