diff options
Diffstat (limited to 'Lib/importlib/abc.py')
| -rw-r--r-- | Lib/importlib/abc.py | 246 |
1 files changed, 214 insertions, 32 deletions
diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py index 7b89d0b..fa343f8 100644 --- a/Lib/importlib/abc.py +++ b/Lib/importlib/abc.py @@ -1,8 +1,16 @@ """Abstract base classes related to import.""" from . import _bootstrap from . import machinery +from . import util import abc +import imp +import io +import marshal +import os.path +import sys +import tokenize import types +import warnings class Loader(metaclass=abc.ABCMeta): @@ -10,8 +18,9 @@ class Loader(metaclass=abc.ABCMeta): """Abstract base class for import loaders.""" @abc.abstractmethod - def load_module(self, fullname:str) -> types.ModuleType: - """Abstract method which when implemented should load a module.""" + def load_module(self, fullname): + """Abstract method which when implemented should load a module. + The fullname is a str.""" raise NotImplementedError @@ -20,8 +29,11 @@ class Finder(metaclass=abc.ABCMeta): """Abstract base class for import finders.""" @abc.abstractmethod - def find_module(self, fullname:str, path:[str]=None) -> Loader: - """Abstract method which when implemented should find a module.""" + def find_module(self, fullname, path=None): + """Abstract method which when implemented should find a module. + The fullname is a str and the optional path is a str or None. + Returns a Loader object. + """ raise NotImplementedError Finder.register(machinery.BuiltinImporter) @@ -39,9 +51,9 @@ class ResourceLoader(Loader): """ @abc.abstractmethod - def get_data(self, path:str) -> bytes: + def get_data(self, path): """Abstract method which when implemented should return the bytes for - the specified path.""" + the specified path. The path must be a str.""" raise NotImplementedError @@ -55,68 +67,238 @@ class InspectLoader(Loader): """ @abc.abstractmethod - def is_package(self, fullname:str) -> bool: + def is_package(self, fullname): """Abstract method which when implemented should return whether the - module is a package.""" - return NotImplementedError + module is a package. The fullname is a str. Returns a bool.""" + raise NotImplementedError @abc.abstractmethod - def get_code(self, fullname:str) -> types.CodeType: + def get_code(self, fullname): """Abstract method which when implemented should return the code object - for the module""" - return NotImplementedError + for the module. The fullname is a str. Returns a types.CodeType.""" + raise NotImplementedError @abc.abstractmethod - def get_source(self, fullname:str) -> str: + def get_source(self, fullname): """Abstract method which should return the source code for the - module.""" - return NotImplementedError + module. The fullname is a str. Returns a str.""" + raise NotImplementedError InspectLoader.register(machinery.BuiltinImporter) InspectLoader.register(machinery.FrozenImporter) -class PyLoader(_bootstrap.PyLoader, ResourceLoader, InspectLoader): +class ExecutionLoader(InspectLoader): - """Abstract base class to assist in loading source code by requiring only - back-end storage methods to be implemented. + """Abstract base class for loaders that wish to support the execution of + modules as scripts. - The methods get_code, get_source, and load_module are implemented for the - user. + 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.""" + raise NotImplementedError + + +class SourceLoader(_bootstrap.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).""" + raise NotImplementedError + + 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. + + """ + raise NotImplementedError + + +class PyLoader(SourceLoader): + + """Implement the deprecated PyLoader ABC in terms of SourceLoader. + + This class has been deprecated! It is slated for removal in Python 3.4. + If compatibility with Python 3.1 is not needed then implement the + SourceLoader ABC instead of this class. If Python 3.1 compatibility is + needed, then use the following idiom to have a single class that is + compatible with Python 3.1 onwards:: + + try: + from importlib.abc import SourceLoader + except ImportError: + from importlib.abc import PyLoader as SourceLoader + + + class CustomLoader(SourceLoader): + def get_filename(self, fullname): + # Implement ... + + def source_path(self, fullname): + '''Implement source_path in terms of get_filename.''' + try: + return self.get_filename(fullname) + except ImportError: + return None + + def is_package(self, fullname): + filename = os.path.basename(self.get_filename(fullname)) + return os.path.splitext(filename)[0] == '__init__' """ @abc.abstractmethod - def source_path(self, fullname:str) -> object: - """Abstract method which when implemented should return the path to the - sourced code for the module.""" + def is_package(self, fullname): + raise NotImplementedError + + @abc.abstractmethod + def source_path(self, fullname): + """Abstract method. Accepts a str module name and returns the path to + the source code for the module.""" raise NotImplementedError + def get_filename(self, fullname): + """Implement get_filename in terms of source_path. -class PyPycLoader(_bootstrap.PyPycLoader, PyLoader): + As get_filename should only return a source file path there is no + chance of the path not existing but loading still being possible, so + ImportError should propagate instead of being turned into returning + None. + + """ + warnings.warn("importlib.abc.PyLoader is deprecated and is " + "slated for removal in Python 3.4; " + "use SourceLoader instead. " + "See the importlib documentation on how to be " + "compatible with Python 3.1 onwards.", + PendingDeprecationWarning) + path = self.source_path(fullname) + if path is None: + raise ImportError + else: + return path + + +class PyPycLoader(PyLoader): """Abstract base class to assist in loading source and bytecode by requiring only back-end storage methods to be implemented. + This class has been deprecated! Removal is slated for Python 3.4. Implement + the SourceLoader ABC instead. If Python 3.1 compatibility is needed, see + PyLoader. + The methods get_code, get_source, and load_module are implemented for the user. """ + def get_filename(self, fullname): + """Return the source or bytecode file path.""" + path = self.source_path(fullname) + if path is not None: + return path + path = self.bytecode_path(fullname) + if path is not None: + return path + raise ImportError("no source or bytecode path available for " + "{0!r}".format(fullname)) + + def get_code(self, fullname): + """Get a code object from source or bytecode.""" + warnings.warn("importlib.abc.PyPycLoader is deprecated and slated for " + "removal in Python 3.4; use SourceLoader instead. " + "If Python 3.1 compatibility is required, see the " + "latest documentation for PyLoader.", + PendingDeprecationWarning) + source_timestamp = self.source_mtime(fullname) + # Try to use bytecode if it is available. + bytecode_path = self.bytecode_path(fullname) + if bytecode_path: + data = self.get_data(bytecode_path) + try: + magic = data[:4] + if len(magic) < 4: + raise ImportError("bad magic number in {}".format(fullname)) + raw_timestamp = data[4:8] + if len(raw_timestamp) < 4: + raise EOFError("bad timestamp in {}".format(fullname)) + pyc_timestamp = marshal._r_long(raw_timestamp) + bytecode = data[8:] + # Verify that the magic number is valid. + if imp.get_magic() != magic: + raise ImportError("bad magic number in {}".format(fullname)) + # Verify that the bytecode is not stale (only matters when + # there is source to fall back on. + if source_timestamp: + if pyc_timestamp < source_timestamp: + raise ImportError("bytecode is stale") + except (ImportError, EOFError): + # If source is available give it a shot. + if source_timestamp is not None: + pass + else: + raise + else: + # Bytecode seems fine, so try to use it. + return marshal.loads(bytecode) + elif source_timestamp is None: + raise ImportError("no source or bytecode available to create code " + "object for {0!r}".format(fullname)) + # Use the source. + source_path = self.source_path(fullname) + if source_path is None: + message = "a source path must exist to load {0}".format(fullname) + raise ImportError(message) + source = self.get_data(source_path) + code_object = compile(source, source_path, 'exec', dont_inherit=True) + # Generate bytecode and write it out. + if not sys.dont_write_bytecode: + data = bytearray(imp.get_magic()) + data.extend(marshal._w_long(source_timestamp)) + data.extend(marshal.dumps(code_object)) + self.write_bytecode(fullname, data) + return code_object + @abc.abstractmethod - def source_mtime(self, fullname:str) -> int: - """Abstract method which when implemented should return the + def source_mtime(self, fullname): + """Abstract method. Accepts a str filename and returns an int modification time for the source of the module.""" raise NotImplementedError @abc.abstractmethod - def bytecode_path(self, fullname:str) -> object: - """Abstract method which when implemented should return the path to the - bytecode for the module.""" + def bytecode_path(self, fullname): + """Abstract method. Accepts a str filename and returns the str pathname + to the bytecode for the module.""" raise NotImplementedError @abc.abstractmethod - def write_bytecode(self, fullname:str, bytecode:bytes): - """Abstract method which when implemented should attempt to write the - bytecode for the module.""" + def write_bytecode(self, fullname, bytecode): + """Abstract method. Accepts a str filename and bytes object + representing the bytecode for the module. Returns a boolean + representing whether the bytecode was written or not.""" raise NotImplementedError |
