diff options
Diffstat (limited to 'Lib/importlib/_bootstrap.py')
-rw-r--r-- | Lib/importlib/_bootstrap.py | 260 |
1 files changed, 149 insertions, 111 deletions
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index 97e3f54..45c1b05 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -315,17 +315,124 @@ def module_for_loader(fxn): return decorated -class _PyFileLoader: - # XXX Still smart to have this as a separate class? Or would it work - # better to integrate with PyFileFinder? Could cache _is_pkg info. - # FileFinder can be changed to return self instead of a specific loader - # call. Otherwise _base_path can be calculated on the fly without issue if - # it is known whether a module should be treated as a path or package to - # minimize stat calls. Could even go as far as to stat the directory the - # importer is in to detect changes and then cache all the info about what - # files were found (if stating directories is platform-dependent). - - """Load a Python source or bytecode file.""" +class PyLoader: + + """Loader base class for Python source. + + Requires implementing the optional PEP 302 protocols as well as + source_mtime and source_path. + + """ + + @module_for_loader + def load_module(self, module): + """Load a source module.""" + return _load_module(module) + + def _load_module(self, module): + """Initialize a module from source.""" + name = module.__name__ + source_path = self.source_path(name) + code_object = self.get_code(module.__name__) + if not hasattr(module, '__file__'): + module.__file__ = source_path + if self.is_package(name): + module.__path__ = [module.__file__.rsplit(path_sep, 1)[0]] + module.__package__ = module.__name__ + if not hasattr(module, '__path__'): + module.__package__ = module.__package__.rpartition('.')[0] + exec(code_object, module.__dict__) + return module + + def get_code(self, fullname): + """Get a code object from source.""" + source_path = self.source_path(fullname) + source = self.get_data(source_path) + # Convert to universal newlines. + line_endings = b'\n' + for index, c in enumerate(source): + if c == ord(b'\n'): + break + elif c == ord(b'\r'): + line_endings = b'\r' + try: + if source[index+1] == ord(b'\n'): + line_endings += b'\n' + except IndexError: + pass + break + if line_endings != b'\n': + source = source.replace(line_endings, b'\n') + return compile(source, source_path, 'exec', dont_inherit=True) + + +class PyPycLoader(PyLoader): + + """Loader base class for Python source and bytecode. + + Requires implementing the methods needed for PyLoader as well as + bytecode_path and write_bytecode. + + """ + + @module_for_loader + def load_module(self, module): + """Load a module from source or bytecode.""" + name = module.__name__ + source_path = self.source_path(name) + bytecode_path = self.bytecode_path(name) + module.__file__ = source_path if source_path else bytecode_path + return self._load_module(module) + + def get_code(self, fullname): + """Get a code object from source or bytecode.""" + # XXX Care enough to make sure this call does not happen if the magic + # number is bad? + 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) + magic = data[:4] + pyc_timestamp = marshal._r_long(data[4:8]) + bytecode = data[8:] + try: + # Verify that the magic number is valid. + if imp.get_magic() != magic: + raise ImportError("bad magic number") + # 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: + # 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. + # XXX If the bytecode is ill-formed, would it be beneficial to + # try for using source if available and issue a warning? + 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. + code_object = super().get_code(fullname) + # 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 + + +class PyFileLoader(PyLoader): + + """Load a Python source file.""" def __init__(self, name, path, is_pkg): self._name = name @@ -354,29 +461,6 @@ class _PyFileLoader: # Not a property so that it is easy to override. return self._find_path(imp.PY_SOURCE) - @check_name - def bytecode_path(self, fullname): - """Return the path to a bytecode file, or None if one does not - exist.""" - # Not a property for easy overriding. - return self._find_path(imp.PY_COMPILED) - - @module_for_loader - def load_module(self, module): - """Load a Python source or bytecode module.""" - name = module.__name__ - source_path = self.source_path(name) - bytecode_path = self.bytecode_path(name) - code_object = self.get_code(module.__name__) - module.__file__ = source_path if source_path else bytecode_path - module.__loader__ = self - if self.is_package(name): - module.__path__ = [module.__file__.rsplit(path_sep, 1)[0]] - module.__package__ = module.__name__ - if not hasattr(module, '__path__'): - module.__package__ = module.__package__.rpartition('.')[0] - exec(code_object, module.__dict__) - return module @check_name def source_mtime(self, name): @@ -405,6 +489,34 @@ class _PyFileLoader: # anything other than UTF-8. return open(source_path, encoding=encoding).read() + + def get_data(self, path): + """Return the data from path as raw bytes.""" + return _fileio._FileIO(path, 'r').read() + + @check_name + def is_package(self, fullname): + """Return a boolean based on whether the module is a package. + + Raises ImportError (like get_source) if the loader cannot handle the + package. + + """ + return self._is_pkg + + +# XXX Rename _PyFileLoader throughout +class PyPycFileLoader(PyPycLoader, PyFileLoader): + + """Load a module from a source or bytecode file.""" + + @check_name + def bytecode_path(self, fullname): + """Return the path to a bytecode file, or None if one does not + exist.""" + # Not a property for easy overriding. + return self._find_path(imp.PY_COMPILED) + @check_name def write_bytecode(self, name, data): """Write out 'data' for the specified module, returning a boolean @@ -428,82 +540,6 @@ class _PyFileLoader: else: raise - def get_code(self, name): - """Return the code object for the module.""" - # XXX Care enough to make sure this call does not happen if the magic - # number is bad? - source_timestamp = self.source_mtime(name) - # Try to use bytecode if it is available. - bytecode_path = self.bytecode_path(name) - if bytecode_path: - data = self.get_data(bytecode_path) - magic = data[:4] - pyc_timestamp = marshal._r_long(data[4:8]) - bytecode = data[8:] - try: - # Verify that the magic number is valid. - if imp.get_magic() != magic: - raise ImportError("bad magic number") - # 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("bytcode is stale") - except ImportError: - # 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. - # XXX If the bytecode is ill-formed, would it be beneficial to - # try for using source if available and issue a warning? - return marshal.loads(bytecode) - elif source_timestamp is None: - raise ImportError("no source or bytecode available to create code " - "object for {0!r}".format(name)) - # Use the source. - source_path = self.source_path(name) - source = self.get_data(source_path) - # Convert to universal newlines. - line_endings = b'\n' - for index, c in enumerate(source): - if c == ord(b'\n'): - break - elif c == ord(b'\r'): - line_endings = b'\r' - try: - if source[index+1] == ord(b'\n'): - line_endings += b'\n' - except IndexError: - pass - break - if line_endings != b'\n': - source = source.replace(line_endings, b'\n') - 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(name, data) - return code_object - - def get_data(self, path): - """Return the data from path as raw bytes.""" - return _fileio._FileIO(path, 'r').read() - - @check_name - def is_package(self, fullname): - """Return a boolean based on whether the module is a package. - - Raises ImportError (like get_source) if the loader cannot handle the - package. - - """ - return self._is_pkg - class FileFinder: @@ -583,7 +619,7 @@ class PyFileFinder(FileFinder): """Importer for source/bytecode files.""" _possible_package = True - _loader = _PyFileLoader + _loader = PyFileLoader def __init__(self, path_entry): # Lack of imp during class creation means _suffixes is set here. @@ -597,6 +633,8 @@ class PyPycFileFinder(PyFileFinder): """Finder for source and bytecode files.""" + _loader = PyPycFileLoader + def __init__(self, path_entry): super().__init__(path_entry) self._suffixes += suffix_list(imp.PY_COMPILED) |