summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_importlib
diff options
context:
space:
mode:
authorBrett Cannon <brett@python.org>2013-05-31 22:56:47 (GMT)
committerBrett Cannon <brett@python.org>2013-05-31 22:56:47 (GMT)
commit0dbb4c7f1338d1391e7214b564ef4638bc257347 (patch)
treed9bd89758691c3b739c68e7eb50b444b15186bd1 /Lib/test/test_importlib
parentf1d7b11db905db5b40e2d97fa21af06871cf89ff (diff)
downloadcpython-0dbb4c7f1338d1391e7214b564ef4638bc257347.zip
cpython-0dbb4c7f1338d1391e7214b564ef4638bc257347.tar.gz
cpython-0dbb4c7f1338d1391e7214b564ef4638bc257347.tar.bz2
Issues #18088, 18089: Introduce
importlib.abc.Loader.init_module_attrs() and implement importlib.abc.InspectLoader.load_module(). The importlib.abc.Loader.init_module_attrs() method sets the various attributes on the module being loaded. It is done unconditionally to support reloading. Typically people used importlib.util.module_for_loader, but since that's a decorator there was no way to override it's actions, so init_module_attrs() came into existence to allow for overriding. This is also why module_for_loader is now pending deprecation (having its other use replaced by importlib.util.module_to_load). All of this allowed for importlib.abc.InspectLoader.load_module() to be implemented. At this point you can now implement a loader with nothing more than get_code() (which only requires get_source(); package support requires is_package()). Thanks to init_module_attrs() the implementation of load_module() is basically a context manager containing 2 methods calls, a call to exec(), and a return statement.
Diffstat (limited to 'Lib/test/test_importlib')
-rw-r--r--Lib/test/test_importlib/source/test_file_loader.py33
-rw-r--r--Lib/test/test_importlib/test_abc.py202
-rw-r--r--Lib/test/test_importlib/test_util.py24
3 files changed, 229 insertions, 30 deletions
diff --git a/Lib/test/test_importlib/source/test_file_loader.py b/Lib/test/test_importlib/source/test_file_loader.py
index 80999e8..6e248f3 100644
--- a/Lib/test/test_importlib/source/test_file_loader.py
+++ b/Lib/test/test_importlib/source/test_file_loader.py
@@ -15,7 +15,7 @@ import stat
import sys
import unittest
-from test.support import make_legacy_pyc
+from test.support import make_legacy_pyc, unload
class SimpleTest(unittest.TestCase):
@@ -26,23 +26,13 @@ class SimpleTest(unittest.TestCase):
"""
def test_load_module_API(self):
- # If fullname is not specified that assume self.name is desired.
- class TesterMixin(importlib.abc.Loader):
- def load_module(self, fullname): return fullname
- def module_repr(self, module): return '<module>'
-
- class Tester(importlib.abc.FileLoader, TesterMixin):
- def get_code(self, _): pass
- def get_source(self, _): pass
- def is_package(self, _): pass
+ class Tester(importlib.abc.FileLoader):
+ def get_source(self, _): return 'attr = 42'
+ def is_package(self, _): return False
- name = 'mod_name'
- loader = Tester(name, 'some_path')
- self.assertEqual(name, loader.load_module())
- self.assertEqual(name, loader.load_module(None))
- self.assertEqual(name, loader.load_module(name))
- with self.assertRaises(ImportError):
- loader.load_module(loader.name + 'XXX')
+ loader = Tester('blah', 'blah.py')
+ self.addCleanup(unload, 'blah')
+ module = loader.load_module() # Should not raise an exception.
def test_get_filename_API(self):
# If fullname is not set then assume self.path is desired.
@@ -473,13 +463,6 @@ class SourcelessLoaderBadBytecodeTest(BadBytecodeTest):
self._test_non_code_marshal(del_source=True)
-def test_main():
- from test.support import run_unittest
- run_unittest(SimpleTest,
- SourceLoaderBadBytecodeTest,
- SourcelessLoaderBadBytecodeTest
- )
-
if __name__ == '__main__':
- test_main()
+ unittest.main()
diff --git a/Lib/test/test_importlib/test_abc.py b/Lib/test/test_importlib/test_abc.py
index b443337..ea417f5 100644
--- a/Lib/test/test_importlib/test_abc.py
+++ b/Lib/test/test_importlib/test_abc.py
@@ -2,12 +2,14 @@ import importlib
from importlib import abc
from importlib import machinery
+import contextlib
import imp
import inspect
import io
import marshal
import os
import sys
+from test import support
import unittest
from unittest import mock
@@ -198,6 +200,15 @@ class ExecutionLoaderDefaultsTests(unittest.TestCase):
with self.assertRaises(ImportError):
self.ins.get_filename('blah')
+##### Loader concrete methods ##################################################
+class LoaderConcreteMethodTests(unittest.TestCase):
+
+ def test_init_module_attrs(self):
+ loader = LoaderSubclass()
+ module = imp.new_module('blah')
+ loader.init_module_attrs(module)
+ self.assertEqual(module.__loader__, loader)
+
##### InspectLoader concrete methods ###########################################
class InspectLoaderSourceToCodeTests(unittest.TestCase):
@@ -269,6 +280,93 @@ class InspectLoaderGetCodeTests(unittest.TestCase):
loader.get_code('blah')
+class InspectLoaderInitModuleTests(unittest.TestCase):
+
+ @staticmethod
+ def mock_is_package(return_value):
+ return mock.patch.object(InspectLoaderSubclass, 'is_package',
+ return_value=return_value)
+
+ def init_module_attrs(self, name):
+ loader = InspectLoaderSubclass()
+ module = imp.new_module(name)
+ loader.init_module_attrs(module)
+ self.assertEqual(module.__loader__, loader)
+ return module
+
+ def test_package(self):
+ # If a package, then __package__ == __name__, __path__ == []
+ with self.mock_is_package(True):
+ name = 'blah'
+ module = self.init_module_attrs(name)
+ self.assertEqual(module.__package__, name)
+ self.assertEqual(module.__path__, [])
+
+ def test_toplevel(self):
+ # If a module is top-level, __package__ == ''
+ with self.mock_is_package(False):
+ name = 'blah'
+ module = self.init_module_attrs(name)
+ self.assertEqual(module.__package__, '')
+
+ def test_submodule(self):
+ # If a module is contained within a package then set __package__ to the
+ # package name.
+ with self.mock_is_package(False):
+ name = 'pkg.mod'
+ module = self.init_module_attrs(name)
+ self.assertEqual(module.__package__, 'pkg')
+
+ def test_is_package_ImportError(self):
+ # If is_package() raises ImportError, __package__ should be None and
+ # __path__ should not be set.
+ with self.mock_is_package(False) as mocked_method:
+ mocked_method.side_effect = ImportError
+ name = 'mod'
+ module = self.init_module_attrs(name)
+ self.assertIsNone(module.__package__)
+ self.assertFalse(hasattr(module, '__path__'))
+
+
+class InspectLoaderLoadModuleTests(unittest.TestCase):
+
+ """Test InspectLoader.load_module()."""
+
+ module_name = 'blah'
+
+ def setUp(self):
+ support.unload(self.module_name)
+ self.addCleanup(support.unload, self.module_name)
+
+ def mock_get_code(self):
+ return mock.patch.object(InspectLoaderSubclass, 'get_code')
+
+ def test_get_code_ImportError(self):
+ # If get_code() raises ImportError, it should propagate.
+ with self.mock_get_code() as mocked_get_code:
+ mocked_get_code.side_effect = ImportError
+ with self.assertRaises(ImportError):
+ loader = InspectLoaderSubclass()
+ loader.load_module(self.module_name)
+
+ def test_get_code_None(self):
+ # If get_code() returns None, raise ImportError.
+ with self.mock_get_code() as mocked_get_code:
+ mocked_get_code.return_value = None
+ with self.assertRaises(ImportError):
+ loader = InspectLoaderSubclass()
+ loader.load_module(self.module_name)
+
+ def test_module_returned(self):
+ # The loaded module should be returned.
+ code = compile('attr = 42', '<string>', 'exec')
+ with self.mock_get_code() as mocked_get_code:
+ mocked_get_code.return_value = code
+ loader = InspectLoaderSubclass()
+ module = loader.load_module(self.module_name)
+ self.assertEqual(module, sys.modules[self.module_name])
+
+
##### ExecutionLoader concrete methods #########################################
class ExecutionLoaderGetCodeTests(unittest.TestCase):
@@ -327,6 +425,69 @@ class ExecutionLoaderGetCodeTests(unittest.TestCase):
self.assertEqual(module.attr, 42)
+class ExecutionLoaderInitModuleTests(unittest.TestCase):
+
+ @staticmethod
+ @contextlib.contextmanager
+ def mock_methods(is_package, filename):
+ is_package_manager = InspectLoaderInitModuleTests.mock_is_package(is_package)
+ get_filename_manager = mock.patch.object(ExecutionLoaderSubclass,
+ 'get_filename', return_value=filename)
+ with is_package_manager as mock_is_package:
+ with get_filename_manager as mock_get_filename:
+ yield {'is_package': mock_is_package,
+ 'get_filename': mock_get_filename}
+
+ def test_toplevel(self):
+ # Verify __loader__, __file__, and __package__; no __path__.
+ name = 'blah'
+ path = os.path.join('some', 'path', '{}.py'.format(name))
+ with self.mock_methods(False, path):
+ loader = ExecutionLoaderSubclass()
+ module = imp.new_module(name)
+ loader.init_module_attrs(module)
+ self.assertIs(module.__loader__, loader)
+ self.assertEqual(module.__file__, path)
+ self.assertEqual(module.__package__, '')
+ self.assertFalse(hasattr(module, '__path__'))
+
+ def test_package(self):
+ # Verify __loader__, __file__, __package__, and __path__.
+ name = 'pkg'
+ path = os.path.join('some', 'pkg', '__init__.py')
+ with self.mock_methods(True, path):
+ loader = ExecutionLoaderSubclass()
+ module = imp.new_module(name)
+ loader.init_module_attrs(module)
+ self.assertIs(module.__loader__, loader)
+ self.assertEqual(module.__file__, path)
+ self.assertEqual(module.__package__, 'pkg')
+ self.assertEqual(module.__path__, [os.path.dirname(path)])
+
+ def test_submodule(self):
+ # Verify __package__ and not __path__; test_toplevel() takes care of
+ # other attributes.
+ name = 'pkg.submodule'
+ path = os.path.join('some', 'pkg', 'submodule.py')
+ with self.mock_methods(False, path):
+ loader = ExecutionLoaderSubclass()
+ module = imp.new_module(name)
+ loader.init_module_attrs(module)
+ self.assertEqual(module.__package__, 'pkg')
+ self.assertEqual(module.__file__, path)
+ self.assertFalse(hasattr(module, '__path__'))
+
+ def test_get_filename_ImportError(self):
+ # If get_filename() raises ImportError, don't set __file__.
+ name = 'blah'
+ path = 'blah.py'
+ with self.mock_methods(False, path) as mocked_methods:
+ mocked_methods['get_filename'].side_effect = ImportError
+ loader = ExecutionLoaderSubclass()
+ module = imp.new_module(name)
+ loader.init_module_attrs(module)
+ self.assertFalse(hasattr(module, '__file__'))
+
##### SourceLoader concrete methods ############################################
class SourceOnlyLoaderMock(abc.SourceLoader):
@@ -621,6 +782,47 @@ class SourceLoaderGetSourceTests(unittest.TestCase):
self.assertEqual(mock.get_source(name), expect)
+class SourceLoaderInitModuleAttrTests(unittest.TestCase):
+
+ """Tests for importlib.abc.SourceLoader.init_module_attrs()."""
+
+ def test_init_module_attrs(self):
+ # If __file__ set, __cached__ == imp.cached_from_source(__file__).
+ name = 'blah'
+ path = 'blah.py'
+ loader = SourceOnlyLoaderMock(path)
+ module = imp.new_module(name)
+ loader.init_module_attrs(module)
+ self.assertEqual(module.__loader__, loader)
+ self.assertEqual(module.__package__, '')
+ self.assertEqual(module.__file__, path)
+ self.assertEqual(module.__cached__, imp.cache_from_source(path))
+
+ @mock.patch('importlib._bootstrap.cache_from_source')
+ def test_cache_from_source_NotImplementedError(self, mock_cache_from_source):
+ # If imp.cache_from_source() raises NotImplementedError don't set
+ # __cached__.
+ mock_cache_from_source.side_effect = NotImplementedError
+ name = 'blah'
+ path = 'blah.py'
+ loader = SourceOnlyLoaderMock(path)
+ module = imp.new_module(name)
+ loader.init_module_attrs(module)
+ self.assertEqual(module.__file__, path)
+ self.assertFalse(hasattr(module, '__cached__'))
+
+ def test_no_get_filename(self):
+ # No __file__, no __cached__.
+ with mock.patch.object(SourceOnlyLoaderMock, 'get_filename') as mocked:
+ mocked.side_effect = ImportError
+ name = 'blah'
+ loader = SourceOnlyLoaderMock('blah.py')
+ module = imp.new_module(name)
+ loader.init_module_attrs(module)
+ self.assertFalse(hasattr(module, '__file__'))
+ self.assertFalse(hasattr(module, '__cached__'))
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py
index 9897def..e6b0084 100644
--- a/Lib/test/test_importlib/test_util.py
+++ b/Lib/test/test_importlib/test_util.py
@@ -5,6 +5,7 @@ import sys
from test import support
import types
import unittest
+import warnings
class ModuleToLoadTests(unittest.TestCase):
@@ -72,14 +73,27 @@ class ModuleForLoaderTests(unittest.TestCase):
"""Tests for importlib.util.module_for_loader."""
+ @staticmethod
+ def module_for_loader(func):
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore', PendingDeprecationWarning)
+ return util.module_for_loader(func)
+
+ def test_warning(self):
+ # Should raise a PendingDeprecationWarning when used.
+ with warnings.catch_warnings():
+ warnings.simplefilter('error', PendingDeprecationWarning)
+ with self.assertRaises(PendingDeprecationWarning):
+ func = util.module_for_loader(lambda x: x)
+
def return_module(self, name):
- fxn = util.module_for_loader(lambda self, module: module)
+ fxn = self.module_for_loader(lambda self, module: module)
return fxn(self, name)
def raise_exception(self, name):
def to_wrap(self, module):
raise ImportError
- fxn = util.module_for_loader(to_wrap)
+ fxn = self.module_for_loader(to_wrap)
try:
fxn(self, name)
except ImportError:
@@ -100,7 +114,7 @@ class ModuleForLoaderTests(unittest.TestCase):
class FakeLoader:
def is_package(self, name):
return True
- @util.module_for_loader
+ @self.module_for_loader
def load_module(self, module):
return module
name = 'a.b.c'
@@ -134,7 +148,7 @@ class ModuleForLoaderTests(unittest.TestCase):
def test_decorator_attrs(self):
def fxn(self, module): pass
- wrapped = util.module_for_loader(fxn)
+ wrapped = self.module_for_loader(fxn)
self.assertEqual(wrapped.__name__, fxn.__name__)
self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
@@ -160,7 +174,7 @@ class ModuleForLoaderTests(unittest.TestCase):
self._pkg = is_package
def is_package(self, name):
return self._pkg
- @util.module_for_loader
+ @self.module_for_loader
def load_module(self, module):
return module