Introduce importlib.abc. The module contains various ABCs related to imports

(mostly stuff specified by PEP 302). There are two ABCs, PyLoader and
PyPycLoader, which help with implementing source and source/bytecode loaders by
implementing load_module in terms of other methods. This removes a lot of
gritty details loaders typically have to worry about.
This commit is contained in:
Brett Cannon 2009-03-09 03:35:50 +00:00
parent aa1c8d8899
commit 2a922ed6ad
9 changed files with 739 additions and 171 deletions

View File

@ -82,6 +82,175 @@ Functions
occuring from to already be imported (i.e., *package* must already be
imported).
:mod:`importlib.abc` -- Abstract base classes related to import
---------------------------------------------------------------
.. module:: importlib.abc
:synopsis: Abstract base classes related to import
The :mod:`importlib.abc` module contains all of the core abstract base classes
used by :keyword:`import`. Some subclasses of the core abstract base classes
are also provided to help in implementing the core ABCs.
.. class:: Finder
An abstract base class representing a :term:`finder`.
..method:: find_module(fullname, path=None)
An abstract method for finding a :term:`loader` for the specified
module. If the :term:`finder` is found on :data:`sys.meta_path` and the
module to be searched for is a subpackage or module then *path* is set
to the value of :attr:`__path__` from the parent package. If a loader
cannot be found, :keyword:`None` is returned.
The exact definition of a :term:`finder` can be found in :pep:`302`.
.. class:: Loader
An abstract base class for a :term:`loader`.
..method:: load_module(fullname)
An abstract method for loading a module. If the module cannot be
loaded, :exc:`ImportError` is raised, otherwise the loaded module is
returned.
If the requested module is already exists in :data:`sys.modules`, that
module should be used and reloaded.
Otherwise a new module is to be created by the loader and inserted into
:data:`sys.modules`before any loading begins to prevent recursion from
the import. If the loader inserted into a module and the load fails it
must be removed by the loader from :data:`sys.modules`; modules already
in :data:`sys.modules` before the loader began execution should be left
alone. The :func:`importlib.util.module_for_loader` decorator handles
all of these details.
The loader is expected to set several attributes on the module when
adding a new module to :data:`sys.modules`.
- :attr:`__name__`
The name of the module.
- :attr:`__file__`
The path to where the module data is stored (not set for built-in
modules).
- :attr:`__path__`
Set to a list of strings specifying the search path within a
package. This attribute is not set on modules.
- :attr:`__package__`
The parent package for the module/package. If the module is
top-level then it has a value of the empty string. The
:func:`importlib.util.set_package` decorator can handle the details
for :attr:`__package__`.
- :attr:`__loader__`
Set to the loader used to load the module.
See :pep:`302` for the exact definition for a loader.
.. class:: ResourceLoader
An abstract base class for a :term:`loader` which implements the optional
:pep:`302` protocol for loading arbitrary resources from the storage
back-end.
..method:: get_data(path)
An abstract method to return the bytes for the data located at *path*.
Loaders that have a file-like storage back-end can implement this
abstract method to give direct access
to the data stored. :exc:`IOError` is to be raised if the *path* cannot
be found. The *path* is expected to be constructed using a module's
:attr:`__path__` attribute or an item from :attr:`__path__`.
.. class:: InspectLoader
An abstract base class for a :term:`loader` which implements the optional
:pep:`302` protocol for loaders which inspect modules.
..method:: is_package(fullname)
An abstract method to return a true value if the module is a package, a
false value otherwise. :exc:`ImportError` is raised if the
:term:`loader` cannot find the module.
..method:: get_source(fullname)
An abstract method to return the source of a module. It is returned as
a string with universal newline support. Returns :keyword:`None` if no
source is available (e.g. a built-in module). Raises :exc:`ImportError`
if the loader cannot find the module specified.
..method:: get_code(fullname)
An abstract method to return the :class:`code` object for a module.
:keyword:`None` is returned if the module does not have a code object
(e.g. built-in module). :exc:`ImportError` is raised if loader cannot
find the requested module.
.. class:: PyLoader
An abstract base class inheriting from :class:`importlib.abc.InspectLoader`
and :class:`importlib.abc.ResourceLoader` designed to ease the loading of
Python source modules (bytecode is not handled; see
:class:`importlib.abc.PyPycLoader` for a source/bytecode ABC). A subclass
implementing this ABC will only need to worry about exposing how the source
code is stored; all other details for loading Python source code will be
handled by the concrete implementations of key methods.
..method:: source_path(fullname)
An abstract method that returns the path to the source code for a
module. Should return :keyword:`None` if there is no source code.
:exc:`ImportError` if the module cannot be found.
..method:: load_module(fullname)
A concrete implementation of :meth:`importlib.abc.Loader.load_module`
that loads Python source code.
..method:: get_code(fullname)
A concrete implementation of
:meth:`importlib.abc.InspectLoader.get_code` that creates code objects
from Python source code.
.. class:: PyPycLoader
An abstract base class inheriting from :class:`importlib.abc.PyLoader`.
This ABC is meant to help in creating loaders that support both Python
source and bytecode.
..method:: source_mtime(fullname)
An abstract method which returns the modification time for the source
code of the specified module. The modification time should be an
integer. If there is no source code, return :keyword:`None. If the
module cannot be found then :exc:`ImportError` is raised.
..method:: bytecode_path(fullname)
An abstract method which returns the path to the bytecode for the
specified module. :keyword:`None` is returned if there is no bytecode.
:exc:`ImportError` is raised if the module is not found.
..method:: write_bytecode(fullname, bytecode)
An abstract method which has the loader write *bytecode* for future
use. If the bytecode is written, return :keyword:`True`. Return
:keyword:`False` if the bytecode could not be written. This method
should not be called if :data:`sys.dont_write_bytecode` is true.
:mod:`importlib.machinery` -- Importers and path hooks
------------------------------------------------------
@ -93,44 +262,27 @@ find and load modules.
.. class:: BuiltinImporter
:term:`Importer` for built-in modules. All known built-in modules are
listed in :data:`sys.builtin_module_names`.
An :term:`importer` for built-in modules. All known built-in modules are
listed in :data:`sys.builtin_module_names`. This class implements the
:class:`importlib.abc.Finder` and :class:`importlib.abc.Loader` ABCs.
Only class methods are defined by this class to alleviate the need for
instantiation.
.. classmethod:: find_module(fullname, path=None)
Class method that allows this class to be a :term:`finder` for built-in
modules.
.. classmethod:: load_module(fullname)
Class method that allows this class to be a :term:`loader` for built-in
modules.
.. class:: FrozenImporter
:term:`Importer` for frozen modules.
An :term:`importer` for frozen modules. This class implements the
:class:`importlib.abc.Finder` and :class:`importlib.abc.Loader` ABCs.
Only class methods are defined by this class to alleviate the need for
instantiation.
.. classmethod:: find_module(fullname, path=None)
Class method that allows this class to be a :term:`finder` for frozen
modules.
.. classmethod:: load_module(fullname)
Class method that allows this class to be a :term:`loader` for frozen
modules.
.. class:: PathFinder
:term:`Finder` for :data:`sys.path`.
:term:`Finder` for :data:`sys.path`. This class implements the
:class:`importlib.abc.Finder` ABC.
This class does not perfectly mirror the semantics of :keyword:`import` in
terms of :data:`sys.path`. No implicit path hooks are assumed for
@ -142,15 +294,15 @@ find and load modules.
.. classmethod:: find_module(fullname, path=None)
Class method that attempts to find a :term:`loader` for the module
specified by *fullname* either on :data:`sys.path` or, if defined, on
specified by *fullname* on :data:`sys.path` or, if defined, on
*path*. For each path entry that is searched,
:data:`sys.path_importer_cache` is checked. If an non-false object is
found then it is used as the :term:`finder` to query for the module
being searched for. For no entry is found in
found then it is used as the :term:`finder` to look for the module
being searched for. If no entry is found in
:data:`sys.path_importer_cache`, then :data:`sys.path_hooks` is
searched for a finder for the path entry and, if found, is stored in
:data:`sys.path_importer_cache` along with being queried about the
module.
module. If no finder is ever found then :keyword:`None` is returned.
:mod:`importlib.util` -- Utility code for importers
@ -166,10 +318,11 @@ an :term:`importer`.
A :term:`decorator` for a :term:`loader` which handles selecting the proper
module object to load with. The decorated method is expected to have a call
signature of ``method(self, module_object)`` for which the second argument
will be the module object to be used by the loader (note that the decorator
signature taking two positional arguments
(e.g. ``load_module(self, module)``) for which the second argument
will be the module object to be used by the loader. Note that the decorator
will not work on static methods because of the assumption of two
arguments).
arguments.
The decorated method will take in the name of the module to be loaded as
expected for a :term:`loader`. If the module is not found in

View File

@ -3,42 +3,12 @@ to do
* Public API left to expose (w/ docs!)
+ abc
+ abc.PyLoader.get_source
+ util.set_loader
- Finder
* Implement InspectLoader for BuiltinImporter and FrozenImporter.
* find_module
- Loader
* load_module
- ResourceLoader(Loader)
* get_data
- InspectLoader(Loader)
* is_package
* get_code
* get_source
- PyLoader(ResourceLoader)
* source_path
- PyPycLoader(PyLoader)
* source_mtime
* bytecode_path
* write_bytecode
+ test (Really want to worry about compatibility with future versions?)
- abc
* FinderTests [doc]
* LoaderTests [doc]
+ Expose function to see if a frozen module is a package.
* Remove ``import *`` from importlib.__init__.
@ -68,3 +38,4 @@ in the language specification).
+ imp
+ py_compile
+ compileall
+ zipimport

View File

@ -383,14 +383,8 @@ class PyPycLoader(PyLoader):
def load_module(self, module):
"""Load a module from source or bytecode."""
name = module.__name__
try:
source_path = self.source_path(name)
except ImportError:
source_path = None
try:
bytecode_path = self.bytecode_path(name)
except ImportError:
bytecode_path = None
source_path = self.source_path(name)
bytecode_path = self.bytecode_path(name)
# get_code can worry about no viable paths existing.
module.__file__ = source_path or bytecode_path
return self._load_module(module)

110
Lib/importlib/abc.py Normal file
View File

@ -0,0 +1,110 @@
"""Abstract base classes related to import."""
from . import _bootstrap
from . import machinery
import abc
import types
class Loader(metaclass=abc.ABCMeta):
"""Abstract base class for import loaders.
See PEP 302 for details.
"""
def load_module(self, fullname:str) -> types.ModuleType:
raise NotImplementedError
Loader.register(machinery.BuiltinImporter)
Loader.register(machinery.FrozenImporter)
class Finder(metaclass=abc.ABCMeta):
"""Abstract base class for import finders.
See PEP 302 for details.
"""
@abc.abstractmethod
def find_module(self, fullname:str, path:[str]=None) -> Loader:
raise NotImplementedError
Finder.register(machinery.BuiltinImporter)
Finder.register(machinery.FrozenImporter)
Finder.register(machinery.PathFinder)
class Importer(Finder, Loader):
"""Abstract base class for importers."""
class ResourceLoader(Loader):
"""Abstract base class for loaders which can return data from the back-end
storage.
This ABC represents one of the optional protocols specified by PEP 302.
"""
@abc.abstractmethod
def get_data(self, path:str) -> bytes:
raise NotImplementedError
class InspectLoader(Loader):
"""Abstract base class for loaders which supports introspection.
This ABC represents one of the optional protocols specified by PEP 302.
"""
@abc.abstractmethod
def is_package(self, fullname:str) -> bool:
return NotImplementedError
@abc.abstractmethod
def get_code(self, fullname:str) -> types.CodeType:
return NotImplementedError
@abc.abstractmethod
def get_source(self, fullname:str) -> str:
return NotImplementedError
class PyLoader(_bootstrap.PyLoader, InspectLoader):
"""Abstract base class that implements the core parts needed to load Python
source code."""
# load_module and get_code are implemented.
@abc.abstractmethod
def source_path(self, fullname:str) -> object:
raise NotImplementedError
class PyPycLoader(_bootstrap.PyPycLoader, PyLoader):
"""Abstract base class that implements the core parts needed to load Python
source and bytecode."""
# Implements load_module and get_code.
@abc.abstractmethod
def source_mtime(self, fullname:str) -> int:
raise NotImplementedError
@abc.abstractmethod
def bytecode_path(self, fullname:str) -> object:
raise NotImplementedError
@abc.abstractmethod
def write_bytecode(self, fullname:str, bytecode:bytes):
raise NotImplementedError

View File

@ -65,10 +65,11 @@ class LoaderTests(unittest.TestCase, metaclass=abc.ABCMeta):
Attributes to verify:
* __file__
* __loader__
* __name__
* __file__
* __package__
* __path__
* __loader__
"""
pass

View File

@ -0,0 +1,390 @@
import importlib
from importlib import abc
from .. import abc as testing_abc
from .. import util
from . import util as source_util
import imp
import marshal
import os
import sys
import types
import unittest
class PyLoaderMock(abc.PyLoader):
# Globals that should be defined for all modules.
source = ("_ = '::'.join([__name__, __file__, __package__, "
"repr(__loader__)])")
def __init__(self, data):
"""Take a dict of 'module_name: path' pairings.
Paths should have no file extension, allowing packages to be denoted by
ending in '__init__'.
"""
self.module_paths = data
self.path_to_module = {val:key for key,val in data.items()}
def get_data(self, path):
if path not in self.path_to_module:
raise IOError
return self.source.encode('utf-8')
def is_package(self, name):
try:
return '__init__' in self.module_paths[name]
except KeyError:
raise ImportError
def get_source(self, name): # Should not be needed.
raise NotImplementedError
def source_path(self, name):
try:
return self.module_paths[name]
except KeyError:
raise ImportError
class PyPycLoaderMock(abc.PyPycLoader, PyLoaderMock):
default_mtime = 1
def __init__(self, source, bc={}):
"""Initialize mock.
'bc' is a dict keyed on a module's name. The value is dict with
possible keys of 'path', 'mtime', 'magic', and 'bc'. Except for 'path',
each of those keys control if any part of created bytecode is to
deviate from default values.
"""
super().__init__(source)
self.module_bytecode = {}
self.path_to_bytecode = {}
self.bytecode_to_path = {}
for name, data in bc.items():
self.path_to_bytecode[data['path']] = name
self.bytecode_to_path[name] = data['path']
magic = data.get('magic', imp.get_magic())
mtime = importlib._w_long(data.get('mtime', self.default_mtime))
if 'bc' in data:
bc = data['bc']
else:
bc = self.compile_bc(name)
self.module_bytecode[name] = magic + mtime + bc
def compile_bc(self, name):
source_path = self.module_paths.get(name, '<test>') or '<test>'
code = compile(self.source, source_path, 'exec')
return marshal.dumps(code)
def source_mtime(self, name):
if name in self.module_paths:
return self.default_mtime
elif name in self.module_bytecode:
return None
else:
raise ImportError
def bytecode_path(self, name):
try:
return self.bytecode_to_path[name]
except KeyError:
if name in self.module_paths:
return None
else:
raise ImportError
def write_bytecode(self, name, bytecode):
self.module_bytecode[name] = bytecode
return True
def get_data(self, path):
if path in self.path_to_module:
return super().get_data(path)
elif path in self.path_to_bytecode:
name = self.path_to_bytecode[path]
return self.module_bytecode[name]
else:
raise IOError
def is_package(self, name):
try:
return super().is_package(name)
except TypeError:
return '__init__' in self.bytecode_to_path[name]
class PyLoaderTests(testing_abc.LoaderTests):
"""Tests for importlib.abc.PyLoader."""
mocker = PyLoaderMock
def eq_attrs(self, ob, **kwargs):
for attr, val in kwargs.items():
self.assertEqual(getattr(ob, attr), val)
def test_module(self):
name = '<module>'
path = 'path/to/module'
mock = self.mocker({name: path})
with util.uncache(name):
module = mock.load_module(name)
self.assert_(name in sys.modules)
self.eq_attrs(module, __name__=name, __file__=path, __package__='',
__loader__=mock)
self.assert_(not hasattr(module, '__path__'))
return mock, name
def test_package(self):
name = '<pkg>'
path = '/path/to/<pkg>/__init__'
mock = self.mocker({name: path})
with util.uncache(name):
module = mock.load_module(name)
self.assert_(name in sys.modules)
self.eq_attrs(module, __name__=name, __file__=path,
__path__=[os.path.dirname(path)], __package__=name,
__loader__=mock)
return mock, name
def test_lacking_parent(self):
name = 'pkg.mod'
path = 'path/to/pkg/mod'
mock = self.mocker({name: path})
with util.uncache(name):
module = mock.load_module(name)
self.assert_(name in sys.modules)
self.eq_attrs(module, __name__=name, __file__=path, __package__='pkg',
__loader__=mock)
self.assert_(not hasattr(module, '__path__'))
return mock, name
def test_module_reuse(self):
name = 'mod'
path = 'path/to/mod'
module = imp.new_module(name)
mock = self.mocker({name: path})
with util.uncache(name):
sys.modules[name] = module
loaded_module = mock.load_module(name)
self.assert_(loaded_module is module)
self.assert_(sys.modules[name] is module)
return mock, name
def test_state_after_failure(self):
name = "mod"
module = imp.new_module(name)
module.blah = None
mock = self.mocker({name: 'path/to/mod'})
mock.source = "1/0"
with util.uncache(name):
sys.modules[name] = module
self.assertRaises(ZeroDivisionError, mock.load_module, name)
self.assert_(sys.modules[name] is module)
self.assert_(hasattr(module, 'blah'))
return mock
def test_unloadable(self):
name = "mod"
mock = self.mocker({name: 'path/to/mod'})
mock.source = "1/0"
with util.uncache(name):
self.assertRaises(ZeroDivisionError, mock.load_module, name)
self.assert_(name not in sys.modules)
return mock
class PyLoaderInterfaceTests(unittest.TestCase):
def test_no_source_path(self):
# No source path should lead to ImportError.
name = 'mod'
mock = PyLoaderMock({})
with util.uncache(name):
self.assertRaises(ImportError, mock.load_module, name)
def test_source_path_is_None(self):
name = 'mod'
mock = PyLoaderMock({name: None})
with util.uncache(name):
self.assertRaises(ImportError, mock.load_module, name)
class PyPycLoaderTests(PyLoaderTests):
"""Tests for importlib.abc.PyPycLoader."""
mocker = PyPycLoaderMock
@source_util.writes_bytecode
def verify_bytecode(self, mock, name):
assert name in mock.module_paths
self.assert_(name in mock.module_bytecode)
magic = mock.module_bytecode[name][:4]
self.assertEqual(magic, imp.get_magic())
mtime = importlib._r_long(mock.module_bytecode[name][4:8])
self.assertEqual(mtime, 1)
bc = mock.module_bytecode[name][8:]
def test_module(self):
mock, name = super().test_module()
self.verify_bytecode(mock, name)
def test_package(self):
mock, name = super().test_package()
self.verify_bytecode(mock, name)
def test_lacking_parent(self):
mock, name = super().test_lacking_parent()
self.verify_bytecode(mock, name)
def test_module_reuse(self):
mock, name = super().test_module_reuse()
self.verify_bytecode(mock, name)
def test_state_after_failure(self):
super().test_state_after_failure()
def test_unloadable(self):
super().test_unloadable()
class SkipWritingBytecodeTests(unittest.TestCase):
"""Test that bytecode is properly handled based on
sys.dont_write_bytecode."""
@source_util.writes_bytecode
def run_test(self, dont_write_bytecode):
name = 'mod'
mock = PyPycLoaderMock({name: 'path/to/mod'})
sys.dont_write_bytecode = dont_write_bytecode
with util.uncache(name):
mock.load_module(name)
self.assert_((name in mock.module_bytecode) is not
dont_write_bytecode)
def test_no_bytecode_written(self):
self.run_test(True)
def test_bytecode_written(self):
self.run_test(False)
class RegeneratedBytecodeTests(unittest.TestCase):
"""Test that bytecode is regenerated as expected."""
@source_util.writes_bytecode
def test_different_magic(self):
# A different magic number should lead to new bytecode.
name = 'mod'
bad_magic = b'\x00\x00\x00\x00'
assert bad_magic != imp.get_magic()
mock = PyPycLoaderMock({name: 'path/to/mod'},
{name: {'path': 'path/to/mod.bytecode',
'magic': bad_magic}})
with util.uncache(name):
mock.load_module(name)
self.assert_(name in mock.module_bytecode)
magic = mock.module_bytecode[name][:4]
self.assertEqual(magic, imp.get_magic())
@source_util.writes_bytecode
def test_old_mtime(self):
# Bytecode with an older mtime should be regenerated.
name = 'mod'
old_mtime = PyPycLoaderMock.default_mtime - 1
mock = PyPycLoaderMock({name: 'path/to/mod'},
{name: {'path': 'path/to/mod.bytecode', 'mtime': old_mtime}})
with util.uncache(name):
mock.load_module(name)
self.assert_(name in mock.module_bytecode)
mtime = importlib._r_long(mock.module_bytecode[name][4:8])
self.assertEqual(mtime, PyPycLoaderMock.default_mtime)
class BadBytecodeFailureTests(unittest.TestCase):
"""Test import failures when there is no source and parts of the bytecode
is bad."""
def test_bad_magic(self):
# A bad magic number should lead to an ImportError.
name = 'mod'
bad_magic = b'\x00\x00\x00\x00'
mock = PyPycLoaderMock({}, {name: {'path': 'path/to/mod',
'magic': bad_magic}})
with util.uncache(name):
self.assertRaises(ImportError, mock.load_module, name)
def test_bad_bytecode(self):
# Bad code object bytecode should elad to an ImportError.
name = 'mod'
mock = PyPycLoaderMock({}, {name: {'path': '/path/to/mod', 'bc': b''}})
with util.uncache(name):
self.assertRaises(ImportError, mock.load_module, name)
def raise_ImportError(*args, **kwargs):
raise ImportError
class MissingPathsTests(unittest.TestCase):
"""Test what happens when a source or bytecode path does not exist (either
from *_path returning None or raising ImportError)."""
def test_source_path_None(self):
# Bytecode should be used when source_path returns None, along with
# __file__ being set to the bytecode path.
name = 'mod'
bytecode_path = 'path/to/mod'
mock = PyPycLoaderMock({name: None}, {name: {'path': bytecode_path}})
with util.uncache(name):
module = mock.load_module(name)
self.assertEqual(module.__file__, bytecode_path)
# Testing for bytecode_path returning None handled by all tests where no
# bytecode initially exists.
def test_all_paths_None(self):
# If all *_path methods return None, raise ImportError.
name = 'mod'
mock = PyPycLoaderMock({name: None})
with util.uncache(name):
self.assertRaises(ImportError, mock.load_module, name)
def test_source_path_ImportError(self):
# An ImportError from source_path should trigger an ImportError.
name = 'mod'
mock = PyPycLoaderMock({}, {name: {'path': 'path/to/mod'}})
with util.uncache(name):
self.assertRaises(ImportError, mock.load_module, name)
def test_bytecode_path_ImportError(self):
# An ImportError from bytecode_path should trigger an ImportError.
name = 'mod'
mock = PyPycLoaderMock({name: 'path/to/mod'})
bad_meth = types.MethodType(raise_ImportError, mock)
mock.bytecode_path = bad_meth
with util.uncache(name):
self.assertRaises(ImportError, mock.load_module, name)
def test_main():
from test.support import run_unittest
run_unittest(PyLoaderTests, PyLoaderInterfaceTests,
PyPycLoaderTests, SkipWritingBytecodeTests,
RegeneratedBytecodeTests, BadBytecodeFailureTests,
MissingPathsTests)
if __name__ == '__main__':
test_main()

View File

@ -102,102 +102,6 @@ class SimpleTest(unittest.TestCase):
self.assert_('_temp' not in sys.modules)
class DontWriteBytecodeTest(unittest.TestCase):
"""If sys.dont_write_bytcode is true then no bytecode should be created."""
def tearDown(self):
sys.dont_write_bytecode = False
@source_util.writes_bytecode
def run_test(self, assertion):
with source_util.create_modules('_temp') as mapping:
loader = importlib.PyPycFileLoader('_temp', mapping['_temp'], False)
loader.load_module('_temp')
bytecode_path = source_util.bytecode_path(mapping['_temp'])
assertion(bytecode_path)
def test_bytecode_written(self):
fxn = lambda bc_path: self.assert_(os.path.exists(bc_path))
self.run_test(fxn)
def test_bytecode_not_written(self):
sys.dont_write_bytecode = True
fxn = lambda bc_path: self.assert_(not os.path.exists(bc_path))
self.run_test(fxn)
class BadDataTest(unittest.TestCase):
"""If the bytecode has a magic number that does not match the
interpreters', ImportError is raised [bad magic]. The timestamp can have
any value. And bad marshal data raises ValueError.
"""
# [bad magic]
def test_bad_magic(self):
with source_util.create_modules('_temp') as mapping:
py_compile.compile(mapping['_temp'])
os.unlink(mapping['_temp'])
bytecode_path = source_util.bytecode_path(mapping['_temp'])
with open(bytecode_path, 'r+b') as file:
file.seek(0)
file.write(b'\x00\x00\x00\x00')
loader = importlib.PyPycFileLoader('_temp', mapping['_temp'], False)
self.assertRaises(ImportError, loader.load_module, '_temp')
self.assert_('_temp' not in sys.modules)
class SourceBytecodeInteraction(unittest.TestCase):
"""When both source and bytecode are present, certain rules dictate which
version of the code takes precedent. All things being equal, the bytecode
is used with the value of __file__ set to the source [basic top-level],
[basic package], [basic sub-module], [basic sub-package].
"""
def import_(self, file, module, *, pkg=False):
loader = importlib.PyPycFileLoader(module, file, pkg)
return loader.load_module(module)
def run_test(self, test, *create, pkg=False):
create += (test,)
with source_util.create_modules(*create) as mapping:
for name in create:
py_compile.compile(mapping[name])
if pkg:
import_name = test.rsplit('.', 1)[0]
else:
import_name = test
loader = importlib.PyPycFileLoader(import_name, mapping[test], pkg)
# Because some platforms only have a granularity to the second for
# atime you can't check the physical files. Instead just make it an
# exception trigger if source was read.
loader.get_source = lambda self, x: 42
module = loader.load_module(import_name)
self.assertEqual(module.__file__, mapping[name])
self.assert_(import_name in sys.modules)
self.assertEqual(id(module), id(sys.modules[import_name]))
# [basic top-level]
def test_basic_top_level(self):
self.run_test('top_level')
# [basic package]
def test_basic_package(self):
self.run_test('pkg.__init__', pkg=True)
# [basic sub-module]
def test_basic_sub_module(self):
self.run_test('pkg.sub', 'pkg.__init__')
# [basic sub-package]
def test_basic_sub_package(self):
self.run_test('pkg.sub.__init__', 'pkg.__init__', pkg=True)
class BadBytecodeTest(unittest.TestCase):
"""But there are several things about the bytecode which might lead to the

View File

@ -1,5 +1,6 @@
from .. import util
import contextlib
import functools
import imp
import os
import os.path
@ -9,11 +10,24 @@ from test import support
def writes_bytecode(fxn):
"""Decorator to protect sys.dont_write_bytecode from mutation."""
@functools.wraps(fxn)
def wrapper(*args, **kwargs):
original = sys.dont_write_bytecode
sys.dont_write_bytecode = False
to_return = fxn(*args, **kwargs)
sys.dont_write_bytecode = original
return to_return
return wrapper
def writes_bytecode_files(fxn):
"""Decorator that returns the function if writing bytecode is enabled, else
a stub function that accepts anything and simply returns None."""
if sys.dont_write_bytecode:
return lambda *args, **kwargs: None
else:
@functools.wraps(fxn)
def wrapper(*args, **kwargs):
to_return = fxn(*args, **kwargs)
sys.dont_write_bytecode = False

View File

@ -0,0 +1,31 @@
from importlib import abc
from importlib import machinery
import unittest
class SubclassTests(unittest.TestCase):
"""Test that the various classes in importlib are subclasses of the
expected ABCS."""
def verify(self, ABC, *classes):
"""Verify the classes are subclasses of the ABC."""
for cls in classes:
self.assert_(issubclass(cls, ABC))
def test_Finder(self):
self.verify(abc.Finder, machinery.BuiltinImporter,
machinery.FrozenImporter, machinery.PathFinder)
def test_Loader(self):
self.verify(abc.Loader, machinery.BuiltinImporter,
machinery.FrozenImporter)
def test_main():
from test.support import run_unittest
run_unittest(SubclassTests)
if __name__ == '__main__':
test_main()