Kaydet (Commit) 2a922ed6 authored tarafından Brett Cannon's avatar Brett Cannon

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.
üst aa1c8d88
This diff is collapsed.
......@@ -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
......@@ -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)
......
"""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
......@@ -65,10 +65,11 @@ class LoaderTests(unittest.TestCase, metaclass=abc.ABCMeta):
Attributes to verify:
* __file__
* __loader__
* __name__
* __file__
* __package__
* __path__
* __loader__
"""
pass
......
This diff is collapsed.
......@@ -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
......
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
......
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()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment