update windows build to Python 3.7
This commit is contained in:
parent
73105fa71e
commit
ddc59ab92d
5761 changed files with 750298 additions and 213405 deletions
176
Lib/importlib/__init__.py
Normal file
176
Lib/importlib/__init__.py
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
"""A pure Python implementation of import."""
|
||||
__all__ = ['__import__', 'import_module', 'invalidate_caches', 'reload']
|
||||
|
||||
# Bootstrap help #####################################################
|
||||
|
||||
# Until bootstrapping is complete, DO NOT import any modules that attempt
|
||||
# to import importlib._bootstrap (directly or indirectly). Since this
|
||||
# partially initialised package would be present in sys.modules, those
|
||||
# modules would get an uninitialised copy of the source version, instead
|
||||
# of a fully initialised version (either the frozen one or the one
|
||||
# initialised below if the frozen one is not available).
|
||||
import _imp # Just the builtin component, NOT the full Python module
|
||||
import sys
|
||||
|
||||
try:
|
||||
import _frozen_importlib as _bootstrap
|
||||
except ImportError:
|
||||
from . import _bootstrap
|
||||
_bootstrap._setup(sys, _imp)
|
||||
else:
|
||||
# importlib._bootstrap is the built-in import, ensure we don't create
|
||||
# a second copy of the module.
|
||||
_bootstrap.__name__ = 'importlib._bootstrap'
|
||||
_bootstrap.__package__ = 'importlib'
|
||||
try:
|
||||
_bootstrap.__file__ = __file__.replace('__init__.py', '_bootstrap.py')
|
||||
except NameError:
|
||||
# __file__ is not guaranteed to be defined, e.g. if this code gets
|
||||
# frozen by a tool like cx_Freeze.
|
||||
pass
|
||||
sys.modules['importlib._bootstrap'] = _bootstrap
|
||||
|
||||
try:
|
||||
import _frozen_importlib_external as _bootstrap_external
|
||||
except ImportError:
|
||||
from . import _bootstrap_external
|
||||
_bootstrap_external._setup(_bootstrap)
|
||||
_bootstrap._bootstrap_external = _bootstrap_external
|
||||
else:
|
||||
_bootstrap_external.__name__ = 'importlib._bootstrap_external'
|
||||
_bootstrap_external.__package__ = 'importlib'
|
||||
try:
|
||||
_bootstrap_external.__file__ = __file__.replace('__init__.py', '_bootstrap_external.py')
|
||||
except NameError:
|
||||
# __file__ is not guaranteed to be defined, e.g. if this code gets
|
||||
# frozen by a tool like cx_Freeze.
|
||||
pass
|
||||
sys.modules['importlib._bootstrap_external'] = _bootstrap_external
|
||||
|
||||
# To simplify imports in test code
|
||||
_w_long = _bootstrap_external._w_long
|
||||
_r_long = _bootstrap_external._r_long
|
||||
|
||||
# Fully bootstrapped at this point, import whatever you like, circular
|
||||
# dependencies and startup overhead minimisation permitting :)
|
||||
|
||||
import types
|
||||
import warnings
|
||||
|
||||
|
||||
# Public API #########################################################
|
||||
|
||||
from ._bootstrap import __import__
|
||||
|
||||
|
||||
def invalidate_caches():
|
||||
"""Call the invalidate_caches() method on all meta path finders stored in
|
||||
sys.meta_path (where implemented)."""
|
||||
for finder in sys.meta_path:
|
||||
if hasattr(finder, 'invalidate_caches'):
|
||||
finder.invalidate_caches()
|
||||
|
||||
|
||||
def find_loader(name, path=None):
|
||||
"""Return the loader for the specified module.
|
||||
|
||||
This is a backward-compatible wrapper around find_spec().
|
||||
|
||||
This function is deprecated in favor of importlib.util.find_spec().
|
||||
|
||||
"""
|
||||
warnings.warn('Deprecated since Python 3.4. '
|
||||
'Use importlib.util.find_spec() instead.',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
try:
|
||||
loader = sys.modules[name].__loader__
|
||||
if loader is None:
|
||||
raise ValueError('{}.__loader__ is None'.format(name))
|
||||
else:
|
||||
return loader
|
||||
except KeyError:
|
||||
pass
|
||||
except AttributeError:
|
||||
raise ValueError('{}.__loader__ is not set'.format(name)) from None
|
||||
|
||||
spec = _bootstrap._find_spec(name, path)
|
||||
# We won't worry about malformed specs (missing attributes).
|
||||
if spec is None:
|
||||
return None
|
||||
if spec.loader is None:
|
||||
if spec.submodule_search_locations is None:
|
||||
raise ImportError('spec for {} missing loader'.format(name),
|
||||
name=name)
|
||||
raise ImportError('namespace packages do not have loaders',
|
||||
name=name)
|
||||
return spec.loader
|
||||
|
||||
|
||||
def import_module(name, package=None):
|
||||
"""Import a module.
|
||||
|
||||
The 'package' argument is required when performing a relative import. It
|
||||
specifies the package to use as the anchor point from which to resolve the
|
||||
relative import to an absolute import.
|
||||
|
||||
"""
|
||||
level = 0
|
||||
if name.startswith('.'):
|
||||
if not package:
|
||||
msg = ("the 'package' argument is required to perform a relative "
|
||||
"import for {!r}")
|
||||
raise TypeError(msg.format(name))
|
||||
for character in name:
|
||||
if character != '.':
|
||||
break
|
||||
level += 1
|
||||
return _bootstrap._gcd_import(name[level:], package, level)
|
||||
|
||||
|
||||
_RELOADING = {}
|
||||
|
||||
|
||||
def reload(module):
|
||||
"""Reload the module and return it.
|
||||
|
||||
The module must have been successfully imported before.
|
||||
|
||||
"""
|
||||
if not module or not isinstance(module, types.ModuleType):
|
||||
raise TypeError("reload() argument must be a module")
|
||||
try:
|
||||
name = module.__spec__.name
|
||||
except AttributeError:
|
||||
name = module.__name__
|
||||
|
||||
if sys.modules.get(name) is not module:
|
||||
msg = "module {} not in sys.modules"
|
||||
raise ImportError(msg.format(name), name=name)
|
||||
if name in _RELOADING:
|
||||
return _RELOADING[name]
|
||||
_RELOADING[name] = module
|
||||
try:
|
||||
parent_name = name.rpartition('.')[0]
|
||||
if parent_name:
|
||||
try:
|
||||
parent = sys.modules[parent_name]
|
||||
except KeyError:
|
||||
msg = "parent {!r} not in sys.modules"
|
||||
raise ImportError(msg.format(parent_name),
|
||||
name=parent_name) from None
|
||||
else:
|
||||
pkgpath = parent.__path__
|
||||
else:
|
||||
pkgpath = None
|
||||
target = module
|
||||
spec = module.__spec__ = _bootstrap._find_spec(name, pkgpath, target)
|
||||
if spec is None:
|
||||
raise ModuleNotFoundError(f"spec not found for the module {name!r}", name=name)
|
||||
_bootstrap._exec(spec, module)
|
||||
# The module may have replaced itself in sys.modules!
|
||||
return sys.modules[name]
|
||||
finally:
|
||||
try:
|
||||
del _RELOADING[name]
|
||||
except KeyError:
|
||||
pass
|
||||
BIN
Lib/importlib/__pycache__/__init__.cpython-37.pyc
Normal file
BIN
Lib/importlib/__pycache__/__init__.cpython-37.pyc
Normal file
Binary file not shown.
BIN
Lib/importlib/__pycache__/abc.cpython-37.pyc
Normal file
BIN
Lib/importlib/__pycache__/abc.cpython-37.pyc
Normal file
Binary file not shown.
BIN
Lib/importlib/__pycache__/machinery.cpython-37.pyc
Normal file
BIN
Lib/importlib/__pycache__/machinery.cpython-37.pyc
Normal file
Binary file not shown.
BIN
Lib/importlib/__pycache__/util.cpython-37.pyc
Normal file
BIN
Lib/importlib/__pycache__/util.cpython-37.pyc
Normal file
Binary file not shown.
1164
Lib/importlib/_bootstrap.py
Normal file
1164
Lib/importlib/_bootstrap.py
Normal file
File diff suppressed because it is too large
Load diff
1562
Lib/importlib/_bootstrap_external.py
Normal file
1562
Lib/importlib/_bootstrap_external.py
Normal file
File diff suppressed because it is too large
Load diff
388
Lib/importlib/abc.py
Normal file
388
Lib/importlib/abc.py
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
"""Abstract base classes related to import."""
|
||||
from . import _bootstrap
|
||||
from . import _bootstrap_external
|
||||
from . import machinery
|
||||
try:
|
||||
import _frozen_importlib
|
||||
except ImportError as exc:
|
||||
if exc.name != '_frozen_importlib':
|
||||
raise
|
||||
_frozen_importlib = None
|
||||
try:
|
||||
import _frozen_importlib_external
|
||||
except ImportError as exc:
|
||||
_frozen_importlib_external = _bootstrap_external
|
||||
import abc
|
||||
import warnings
|
||||
|
||||
|
||||
def _register(abstract_cls, *classes):
|
||||
for cls in classes:
|
||||
abstract_cls.register(cls)
|
||||
if _frozen_importlib is not None:
|
||||
try:
|
||||
frozen_cls = getattr(_frozen_importlib, cls.__name__)
|
||||
except AttributeError:
|
||||
frozen_cls = getattr(_frozen_importlib_external, cls.__name__)
|
||||
abstract_cls.register(frozen_cls)
|
||||
|
||||
|
||||
class Finder(metaclass=abc.ABCMeta):
|
||||
|
||||
"""Legacy abstract base class for import finders.
|
||||
|
||||
It may be subclassed for compatibility with legacy third party
|
||||
reimplementations of the import system. Otherwise, finder
|
||||
implementations should derive from the more specific MetaPathFinder
|
||||
or PathEntryFinder ABCs.
|
||||
|
||||
Deprecated since Python 3.3
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_module(self, fullname, path=None):
|
||||
"""An abstract method that should find a module.
|
||||
The fullname is a str and the optional path is a str or None.
|
||||
Returns a Loader object or None.
|
||||
"""
|
||||
|
||||
|
||||
class MetaPathFinder(Finder):
|
||||
|
||||
"""Abstract base class for import finders on sys.meta_path."""
|
||||
|
||||
# We don't define find_spec() here since that would break
|
||||
# hasattr checks we do to support backward compatibility.
|
||||
|
||||
def find_module(self, fullname, path):
|
||||
"""Return a loader for the module.
|
||||
|
||||
If no module is found, return None. The fullname is a str and
|
||||
the path is a list of strings or None.
|
||||
|
||||
This method is deprecated since Python 3.4 in favor of
|
||||
finder.find_spec(). If find_spec() exists then backwards-compatible
|
||||
functionality is provided for this method.
|
||||
|
||||
"""
|
||||
warnings.warn("MetaPathFinder.find_module() is deprecated since Python "
|
||||
"3.4 in favor of MetaPathFinder.find_spec() "
|
||||
"(available since 3.4)",
|
||||
DeprecationWarning,
|
||||
stacklevel=2)
|
||||
if not hasattr(self, 'find_spec'):
|
||||
return None
|
||||
found = self.find_spec(fullname, path)
|
||||
return found.loader if found is not None else None
|
||||
|
||||
def invalidate_caches(self):
|
||||
"""An optional method for clearing the finder's cache, if any.
|
||||
This method is used by importlib.invalidate_caches().
|
||||
"""
|
||||
|
||||
_register(MetaPathFinder, machinery.BuiltinImporter, machinery.FrozenImporter,
|
||||
machinery.PathFinder, machinery.WindowsRegistryFinder)
|
||||
|
||||
|
||||
class PathEntryFinder(Finder):
|
||||
|
||||
"""Abstract base class for path entry finders used by PathFinder."""
|
||||
|
||||
# We don't define find_spec() here since that would break
|
||||
# hasattr checks we do to support backward compatibility.
|
||||
|
||||
def find_loader(self, fullname):
|
||||
"""Return (loader, namespace portion) for the path entry.
|
||||
|
||||
The fullname is a str. The namespace portion is a sequence of
|
||||
path entries contributing to part of a namespace package. The
|
||||
sequence may be empty. If loader is not None, the portion will
|
||||
be ignored.
|
||||
|
||||
The portion will be discarded if another path entry finder
|
||||
locates the module as a normal module or package.
|
||||
|
||||
This method is deprecated since Python 3.4 in favor of
|
||||
finder.find_spec(). If find_spec() is provided than backwards-compatible
|
||||
functionality is provided.
|
||||
"""
|
||||
warnings.warn("PathEntryFinder.find_loader() is deprecated since Python "
|
||||
"3.4 in favor of PathEntryFinder.find_spec() "
|
||||
"(available since 3.4)",
|
||||
DeprecationWarning,
|
||||
stacklevel=2)
|
||||
if not hasattr(self, 'find_spec'):
|
||||
return None, []
|
||||
found = self.find_spec(fullname)
|
||||
if found is not None:
|
||||
if not found.submodule_search_locations:
|
||||
portions = []
|
||||
else:
|
||||
portions = found.submodule_search_locations
|
||||
return found.loader, portions
|
||||
else:
|
||||
return None, []
|
||||
|
||||
find_module = _bootstrap_external._find_module_shim
|
||||
|
||||
def invalidate_caches(self):
|
||||
"""An optional method for clearing the finder's cache, if any.
|
||||
This method is used by PathFinder.invalidate_caches().
|
||||
"""
|
||||
|
||||
_register(PathEntryFinder, machinery.FileFinder)
|
||||
|
||||
|
||||
class Loader(metaclass=abc.ABCMeta):
|
||||
|
||||
"""Abstract base class for import loaders."""
|
||||
|
||||
def create_module(self, spec):
|
||||
"""Return a module to initialize and into which to load.
|
||||
|
||||
This method should raise ImportError if anything prevents it
|
||||
from creating a new module. It may return None to indicate
|
||||
that the spec should create the new module.
|
||||
"""
|
||||
# By default, defer to default semantics for the new module.
|
||||
return None
|
||||
|
||||
# We don't define exec_module() here since that would break
|
||||
# hasattr checks we do to support backward compatibility.
|
||||
|
||||
def load_module(self, fullname):
|
||||
"""Return the loaded module.
|
||||
|
||||
The module must be added to sys.modules and have import-related
|
||||
attributes set properly. The fullname is a str.
|
||||
|
||||
ImportError is raised on failure.
|
||||
|
||||
This method is deprecated in favor of loader.exec_module(). If
|
||||
exec_module() exists then it is used to provide a backwards-compatible
|
||||
functionality for this method.
|
||||
|
||||
"""
|
||||
if not hasattr(self, 'exec_module'):
|
||||
raise ImportError
|
||||
return _bootstrap._load_module_shim(self, fullname)
|
||||
|
||||
def module_repr(self, module):
|
||||
"""Return a module's repr.
|
||||
|
||||
Used by the module type when the method does not raise
|
||||
NotImplementedError.
|
||||
|
||||
This method is deprecated.
|
||||
|
||||
"""
|
||||
# The exception will cause ModuleType.__repr__ to ignore this method.
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ResourceLoader(Loader):
|
||||
|
||||
"""Abstract base class for loaders which can return data from their
|
||||
back-end storage.
|
||||
|
||||
This ABC represents one of the optional protocols specified by PEP 302.
|
||||
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_data(self, path):
|
||||
"""Abstract method which when implemented should return the bytes for
|
||||
the specified path. The path must be a str."""
|
||||
raise OSError
|
||||
|
||||
|
||||
class InspectLoader(Loader):
|
||||
|
||||
"""Abstract base class for loaders which support inspection about the
|
||||
modules they can load.
|
||||
|
||||
This ABC represents one of the optional protocols specified by PEP 302.
|
||||
|
||||
"""
|
||||
|
||||
def is_package(self, fullname):
|
||||
"""Optional method which when implemented should return whether the
|
||||
module is a package. The fullname is a str. Returns a bool.
|
||||
|
||||
Raises ImportError if the module cannot be found.
|
||||
"""
|
||||
raise ImportError
|
||||
|
||||
def get_code(self, fullname):
|
||||
"""Method which returns the code object for the module.
|
||||
|
||||
The fullname is a str. Returns a types.CodeType if possible, else
|
||||
returns None if a code object does not make sense
|
||||
(e.g. built-in module). Raises ImportError if the module cannot be
|
||||
found.
|
||||
"""
|
||||
source = self.get_source(fullname)
|
||||
if source is None:
|
||||
return None
|
||||
return self.source_to_code(source)
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_source(self, fullname):
|
||||
"""Abstract method which should return the source code for the
|
||||
module. The fullname is a str. Returns a str.
|
||||
|
||||
Raises ImportError if the module cannot be found.
|
||||
"""
|
||||
raise ImportError
|
||||
|
||||
@staticmethod
|
||||
def source_to_code(data, path='<string>'):
|
||||
"""Compile 'data' into a code object.
|
||||
|
||||
The 'data' argument can be anything that compile() can handle. The'path'
|
||||
argument should be where the data was retrieved (when applicable)."""
|
||||
return compile(data, path, 'exec', dont_inherit=True)
|
||||
|
||||
exec_module = _bootstrap_external._LoaderBasics.exec_module
|
||||
load_module = _bootstrap_external._LoaderBasics.load_module
|
||||
|
||||
_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter)
|
||||
|
||||
|
||||
class ExecutionLoader(InspectLoader):
|
||||
|
||||
"""Abstract base class for loaders that wish to support the execution of
|
||||
modules as scripts.
|
||||
|
||||
This ABC represents one of the optional protocols specified in PEP 302.
|
||||
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_filename(self, fullname):
|
||||
"""Abstract method which should return the value that __file__ is to be
|
||||
set to.
|
||||
|
||||
Raises ImportError if the module cannot be found.
|
||||
"""
|
||||
raise ImportError
|
||||
|
||||
def get_code(self, fullname):
|
||||
"""Method to return the code object for fullname.
|
||||
|
||||
Should return None if not applicable (e.g. built-in module).
|
||||
Raise ImportError if the module cannot be found.
|
||||
"""
|
||||
source = self.get_source(fullname)
|
||||
if source is None:
|
||||
return None
|
||||
try:
|
||||
path = self.get_filename(fullname)
|
||||
except ImportError:
|
||||
return self.source_to_code(source)
|
||||
else:
|
||||
return self.source_to_code(source, path)
|
||||
|
||||
_register(ExecutionLoader, machinery.ExtensionFileLoader)
|
||||
|
||||
|
||||
class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader):
|
||||
|
||||
"""Abstract base class partially implementing the ResourceLoader and
|
||||
ExecutionLoader ABCs."""
|
||||
|
||||
_register(FileLoader, machinery.SourceFileLoader,
|
||||
machinery.SourcelessFileLoader)
|
||||
|
||||
|
||||
class SourceLoader(_bootstrap_external.SourceLoader, ResourceLoader, ExecutionLoader):
|
||||
|
||||
"""Abstract base class for loading source code (and optionally any
|
||||
corresponding bytecode).
|
||||
|
||||
To support loading from source code, the abstractmethods inherited from
|
||||
ResourceLoader and ExecutionLoader need to be implemented. To also support
|
||||
loading from bytecode, the optional methods specified directly by this ABC
|
||||
is required.
|
||||
|
||||
Inherited abstractmethods not implemented in this ABC:
|
||||
|
||||
* ResourceLoader.get_data
|
||||
* ExecutionLoader.get_filename
|
||||
|
||||
"""
|
||||
|
||||
def path_mtime(self, path):
|
||||
"""Return the (int) modification time for the path (str)."""
|
||||
if self.path_stats.__func__ is SourceLoader.path_stats:
|
||||
raise OSError
|
||||
return int(self.path_stats(path)['mtime'])
|
||||
|
||||
def path_stats(self, path):
|
||||
"""Return a metadata dict for the source pointed to by the path (str).
|
||||
Possible keys:
|
||||
- 'mtime' (mandatory) is the numeric timestamp of last source
|
||||
code modification;
|
||||
- 'size' (optional) is the size in bytes of the source code.
|
||||
"""
|
||||
if self.path_mtime.__func__ is SourceLoader.path_mtime:
|
||||
raise OSError
|
||||
return {'mtime': self.path_mtime(path)}
|
||||
|
||||
def set_data(self, path, data):
|
||||
"""Write the bytes to the path (if possible).
|
||||
|
||||
Accepts a str path and data as bytes.
|
||||
|
||||
Any needed intermediary directories are to be created. If for some
|
||||
reason the file cannot be written because of permissions, fail
|
||||
silently.
|
||||
"""
|
||||
|
||||
_register(SourceLoader, machinery.SourceFileLoader)
|
||||
|
||||
|
||||
class ResourceReader(metaclass=abc.ABCMeta):
|
||||
|
||||
"""Abstract base class to provide resource-reading support.
|
||||
|
||||
Loaders that support resource reading are expected to implement
|
||||
the ``get_resource_reader(fullname)`` method and have it either return None
|
||||
or an object compatible with this ABC.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def open_resource(self, resource):
|
||||
"""Return an opened, file-like object for binary reading.
|
||||
|
||||
The 'resource' argument is expected to represent only a file name
|
||||
and thus not contain any subdirectory components.
|
||||
|
||||
If the resource cannot be found, FileNotFoundError is raised.
|
||||
"""
|
||||
raise FileNotFoundError
|
||||
|
||||
@abc.abstractmethod
|
||||
def resource_path(self, resource):
|
||||
"""Return the file system path to the specified resource.
|
||||
|
||||
The 'resource' argument is expected to represent only a file name
|
||||
and thus not contain any subdirectory components.
|
||||
|
||||
If the resource does not exist on the file system, raise
|
||||
FileNotFoundError.
|
||||
"""
|
||||
raise FileNotFoundError
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_resource(self, name):
|
||||
"""Return True if the named 'name' is consider a resource."""
|
||||
raise FileNotFoundError
|
||||
|
||||
@abc.abstractmethod
|
||||
def contents(self):
|
||||
"""Return an iterable of strings over the contents of the package."""
|
||||
return []
|
||||
|
||||
|
||||
_register(ResourceReader, machinery.SourceFileLoader)
|
||||
21
Lib/importlib/machinery.py
Normal file
21
Lib/importlib/machinery.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"""The machinery of importlib: finders, loaders, hooks, etc."""
|
||||
|
||||
import _imp
|
||||
|
||||
from ._bootstrap import ModuleSpec
|
||||
from ._bootstrap import BuiltinImporter
|
||||
from ._bootstrap import FrozenImporter
|
||||
from ._bootstrap_external import (SOURCE_SUFFIXES, DEBUG_BYTECODE_SUFFIXES,
|
||||
OPTIMIZED_BYTECODE_SUFFIXES, BYTECODE_SUFFIXES,
|
||||
EXTENSION_SUFFIXES)
|
||||
from ._bootstrap_external import WindowsRegistryFinder
|
||||
from ._bootstrap_external import PathFinder
|
||||
from ._bootstrap_external import FileFinder
|
||||
from ._bootstrap_external import SourceFileLoader
|
||||
from ._bootstrap_external import SourcelessFileLoader
|
||||
from ._bootstrap_external import ExtensionFileLoader
|
||||
|
||||
|
||||
def all_suffixes():
|
||||
"""Returns a list of all recognized module suffixes for this process"""
|
||||
return SOURCE_SUFFIXES + BYTECODE_SUFFIXES + EXTENSION_SUFFIXES
|
||||
343
Lib/importlib/resources.py
Normal file
343
Lib/importlib/resources.py
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
import os
|
||||
import tempfile
|
||||
|
||||
from . import abc as resources_abc
|
||||
from contextlib import contextmanager, suppress
|
||||
from importlib import import_module
|
||||
from importlib.abc import ResourceLoader
|
||||
from io import BytesIO, TextIOWrapper
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Iterable, Iterator, Optional, Set, Union # noqa: F401
|
||||
from typing import cast
|
||||
from typing.io import BinaryIO, TextIO
|
||||
from zipimport import ZipImportError
|
||||
|
||||
|
||||
__all__ = [
|
||||
'Package',
|
||||
'Resource',
|
||||
'contents',
|
||||
'is_resource',
|
||||
'open_binary',
|
||||
'open_text',
|
||||
'path',
|
||||
'read_binary',
|
||||
'read_text',
|
||||
]
|
||||
|
||||
|
||||
Package = Union[str, ModuleType]
|
||||
Resource = Union[str, os.PathLike]
|
||||
|
||||
|
||||
def _get_package(package) -> ModuleType:
|
||||
"""Take a package name or module object and return the module.
|
||||
|
||||
If a name, the module is imported. If the passed or imported module
|
||||
object is not a package, raise an exception.
|
||||
"""
|
||||
if hasattr(package, '__spec__'):
|
||||
if package.__spec__.submodule_search_locations is None:
|
||||
raise TypeError('{!r} is not a package'.format(
|
||||
package.__spec__.name))
|
||||
else:
|
||||
return package
|
||||
else:
|
||||
module = import_module(package)
|
||||
if module.__spec__.submodule_search_locations is None:
|
||||
raise TypeError('{!r} is not a package'.format(package))
|
||||
else:
|
||||
return module
|
||||
|
||||
|
||||
def _normalize_path(path) -> str:
|
||||
"""Normalize a path by ensuring it is a string.
|
||||
|
||||
If the resulting string contains path separators, an exception is raised.
|
||||
"""
|
||||
parent, file_name = os.path.split(path)
|
||||
if parent:
|
||||
raise ValueError('{!r} must be only a file name'.format(path))
|
||||
else:
|
||||
return file_name
|
||||
|
||||
|
||||
def _get_resource_reader(
|
||||
package: ModuleType) -> Optional[resources_abc.ResourceReader]:
|
||||
# Return the package's loader if it's a ResourceReader. We can't use
|
||||
# a issubclass() check here because apparently abc.'s __subclasscheck__()
|
||||
# hook wants to create a weak reference to the object, but
|
||||
# zipimport.zipimporter does not support weak references, resulting in a
|
||||
# TypeError. That seems terrible.
|
||||
spec = package.__spec__
|
||||
if hasattr(spec.loader, 'get_resource_reader'):
|
||||
return cast(resources_abc.ResourceReader,
|
||||
spec.loader.get_resource_reader(spec.name))
|
||||
return None
|
||||
|
||||
|
||||
def _check_location(package):
|
||||
if package.__spec__.origin is None or not package.__spec__.has_location:
|
||||
raise FileNotFoundError(f'Package has no location {package!r}')
|
||||
|
||||
|
||||
def open_binary(package: Package, resource: Resource) -> BinaryIO:
|
||||
"""Return a file-like object opened for binary reading of the resource."""
|
||||
resource = _normalize_path(resource)
|
||||
package = _get_package(package)
|
||||
reader = _get_resource_reader(package)
|
||||
if reader is not None:
|
||||
return reader.open_resource(resource)
|
||||
_check_location(package)
|
||||
absolute_package_path = os.path.abspath(package.__spec__.origin)
|
||||
package_path = os.path.dirname(absolute_package_path)
|
||||
full_path = os.path.join(package_path, resource)
|
||||
try:
|
||||
return open(full_path, mode='rb')
|
||||
except OSError:
|
||||
# Just assume the loader is a resource loader; all the relevant
|
||||
# importlib.machinery loaders are and an AttributeError for
|
||||
# get_data() will make it clear what is needed from the loader.
|
||||
loader = cast(ResourceLoader, package.__spec__.loader)
|
||||
data = None
|
||||
if hasattr(package.__spec__.loader, 'get_data'):
|
||||
with suppress(OSError):
|
||||
data = loader.get_data(full_path)
|
||||
if data is None:
|
||||
package_name = package.__spec__.name
|
||||
message = '{!r} resource not found in {!r}'.format(
|
||||
resource, package_name)
|
||||
raise FileNotFoundError(message)
|
||||
else:
|
||||
return BytesIO(data)
|
||||
|
||||
|
||||
def open_text(package: Package,
|
||||
resource: Resource,
|
||||
encoding: str = 'utf-8',
|
||||
errors: str = 'strict') -> TextIO:
|
||||
"""Return a file-like object opened for text reading of the resource."""
|
||||
resource = _normalize_path(resource)
|
||||
package = _get_package(package)
|
||||
reader = _get_resource_reader(package)
|
||||
if reader is not None:
|
||||
return TextIOWrapper(reader.open_resource(resource), encoding, errors)
|
||||
_check_location(package)
|
||||
absolute_package_path = os.path.abspath(package.__spec__.origin)
|
||||
package_path = os.path.dirname(absolute_package_path)
|
||||
full_path = os.path.join(package_path, resource)
|
||||
try:
|
||||
return open(full_path, mode='r', encoding=encoding, errors=errors)
|
||||
except OSError:
|
||||
# Just assume the loader is a resource loader; all the relevant
|
||||
# importlib.machinery loaders are and an AttributeError for
|
||||
# get_data() will make it clear what is needed from the loader.
|
||||
loader = cast(ResourceLoader, package.__spec__.loader)
|
||||
data = None
|
||||
if hasattr(package.__spec__.loader, 'get_data'):
|
||||
with suppress(OSError):
|
||||
data = loader.get_data(full_path)
|
||||
if data is None:
|
||||
package_name = package.__spec__.name
|
||||
message = '{!r} resource not found in {!r}'.format(
|
||||
resource, package_name)
|
||||
raise FileNotFoundError(message)
|
||||
else:
|
||||
return TextIOWrapper(BytesIO(data), encoding, errors)
|
||||
|
||||
|
||||
def read_binary(package: Package, resource: Resource) -> bytes:
|
||||
"""Return the binary contents of the resource."""
|
||||
resource = _normalize_path(resource)
|
||||
package = _get_package(package)
|
||||
with open_binary(package, resource) as fp:
|
||||
return fp.read()
|
||||
|
||||
|
||||
def read_text(package: Package,
|
||||
resource: Resource,
|
||||
encoding: str = 'utf-8',
|
||||
errors: str = 'strict') -> str:
|
||||
"""Return the decoded string of the resource.
|
||||
|
||||
The decoding-related arguments have the same semantics as those of
|
||||
bytes.decode().
|
||||
"""
|
||||
resource = _normalize_path(resource)
|
||||
package = _get_package(package)
|
||||
with open_text(package, resource, encoding, errors) as fp:
|
||||
return fp.read()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def path(package: Package, resource: Resource) -> Iterator[Path]:
|
||||
"""A context manager providing a file path object to the resource.
|
||||
|
||||
If the resource does not already exist on its own on the file system,
|
||||
a temporary file will be created. If the file was created, the file
|
||||
will be deleted upon exiting the context manager (no exception is
|
||||
raised if the file was deleted prior to the context manager
|
||||
exiting).
|
||||
"""
|
||||
resource = _normalize_path(resource)
|
||||
package = _get_package(package)
|
||||
reader = _get_resource_reader(package)
|
||||
if reader is not None:
|
||||
try:
|
||||
yield Path(reader.resource_path(resource))
|
||||
return
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
else:
|
||||
_check_location(package)
|
||||
# Fall-through for both the lack of resource_path() *and* if
|
||||
# resource_path() raises FileNotFoundError.
|
||||
package_directory = Path(package.__spec__.origin).parent
|
||||
file_path = package_directory / resource
|
||||
if file_path.exists():
|
||||
yield file_path
|
||||
else:
|
||||
with open_binary(package, resource) as fp:
|
||||
data = fp.read()
|
||||
# Not using tempfile.NamedTemporaryFile as it leads to deeper 'try'
|
||||
# blocks due to the need to close the temporary file to work on
|
||||
# Windows properly.
|
||||
fd, raw_path = tempfile.mkstemp()
|
||||
try:
|
||||
os.write(fd, data)
|
||||
os.close(fd)
|
||||
yield Path(raw_path)
|
||||
finally:
|
||||
try:
|
||||
os.remove(raw_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def is_resource(package: Package, name: str) -> bool:
|
||||
"""True if 'name' is a resource inside 'package'.
|
||||
|
||||
Directories are *not* resources.
|
||||
"""
|
||||
package = _get_package(package)
|
||||
_normalize_path(name)
|
||||
reader = _get_resource_reader(package)
|
||||
if reader is not None:
|
||||
return reader.is_resource(name)
|
||||
try:
|
||||
package_contents = set(contents(package))
|
||||
except (NotADirectoryError, FileNotFoundError):
|
||||
return False
|
||||
if name not in package_contents:
|
||||
return False
|
||||
# Just because the given file_name lives as an entry in the package's
|
||||
# contents doesn't necessarily mean it's a resource. Directories are not
|
||||
# resources, so let's try to find out if it's a directory or not.
|
||||
path = Path(package.__spec__.origin).parent / name
|
||||
return path.is_file()
|
||||
|
||||
|
||||
def contents(package: Package) -> Iterable[str]:
|
||||
"""Return an iterable of entries in 'package'.
|
||||
|
||||
Note that not all entries are resources. Specifically, directories are
|
||||
not considered resources. Use `is_resource()` on each entry returned here
|
||||
to check if it is a resource or not.
|
||||
"""
|
||||
package = _get_package(package)
|
||||
reader = _get_resource_reader(package)
|
||||
if reader is not None:
|
||||
return reader.contents()
|
||||
# Is the package a namespace package? By definition, namespace packages
|
||||
# cannot have resources. We could use _check_location() and catch the
|
||||
# exception, but that's extra work, so just inline the check.
|
||||
elif package.__spec__.origin is None or not package.__spec__.has_location:
|
||||
return ()
|
||||
else:
|
||||
package_directory = Path(package.__spec__.origin).parent
|
||||
return os.listdir(package_directory)
|
||||
|
||||
|
||||
# Private implementation of ResourceReader and get_resource_reader() called
|
||||
# from zipimport.c. Don't use these directly! We're implementing these in
|
||||
# Python because 1) it's easier, 2) zipimport may get rewritten in Python
|
||||
# itself at some point, so doing this all in C would difficult and a waste of
|
||||
# effort.
|
||||
|
||||
class _ZipImportResourceReader(resources_abc.ResourceReader):
|
||||
"""Private class used to support ZipImport.get_resource_reader().
|
||||
|
||||
This class is allowed to reference all the innards and private parts of
|
||||
the zipimporter.
|
||||
"""
|
||||
|
||||
def __init__(self, zipimporter, fullname):
|
||||
self.zipimporter = zipimporter
|
||||
self.fullname = fullname
|
||||
|
||||
def open_resource(self, resource):
|
||||
fullname_as_path = self.fullname.replace('.', '/')
|
||||
path = f'{fullname_as_path}/{resource}'
|
||||
try:
|
||||
return BytesIO(self.zipimporter.get_data(path))
|
||||
except OSError:
|
||||
raise FileNotFoundError(path)
|
||||
|
||||
def resource_path(self, resource):
|
||||
# All resources are in the zip file, so there is no path to the file.
|
||||
# Raising FileNotFoundError tells the higher level API to extract the
|
||||
# binary data and create a temporary file.
|
||||
raise FileNotFoundError
|
||||
|
||||
def is_resource(self, name):
|
||||
# Maybe we could do better, but if we can get the data, it's a
|
||||
# resource. Otherwise it isn't.
|
||||
fullname_as_path = self.fullname.replace('.', '/')
|
||||
path = f'{fullname_as_path}/{name}'
|
||||
try:
|
||||
self.zipimporter.get_data(path)
|
||||
except OSError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def contents(self):
|
||||
# This is a bit convoluted, because fullname will be a module path,
|
||||
# but _files is a list of file names relative to the top of the
|
||||
# archive's namespace. We want to compare file paths to find all the
|
||||
# names of things inside the module represented by fullname. So we
|
||||
# turn the module path of fullname into a file path relative to the
|
||||
# top of the archive, and then we iterate through _files looking for
|
||||
# names inside that "directory".
|
||||
fullname_path = Path(self.zipimporter.get_filename(self.fullname))
|
||||
relative_path = fullname_path.relative_to(self.zipimporter.archive)
|
||||
# Don't forget that fullname names a package, so its path will include
|
||||
# __init__.py, which we want to ignore.
|
||||
assert relative_path.name == '__init__.py'
|
||||
package_path = relative_path.parent
|
||||
subdirs_seen = set()
|
||||
for filename in self.zipimporter._files:
|
||||
try:
|
||||
relative = Path(filename).relative_to(package_path)
|
||||
except ValueError:
|
||||
continue
|
||||
# If the path of the file (which is relative to the top of the zip
|
||||
# namespace), relative to the package given when the resource
|
||||
# reader was created, has a parent, then it's a name in a
|
||||
# subdirectory and thus we skip it.
|
||||
parent_name = relative.parent.name
|
||||
if len(parent_name) == 0:
|
||||
yield relative.name
|
||||
elif parent_name not in subdirs_seen:
|
||||
subdirs_seen.add(parent_name)
|
||||
yield parent_name
|
||||
|
||||
|
||||
# Called from zipimport.c
|
||||
def _zipimport_get_resource_reader(zipimporter, fullname):
|
||||
try:
|
||||
if not zipimporter.is_package(fullname):
|
||||
return None
|
||||
except ZipImportError:
|
||||
return None
|
||||
return _ZipImportResourceReader(zipimporter, fullname)
|
||||
300
Lib/importlib/util.py
Normal file
300
Lib/importlib/util.py
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
"""Utility code for constructing importers, etc."""
|
||||
from . import abc
|
||||
from ._bootstrap import module_from_spec
|
||||
from ._bootstrap import _resolve_name
|
||||
from ._bootstrap import spec_from_loader
|
||||
from ._bootstrap import _find_spec
|
||||
from ._bootstrap_external import MAGIC_NUMBER
|
||||
from ._bootstrap_external import _RAW_MAGIC_NUMBER
|
||||
from ._bootstrap_external import cache_from_source
|
||||
from ._bootstrap_external import decode_source
|
||||
from ._bootstrap_external import source_from_cache
|
||||
from ._bootstrap_external import spec_from_file_location
|
||||
|
||||
from contextlib import contextmanager
|
||||
import _imp
|
||||
import functools
|
||||
import sys
|
||||
import types
|
||||
import warnings
|
||||
|
||||
|
||||
def source_hash(source_bytes):
|
||||
"Return the hash of *source_bytes* as used in hash-based pyc files."
|
||||
return _imp.source_hash(_RAW_MAGIC_NUMBER, source_bytes)
|
||||
|
||||
|
||||
def resolve_name(name, package):
|
||||
"""Resolve a relative module name to an absolute one."""
|
||||
if not name.startswith('.'):
|
||||
return name
|
||||
elif not package:
|
||||
raise ValueError(f'no package specified for {repr(name)} '
|
||||
'(required for relative module names)')
|
||||
level = 0
|
||||
for character in name:
|
||||
if character != '.':
|
||||
break
|
||||
level += 1
|
||||
return _resolve_name(name[level:], package, level)
|
||||
|
||||
|
||||
def _find_spec_from_path(name, path=None):
|
||||
"""Return the spec for the specified module.
|
||||
|
||||
First, sys.modules is checked to see if the module was already imported. If
|
||||
so, then sys.modules[name].__spec__ is returned. If that happens to be
|
||||
set to None, then ValueError is raised. If the module is not in
|
||||
sys.modules, then sys.meta_path is searched for a suitable spec with the
|
||||
value of 'path' given to the finders. None is returned if no spec could
|
||||
be found.
|
||||
|
||||
Dotted names do not have their parent packages implicitly imported. You will
|
||||
most likely need to explicitly import all parent packages in the proper
|
||||
order for a submodule to get the correct spec.
|
||||
|
||||
"""
|
||||
if name not in sys.modules:
|
||||
return _find_spec(name, path)
|
||||
else:
|
||||
module = sys.modules[name]
|
||||
if module is None:
|
||||
return None
|
||||
try:
|
||||
spec = module.__spec__
|
||||
except AttributeError:
|
||||
raise ValueError('{}.__spec__ is not set'.format(name)) from None
|
||||
else:
|
||||
if spec is None:
|
||||
raise ValueError('{}.__spec__ is None'.format(name))
|
||||
return spec
|
||||
|
||||
|
||||
def find_spec(name, package=None):
|
||||
"""Return the spec for the specified module.
|
||||
|
||||
First, sys.modules is checked to see if the module was already imported. If
|
||||
so, then sys.modules[name].__spec__ is returned. If that happens to be
|
||||
set to None, then ValueError is raised. If the module is not in
|
||||
sys.modules, then sys.meta_path is searched for a suitable spec with the
|
||||
value of 'path' given to the finders. None is returned if no spec could
|
||||
be found.
|
||||
|
||||
If the name is for submodule (contains a dot), the parent module is
|
||||
automatically imported.
|
||||
|
||||
The name and package arguments work the same as importlib.import_module().
|
||||
In other words, relative module names (with leading dots) work.
|
||||
|
||||
"""
|
||||
fullname = resolve_name(name, package) if name.startswith('.') else name
|
||||
if fullname not in sys.modules:
|
||||
parent_name = fullname.rpartition('.')[0]
|
||||
if parent_name:
|
||||
parent = __import__(parent_name, fromlist=['__path__'])
|
||||
try:
|
||||
parent_path = parent.__path__
|
||||
except AttributeError as e:
|
||||
raise ModuleNotFoundError(
|
||||
f"__path__ attribute not found on {parent_name!r} "
|
||||
f"while trying to find {fullname!r}", name=fullname) from e
|
||||
else:
|
||||
parent_path = None
|
||||
return _find_spec(fullname, parent_path)
|
||||
else:
|
||||
module = sys.modules[fullname]
|
||||
if module is None:
|
||||
return None
|
||||
try:
|
||||
spec = module.__spec__
|
||||
except AttributeError:
|
||||
raise ValueError('{}.__spec__ is not set'.format(name)) from None
|
||||
else:
|
||||
if spec is None:
|
||||
raise ValueError('{}.__spec__ is None'.format(name))
|
||||
return spec
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _module_to_load(name):
|
||||
is_reload = name in sys.modules
|
||||
|
||||
module = sys.modules.get(name)
|
||||
if not is_reload:
|
||||
# This must be done before open() is called as the 'io' module
|
||||
# implicitly imports 'locale' and would otherwise trigger an
|
||||
# infinite loop.
|
||||
module = type(sys)(name)
|
||||
# This must be done before putting the module in sys.modules
|
||||
# (otherwise an optimization shortcut in import.c becomes wrong)
|
||||
module.__initializing__ = True
|
||||
sys.modules[name] = module
|
||||
try:
|
||||
yield module
|
||||
except Exception:
|
||||
if not is_reload:
|
||||
try:
|
||||
del sys.modules[name]
|
||||
except KeyError:
|
||||
pass
|
||||
finally:
|
||||
module.__initializing__ = False
|
||||
|
||||
|
||||
def set_package(fxn):
|
||||
"""Set __package__ on the returned module.
|
||||
|
||||
This function is deprecated.
|
||||
|
||||
"""
|
||||
@functools.wraps(fxn)
|
||||
def set_package_wrapper(*args, **kwargs):
|
||||
warnings.warn('The import system now takes care of this automatically.',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
module = fxn(*args, **kwargs)
|
||||
if getattr(module, '__package__', None) is None:
|
||||
module.__package__ = module.__name__
|
||||
if not hasattr(module, '__path__'):
|
||||
module.__package__ = module.__package__.rpartition('.')[0]
|
||||
return module
|
||||
return set_package_wrapper
|
||||
|
||||
|
||||
def set_loader(fxn):
|
||||
"""Set __loader__ on the returned module.
|
||||
|
||||
This function is deprecated.
|
||||
|
||||
"""
|
||||
@functools.wraps(fxn)
|
||||
def set_loader_wrapper(self, *args, **kwargs):
|
||||
warnings.warn('The import system now takes care of this automatically.',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
module = fxn(self, *args, **kwargs)
|
||||
if getattr(module, '__loader__', None) is None:
|
||||
module.__loader__ = self
|
||||
return module
|
||||
return set_loader_wrapper
|
||||
|
||||
|
||||
def module_for_loader(fxn):
|
||||
"""Decorator to handle selecting the proper module for loaders.
|
||||
|
||||
The decorated function is passed the module to use instead of the module
|
||||
name. The module passed in to the function is either from sys.modules if
|
||||
it already exists or is a new module. If the module is new, then __name__
|
||||
is set the first argument to the method, __loader__ is set to self, and
|
||||
__package__ is set accordingly (if self.is_package() is defined) will be set
|
||||
before it is passed to the decorated function (if self.is_package() does
|
||||
not work for the module it will be set post-load).
|
||||
|
||||
If an exception is raised and the decorator created the module it is
|
||||
subsequently removed from sys.modules.
|
||||
|
||||
The decorator assumes that the decorated function takes the module name as
|
||||
the second argument.
|
||||
|
||||
"""
|
||||
warnings.warn('The import system now takes care of this automatically.',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
@functools.wraps(fxn)
|
||||
def module_for_loader_wrapper(self, fullname, *args, **kwargs):
|
||||
with _module_to_load(fullname) as module:
|
||||
module.__loader__ = self
|
||||
try:
|
||||
is_package = self.is_package(fullname)
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
if is_package:
|
||||
module.__package__ = fullname
|
||||
else:
|
||||
module.__package__ = fullname.rpartition('.')[0]
|
||||
# If __package__ was not set above, __import__() will do it later.
|
||||
return fxn(self, module, *args, **kwargs)
|
||||
|
||||
return module_for_loader_wrapper
|
||||
|
||||
|
||||
class _LazyModule(types.ModuleType):
|
||||
|
||||
"""A subclass of the module type which triggers loading upon attribute access."""
|
||||
|
||||
def __getattribute__(self, attr):
|
||||
"""Trigger the load of the module and return the attribute."""
|
||||
# All module metadata must be garnered from __spec__ in order to avoid
|
||||
# using mutated values.
|
||||
# Stop triggering this method.
|
||||
self.__class__ = types.ModuleType
|
||||
# Get the original name to make sure no object substitution occurred
|
||||
# in sys.modules.
|
||||
original_name = self.__spec__.name
|
||||
# Figure out exactly what attributes were mutated between the creation
|
||||
# of the module and now.
|
||||
attrs_then = self.__spec__.loader_state['__dict__']
|
||||
original_type = self.__spec__.loader_state['__class__']
|
||||
attrs_now = self.__dict__
|
||||
attrs_updated = {}
|
||||
for key, value in attrs_now.items():
|
||||
# Code that set the attribute may have kept a reference to the
|
||||
# assigned object, making identity more important than equality.
|
||||
if key not in attrs_then:
|
||||
attrs_updated[key] = value
|
||||
elif id(attrs_now[key]) != id(attrs_then[key]):
|
||||
attrs_updated[key] = value
|
||||
self.__spec__.loader.exec_module(self)
|
||||
# If exec_module() was used directly there is no guarantee the module
|
||||
# object was put into sys.modules.
|
||||
if original_name in sys.modules:
|
||||
if id(self) != id(sys.modules[original_name]):
|
||||
raise ValueError(f"module object for {original_name!r} "
|
||||
"substituted in sys.modules during a lazy "
|
||||
"load")
|
||||
# Update after loading since that's what would happen in an eager
|
||||
# loading situation.
|
||||
self.__dict__.update(attrs_updated)
|
||||
return getattr(self, attr)
|
||||
|
||||
def __delattr__(self, attr):
|
||||
"""Trigger the load and then perform the deletion."""
|
||||
# To trigger the load and raise an exception if the attribute
|
||||
# doesn't exist.
|
||||
self.__getattribute__(attr)
|
||||
delattr(self, attr)
|
||||
|
||||
|
||||
class LazyLoader(abc.Loader):
|
||||
|
||||
"""A loader that creates a module which defers loading until attribute access."""
|
||||
|
||||
@staticmethod
|
||||
def __check_eager_loader(loader):
|
||||
if not hasattr(loader, 'exec_module'):
|
||||
raise TypeError('loader must define exec_module()')
|
||||
|
||||
@classmethod
|
||||
def factory(cls, loader):
|
||||
"""Construct a callable which returns the eager loader made lazy."""
|
||||
cls.__check_eager_loader(loader)
|
||||
return lambda *args, **kwargs: cls(loader(*args, **kwargs))
|
||||
|
||||
def __init__(self, loader):
|
||||
self.__check_eager_loader(loader)
|
||||
self.loader = loader
|
||||
|
||||
def create_module(self, spec):
|
||||
return self.loader.create_module(spec)
|
||||
|
||||
def exec_module(self, module):
|
||||
"""Make the module load lazily."""
|
||||
module.__spec__.loader = self.loader
|
||||
module.__loader__ = self.loader
|
||||
# Don't need to worry about deep-copying as trying to set an attribute
|
||||
# on an object would have triggered the load,
|
||||
# e.g. ``module.__spec__.loader = None`` would trigger a load from
|
||||
# trying to access module.__spec__.
|
||||
loader_state = {}
|
||||
loader_state['__dict__'] = module.__dict__.copy()
|
||||
loader_state['__class__'] = module.__class__
|
||||
module.__spec__.loader_state = loader_state
|
||||
module.__class__ = _LazyModule
|
||||
Loading…
Add table
Add a link
Reference in a new issue