# HG changeset patch # User Siddharth Agarwal # Date 1495394584 25200 # Node ID 859496bb6db32a2c4f200edaed91c718ac59eb27 # Parent f37f9499fea8038487dcb1791eccd78ee8ae3c62 demandimport: add python 3 implementation This implementation uses the new importlib finder/loader functionality available in Python 3.5 and up. # no-check-commit diff -r f37f9499fea8 -r 859496bb6db3 hgdemandimport/__init__.py --- a/hgdemandimport/__init__.py Sun May 21 12:10:53 2017 -0700 +++ b/hgdemandimport/__init__.py Sun May 21 12:23:04 2017 -0700 @@ -15,7 +15,10 @@ import sys -from . import demandimportpy2 as demandimport +if sys.version_info[0] >= 3: + from . import demandimportpy3 as demandimport +else: + from . import demandimportpy2 as demandimport # Extensions can add to this list if necessary. ignore = [ diff -r f37f9499fea8 -r 859496bb6db3 hgdemandimport/demandimportpy3.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgdemandimport/demandimportpy3.py Sun May 21 12:23:04 2017 -0700 @@ -0,0 +1,112 @@ +# demandimportpy3 - global demand-loading of modules for Mercurial +# +# Copyright 2017 Facebook Inc. +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +"""Lazy loading for Python 3.6 and above. + +This uses the new importlib finder/loader functionality available in Python 3.5 +and up. The code reuses most of the mechanics implemented inside importlib.util, +but with a few additions: + +* Allow excluding certain modules from lazy imports. +* Expose an interface that's substantially the same as demandimport for + Python 2. + +This also has some limitations compared to the Python 2 implementation: + +* Much of the logic is per-package, not per-module, so any packages loaded + before demandimport is enabled will not be lazily imported in the future. In + practice, we only expect builtins to be loaded before demandimport is + enabled. +""" + +# This line is unnecessary, but it satisfies test-check-py3-compat.t. +from __future__ import absolute_import + +import contextlib +import os +import sys + +import importlib.abc +import importlib.machinery +import importlib.util + +_deactivated = False + +class _lazyloaderex(importlib.util.LazyLoader): + """This is a LazyLoader except it also follows the _deactivated global and + the ignore list. + """ + def exec_module(self, module): + """Make the module load lazily.""" + if _deactivated or module.__name__ in ignore: + self.loader.exec_module(module) + else: + super().exec_module(module) + +# This is 3.6+ because with Python 3.5 it isn't possible to lazily load +# extensions. See the discussion in https://python.org/sf/26186 for more. +_extensions_loader = _lazyloaderex.factory( + importlib.machinery.ExtensionFileLoader) +_bytecode_loader = _lazyloaderex.factory( + importlib.machinery.SourcelessFileLoader) +_source_loader = _lazyloaderex.factory(importlib.machinery.SourceFileLoader) + +def _makefinder(path): + return importlib.machinery.FileFinder( + path, + # This is the order in which loaders are passed in in core Python. + (_extensions_loader, importlib.machinery.EXTENSION_SUFFIXES), + (_source_loader, importlib.machinery.SOURCE_SUFFIXES), + (_bytecode_loader, importlib.machinery.BYTECODE_SUFFIXES), + ) + +ignore = [] + +def init(ignorelist): + global ignore + ignore = ignorelist + +def isenabled(): + return _makefinder in sys.path_hooks and not _deactivated + +def disable(): + try: + while True: + sys.path_hooks.remove(_makefinder) + except ValueError: + pass + +def enable(): + if os.environ.get('HGDEMANDIMPORT') != 'disable': + sys.path_hooks.insert(0, _makefinder) + +@contextlib.contextmanager +def deactivated(): + # This implementation is a bit different from Python 2's. Python 3 + # maintains a per-package finder cache in sys.path_importer_cache (see + # PEP 302). This means that we can't just call disable + enable. + # If we do that, in situations like: + # + # demandimport.enable() + # ... + # from foo.bar import mod1 + # with demandimport.deactivated(): + # from foo.bar import mod2 + # + # mod2 will be imported lazily. (The converse also holds -- whatever finder + # first gets cached will be used.) + # + # Instead, have a global flag the LazyLoader can use. + global _deactivated + demandenabled = isenabled() + if demandenabled: + _deactivated = True + try: + yield + finally: + if demandenabled: + _deactivated = False