Implement the PEP 302 protocol for get_filename() as
importlib.abc.ExecutionLoader. PyLoader now inherits from this ABC instead of InspectLoader directly. Both PyLoader and PyPycLoader provide concrete implementations of get_filename in terms of source_path and bytecode_path.
This commit is contained in:
parent
64ef00fa60
commit
6919427e94
@ -202,10 +202,24 @@ are also provided to help in implementing the core ABCs.
|
|||||||
:term:`loader` cannot find the module.
|
:term:`loader` cannot find the module.
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: ExecutionLoader
|
||||||
|
|
||||||
|
An abstract base class which inherits from :class:`InspectLoader` that,
|
||||||
|
when implemented, allows a module to be executed as a script. The ABC
|
||||||
|
represents an optional :pep:`302` protocol.
|
||||||
|
|
||||||
|
.. method:: get_filename(fullname)
|
||||||
|
|
||||||
|
An abstract method that is to return the value for :attr:`__file__` for
|
||||||
|
the specified module. If no path is available, :exc:`ImportError` is
|
||||||
|
raised.
|
||||||
|
|
||||||
|
|
||||||
.. class:: PyLoader
|
.. class:: PyLoader
|
||||||
|
|
||||||
An abstract base class inheriting from :class:`importlib.abc.InspectLoader`
|
An abstract base class inheriting from
|
||||||
and :class:`importlib.abc.ResourceLoader` designed to ease the loading of
|
:class:`importlib.abc.ExecutionLoader` and
|
||||||
|
:class:`importlib.abc.ResourceLoader` designed to ease the loading of
|
||||||
Python source modules (bytecode is not handled; see
|
Python source modules (bytecode is not handled; see
|
||||||
:class:`importlib.abc.PyPycLoader` for a source/bytecode ABC). A subclass
|
:class:`importlib.abc.PyPycLoader` for a source/bytecode ABC). A subclass
|
||||||
implementing this ABC will only need to worry about exposing how the source
|
implementing this ABC will only need to worry about exposing how the source
|
||||||
@ -218,6 +232,13 @@ are also provided to help in implementing the core ABCs.
|
|||||||
module. Should return :keyword:`None` if there is no source code.
|
module. Should return :keyword:`None` if there is no source code.
|
||||||
:exc:`ImportError` if the module cannot be found.
|
:exc:`ImportError` if the module cannot be found.
|
||||||
|
|
||||||
|
.. method:: get_filename(fullname)
|
||||||
|
|
||||||
|
A concrete implementation of
|
||||||
|
:meth:`importlib.abc.ExecutionLoader.get_filename` that
|
||||||
|
relies on :meth:`source_path`. If :meth:`source_path` returns
|
||||||
|
:keyword:`None`, then :exc:`ImportError` is raised.
|
||||||
|
|
||||||
.. method:: load_module(fullname)
|
.. method:: load_module(fullname)
|
||||||
|
|
||||||
A concrete implementation of :meth:`importlib.abc.Loader.load_module`
|
A concrete implementation of :meth:`importlib.abc.Loader.load_module`
|
||||||
@ -238,8 +259,8 @@ are also provided to help in implementing the core ABCs.
|
|||||||
|
|
||||||
A concrete implementation of
|
A concrete implementation of
|
||||||
:meth:`importlib.abc.InspectLoader.get_source`. Uses
|
:meth:`importlib.abc.InspectLoader.get_source`. Uses
|
||||||
:meth:`importlib.abc.InspectLoader.get_data` and :meth:`source_path` to
|
:meth:`importlib.abc.ResourceLoader.get_data` and :meth:`source_path`
|
||||||
get the source code. It tries to guess the source encoding using
|
to get the source code. It tries to guess the source encoding using
|
||||||
:func:`tokenize.detect_encoding`.
|
:func:`tokenize.detect_encoding`.
|
||||||
|
|
||||||
|
|
||||||
@ -253,7 +274,7 @@ are also provided to help in implementing the core ABCs.
|
|||||||
|
|
||||||
An abstract method which returns the modification time for the source
|
An abstract method which returns the modification time for the source
|
||||||
code of the specified module. The modification time should be an
|
code of the specified module. The modification time should be an
|
||||||
integer. If there is no source code, return :keyword:`None. If the
|
integer. If there is no source code, return :keyword:`None`. If the
|
||||||
module cannot be found then :exc:`ImportError` is raised.
|
module cannot be found then :exc:`ImportError` is raised.
|
||||||
|
|
||||||
.. method:: bytecode_path(fullname)
|
.. method:: bytecode_path(fullname)
|
||||||
@ -263,6 +284,16 @@ are also provided to help in implementing the core ABCs.
|
|||||||
if no bytecode exists (yet).
|
if no bytecode exists (yet).
|
||||||
Raises :exc:`ImportError` if the module is not found.
|
Raises :exc:`ImportError` if the module is not found.
|
||||||
|
|
||||||
|
.. method:: get_filename(fullname)
|
||||||
|
|
||||||
|
A concrete implementation of
|
||||||
|
:meth:`importlib.abc.ExecutionLoader.get_filename` that relies on
|
||||||
|
:meth:`importlib.abc.PyLoader.source_path` and :meth:`bytecode_path`.
|
||||||
|
If :meth:`source_path` returns a path, then that value is returned.
|
||||||
|
Else if :meth:`bytecode_path` returns a path, that path will be
|
||||||
|
returned. If a path is not available from both methods,
|
||||||
|
:exc:`ImportError` is raised.
|
||||||
|
|
||||||
.. method:: write_bytecode(fullname, bytecode)
|
.. method:: write_bytecode(fullname, bytecode)
|
||||||
|
|
||||||
An abstract method which has the loader write *bytecode* for future
|
An abstract method which has the loader write *bytecode* for future
|
||||||
|
@ -315,16 +315,10 @@ class PyLoader:
|
|||||||
|
|
||||||
@module_for_loader
|
@module_for_loader
|
||||||
def load_module(self, module):
|
def load_module(self, module):
|
||||||
"""Load a source module."""
|
"""Initialize the module."""
|
||||||
return self._load_module(module)
|
|
||||||
|
|
||||||
def _load_module(self, module):
|
|
||||||
"""Initialize a module from source."""
|
|
||||||
name = module.__name__
|
name = module.__name__
|
||||||
code_object = self.get_code(module.__name__)
|
code_object = self.get_code(module.__name__)
|
||||||
# __file__ may have been set by the caller, e.g. bytecode path.
|
module.__file__ = self.get_filename(name)
|
||||||
if not hasattr(module, '__file__'):
|
|
||||||
module.__file__ = self.source_path(name)
|
|
||||||
if self.is_package(name):
|
if self.is_package(name):
|
||||||
module.__path__ = [module.__file__.rsplit(path_sep, 1)[0]]
|
module.__path__ = [module.__file__.rsplit(path_sep, 1)[0]]
|
||||||
module.__package__ = module.__name__
|
module.__package__ = module.__name__
|
||||||
@ -334,6 +328,15 @@ class PyLoader:
|
|||||||
exec(code_object, module.__dict__)
|
exec(code_object, module.__dict__)
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
def get_filename(self, fullname):
|
||||||
|
"""Return the path to the source file, else raise ImportError."""
|
||||||
|
path = self.source_path(fullname)
|
||||||
|
if path is not None:
|
||||||
|
return path
|
||||||
|
else:
|
||||||
|
raise ImportError("no source path available for "
|
||||||
|
"{0!r}".format(fullname))
|
||||||
|
|
||||||
def get_code(self, fullname):
|
def get_code(self, fullname):
|
||||||
"""Get a code object from source."""
|
"""Get a code object from source."""
|
||||||
source_path = self.source_path(fullname)
|
source_path = self.source_path(fullname)
|
||||||
@ -388,15 +391,16 @@ class PyPycLoader(PyLoader):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@module_for_loader
|
def get_filename(self, fullname):
|
||||||
def load_module(self, module):
|
"""Return the source or bytecode file path."""
|
||||||
"""Load a module from source or bytecode."""
|
path = self.source_path(fullname)
|
||||||
name = module.__name__
|
if path is not None:
|
||||||
source_path = self.source_path(name)
|
return path
|
||||||
bytecode_path = self.bytecode_path(name)
|
path = self.bytecode_path(fullname)
|
||||||
# get_code can worry about no viable paths existing.
|
if path is not None:
|
||||||
module.__file__ = source_path or bytecode_path
|
return path
|
||||||
return self._load_module(module)
|
raise ImportError("no source or bytecode path available for "
|
||||||
|
"{0!r}".format(fullname))
|
||||||
|
|
||||||
def get_code(self, fullname):
|
def get_code(self, fullname):
|
||||||
"""Get a code object from source or bytecode."""
|
"""Get a code object from source or bytecode."""
|
||||||
|
@ -76,7 +76,23 @@ InspectLoader.register(machinery.BuiltinImporter)
|
|||||||
InspectLoader.register(machinery.FrozenImporter)
|
InspectLoader.register(machinery.FrozenImporter)
|
||||||
|
|
||||||
|
|
||||||
class PyLoader(_bootstrap.PyLoader, ResourceLoader, InspectLoader):
|
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:str) -> str:
|
||||||
|
"""Abstract method which should return the value that __file__ is to be
|
||||||
|
set to."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class PyLoader(_bootstrap.PyLoader, ResourceLoader, ExecutionLoader):
|
||||||
|
|
||||||
"""Abstract base class to assist in loading source code by requiring only
|
"""Abstract base class to assist in loading source code by requiring only
|
||||||
back-end storage methods to be implemented.
|
back-end storage methods to be implemented.
|
||||||
|
@ -218,6 +218,21 @@ class PyLoaderInterfaceTests(unittest.TestCase):
|
|||||||
with util.uncache(name), self.assertRaises(ImportError):
|
with util.uncache(name), self.assertRaises(ImportError):
|
||||||
mock.load_module(name)
|
mock.load_module(name)
|
||||||
|
|
||||||
|
def test_get_filename_with_source_path(self):
|
||||||
|
# get_filename() should return what source_path() returns.
|
||||||
|
name = 'mod'
|
||||||
|
path = os.path.join('path', 'to', 'source')
|
||||||
|
mock = PyLoaderMock({name: path})
|
||||||
|
with util.uncache(name):
|
||||||
|
self.assertEqual(mock.get_filename(name), path)
|
||||||
|
|
||||||
|
def test_get_filename_no_source_path(self):
|
||||||
|
# get_filename() should raise ImportError if source_path returns None.
|
||||||
|
name = 'mod'
|
||||||
|
mock = PyLoaderMock({name: None})
|
||||||
|
with util.uncache(name), self.assertRaises(ImportError):
|
||||||
|
mock.get_filename(name)
|
||||||
|
|
||||||
|
|
||||||
class PyLoaderGetSourceTests(unittest.TestCase):
|
class PyLoaderGetSourceTests(unittest.TestCase):
|
||||||
|
|
||||||
@ -283,6 +298,38 @@ class PyPycLoaderTests(PyLoaderTests):
|
|||||||
super().test_unloadable()
|
super().test_unloadable()
|
||||||
|
|
||||||
|
|
||||||
|
class PyPycLoaderInterfaceTests(unittest.TestCase):
|
||||||
|
|
||||||
|
"""Test for the interface of importlib.abc.PyPycLoader."""
|
||||||
|
|
||||||
|
def get_filename_check(self, src_path, bc_path, expect):
|
||||||
|
name = 'mod'
|
||||||
|
mock = PyPycLoaderMock({name: src_path}, {name: {'path': bc_path}})
|
||||||
|
with util.uncache(name):
|
||||||
|
assert mock.source_path(name) == src_path
|
||||||
|
assert mock.bytecode_path(name) == bc_path
|
||||||
|
self.assertEqual(mock.get_filename(name), expect)
|
||||||
|
|
||||||
|
def test_filename_with_source_bc(self):
|
||||||
|
# When source and bytecode paths present, return the source path.
|
||||||
|
self.get_filename_check('source_path', 'bc_path', 'source_path')
|
||||||
|
|
||||||
|
def test_filename_with_source_no_bc(self):
|
||||||
|
# With source but no bc, return source path.
|
||||||
|
self.get_filename_check('source_path', None, 'source_path')
|
||||||
|
|
||||||
|
def test_filename_with_no_source_bc(self):
|
||||||
|
# With not source but bc, return the bc path.
|
||||||
|
self.get_filename_check(None, 'bc_path', 'bc_path')
|
||||||
|
|
||||||
|
def test_filename_with_no_source_or_bc(self):
|
||||||
|
# With no source or bc, raise ImportError.
|
||||||
|
name = 'mod'
|
||||||
|
mock = PyPycLoaderMock({name: None}, {name: {'path': None}})
|
||||||
|
with util.uncache(name), self.assertRaises(ImportError):
|
||||||
|
mock.get_filename(name)
|
||||||
|
|
||||||
|
|
||||||
class SkipWritingBytecodeTests(unittest.TestCase):
|
class SkipWritingBytecodeTests(unittest.TestCase):
|
||||||
|
|
||||||
"""Test that bytecode is properly handled based on
|
"""Test that bytecode is properly handled based on
|
||||||
@ -421,9 +468,9 @@ class MissingPathsTests(unittest.TestCase):
|
|||||||
def test_main():
|
def test_main():
|
||||||
from test.support import run_unittest
|
from test.support import run_unittest
|
||||||
run_unittest(PyLoaderTests, PyLoaderInterfaceTests, PyLoaderGetSourceTests,
|
run_unittest(PyLoaderTests, PyLoaderInterfaceTests, PyLoaderGetSourceTests,
|
||||||
PyPycLoaderTests, SkipWritingBytecodeTests,
|
PyPycLoaderTests, PyPycLoaderInterfaceTests,
|
||||||
RegeneratedBytecodeTests, BadBytecodeFailureTests,
|
SkipWritingBytecodeTests, RegeneratedBytecodeTests,
|
||||||
MissingPathsTests)
|
BadBytecodeFailureTests, MissingPathsTests)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -53,9 +53,15 @@ class InspectLoader(InheritanceTests, unittest.TestCase):
|
|||||||
machinery.FrozenImporter]
|
machinery.FrozenImporter]
|
||||||
|
|
||||||
|
|
||||||
|
class ExecutionLoader(InheritanceTests, unittest.TestCase):
|
||||||
|
|
||||||
|
superclasses = [abc.InspectLoader]
|
||||||
|
subclasses = [abc.PyLoader]
|
||||||
|
|
||||||
|
|
||||||
class PyLoader(InheritanceTests, unittest.TestCase):
|
class PyLoader(InheritanceTests, unittest.TestCase):
|
||||||
|
|
||||||
superclasses = [abc.Loader, abc.ResourceLoader, abc.InspectLoader]
|
superclasses = [abc.Loader, abc.ResourceLoader, abc.ExecutionLoader]
|
||||||
|
|
||||||
|
|
||||||
class PyPycLoader(InheritanceTests, unittest.TestCase):
|
class PyPycLoader(InheritanceTests, unittest.TestCase):
|
||||||
|
@ -43,6 +43,11 @@ C-API
|
|||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Add importlib.abc.ExecutionLoader to represent the PEP 302 protocol for
|
||||||
|
loaders that allow for modules to be executed. Both importlib.abc.PyLoader
|
||||||
|
and PyPycLoader inherit from this class and provide implementations in
|
||||||
|
relation to other methods required by the ABCs.
|
||||||
|
|
||||||
- importlib.abc.PyLoader did not inherit from importlib.abc.ResourceLoader like
|
- importlib.abc.PyLoader did not inherit from importlib.abc.ResourceLoader like
|
||||||
the documentation said it did even though the code in PyLoader relied on the
|
the documentation said it did even though the code in PyLoader relied on the
|
||||||
abstract method required by ResourceLoader.
|
abstract method required by ResourceLoader.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user