summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrett Cannon <bcannon@gmail.com>2009-01-26 01:16:50 +0000
committerBrett Cannon <bcannon@gmail.com>2009-01-26 01:16:50 +0000
commit878dc9ca7389e829ab0f73d4c91a7dc3cd00c4c0 (patch)
treedeac60dfc018fdcc2f4e8a3733ee377b87403797
parent7b47bd10357d5faec79d4a9a29fe400196789019 (diff)
downloadcpython-878dc9ca7389e829ab0f73d4c91a7dc3cd00c4c0.tar.gz
Backport importlib in the form of providing importlib.import_module(). This has
been done purely to help transitions from 2.7 to 3.1.
-rw-r--r--Doc/library/importlib.rst27
-rw-r--r--Doc/library/modules.rst1
-rw-r--r--Lib/importlib.py38
-rw-r--r--Lib/test/test_importlib.py173
-rw-r--r--Misc/NEWS3
5 files changed, 242 insertions, 0 deletions
diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst
new file mode 100644
index 0000000000..375c2ae918
--- /dev/null
+++ b/Doc/library/importlib.rst
@@ -0,0 +1,27 @@
+:mod:`importlib` -- Convenience wrappers for :func:`__import__`
+===============================================================
+
+.. module:: importlib
+ :synopsis: Convenience wrappers for __import__
+
+.. moduleauthor:: Brett Cannon <brett@python.org>
+.. sectionauthor:: Brett Cannon <brett@python.org>
+
+.. versionadded:: 2.7
+
+This module is a minor subset of what is available in the more full-featured
+package of the same name from Python 3.1 that provides a complete
+implementation of :keyword:`import`. What is here has been provided to
+help ease in transitioning from 2.7 to 3.1.
+
+
+.. function:: import_module(name, package=None)
+
+ Import a module. The *name* argument specifies what module to
+ import in absolute or relative terms
+ (e.g. either ``pkg.mod`` or ``..mod``). If the name is
+ specified in relative terms, then the *package* argument must be
+ specified to the package which is to act as the anchor for resolving the
+ package name (e.g. ``import_module('..mod', 'pkg.subpkg')`` will import
+ ``pkg.mod``). The specified module will be inserted into
+ :data:`sys.modules` and returned.
diff --git a/Doc/library/modules.rst b/Doc/library/modules.rst
index ec6f7cd575..b5543e6139 100644
--- a/Doc/library/modules.rst
+++ b/Doc/library/modules.rst
@@ -14,6 +14,7 @@ The full list of modules described in this chapter is:
.. toctree::
imp.rst
+ importlib.rst
imputil.rst
zipimport.rst
pkgutil.rst
diff --git a/Lib/importlib.py b/Lib/importlib.py
new file mode 100644
index 0000000000..89398deda0
--- /dev/null
+++ b/Lib/importlib.py
@@ -0,0 +1,38 @@
+"""Backport of importlib.import_module from 3.x."""
+import sys
+
+def _resolve_name(name, package, level):
+ """Return the absolute name of the module to be imported."""
+ level -= 1
+ try:
+ if package.count('.') < level:
+ raise ValueError("attempted relative import beyond top-level "
+ "package")
+ except AttributeError:
+ raise ValueError("__package__ not set to a string")
+ base = package.rsplit('.', level)[0]
+ if name:
+ return "{0}.{1}".format(base, name)
+ else:
+ return base
+
+
+def import_module(name, package=None):
+ """Import a module.
+
+ The 'package' argument is required when performing a relative import. It
+ specifies the package to use as the anchor point from which to resolve the
+ relative import to an absolute import.
+
+ """
+ if name.startswith('.'):
+ if not package:
+ raise TypeError("relative imports require the 'package' argument")
+ level = 0
+ for character in name:
+ if character != '.':
+ break
+ level += 1
+ name = _resolve_name(name[level:], package, level)
+ __import__(name)
+ return sys.modules[name]
diff --git a/Lib/test/test_importlib.py b/Lib/test/test_importlib.py
new file mode 100644
index 0000000000..572b1f169e
--- /dev/null
+++ b/Lib/test/test_importlib.py
@@ -0,0 +1,173 @@
+import contextlib
+import imp
+import importlib
+import sys
+import unittest
+
+
+@contextlib.contextmanager
+def uncache(*names):
+ """Uncache a module from sys.modules.
+
+ A basic sanity check is performed to prevent uncaching modules that either
+ cannot/shouldn't be uncached.
+
+ """
+ for name in names:
+ if name in ('sys', 'marshal', 'imp'):
+ raise ValueError(
+ "cannot uncache {0} as it will break _importlib".format(name))
+ try:
+ del sys.modules[name]
+ except KeyError:
+ pass
+ try:
+ yield
+ finally:
+ for name in names:
+ try:
+ del sys.modules[name]
+ except KeyError:
+ pass
+
+
+@contextlib.contextmanager
+def import_state(**kwargs):
+ """Context manager to manage the various importers and stored state in the
+ sys module.
+
+ The 'modules' attribute is not supported as the interpreter state stores a
+ pointer to the dict that the interpreter uses internally;
+ reassigning to sys.modules does not have the desired effect.
+
+ """
+ originals = {}
+ try:
+ for attr, default in (('meta_path', []), ('path', []),
+ ('path_hooks', []),
+ ('path_importer_cache', {})):
+ originals[attr] = getattr(sys, attr)
+ if attr in kwargs:
+ new_value = kwargs[attr]
+ del kwargs[attr]
+ else:
+ new_value = default
+ setattr(sys, attr, new_value)
+ if len(kwargs):
+ raise ValueError(
+ 'unrecognized arguments: {0}'.format(kwargs.keys()))
+ yield
+ finally:
+ for attr, value in originals.items():
+ setattr(sys, attr, value)
+
+
+class mock_modules(object):
+
+ """A mock importer/loader."""
+
+ def __init__(self, *names):
+ self.modules = {}
+ for name in names:
+ if not name.endswith('.__init__'):
+ import_name = name
+ else:
+ import_name = name[:-len('.__init__')]
+ if '.' not in name:
+ package = None
+ elif import_name == name:
+ package = name.rsplit('.', 1)[0]
+ else:
+ package = import_name
+ module = imp.new_module(import_name)
+ module.__loader__ = self
+ module.__file__ = '<mock __file__>'
+ module.__package__ = package
+ module.attr = name
+ if import_name != name:
+ module.__path__ = ['<mock __path__>']
+ self.modules[import_name] = module
+
+ def __getitem__(self, name):
+ return self.modules[name]
+
+ def find_module(self, fullname, path=None):
+ if fullname not in self.modules:
+ return None
+ else:
+ return self
+
+ def load_module(self, fullname):
+ if fullname not in self.modules:
+ raise ImportError
+ else:
+ sys.modules[fullname] = self.modules[fullname]
+ return self.modules[fullname]
+
+ def __enter__(self):
+ self._uncache = uncache(*self.modules.keys())
+ self._uncache.__enter__()
+ return self
+
+ def __exit__(self, *exc_info):
+ self._uncache.__exit__(None, None, None)
+
+
+
+class ImportModuleTests(unittest.TestCase):
+
+ """Test importlib.import_module."""
+
+ def test_module_import(self):
+ # Test importing a top-level module.
+ with mock_modules('top_level') as mock:
+ with import_state(meta_path=[mock]):
+ module = importlib.import_module('top_level')
+ self.assertEqual(module.__name__, 'top_level')
+
+ def test_absolute_package_import(self):
+ # Test importing a module from a package with an absolute name.
+ pkg_name = 'pkg'
+ pkg_long_name = '{0}.__init__'.format(pkg_name)
+ name = '{0}.mod'.format(pkg_name)
+ with mock_modules(pkg_long_name, name) as mock:
+ with import_state(meta_path=[mock]):
+ module = importlib.import_module(name)
+ self.assertEqual(module.__name__, name)
+
+ def test_relative_package_import(self):
+ # Test importing a module from a package through a relatve import.
+ pkg_name = 'pkg'
+ pkg_long_name = '{0}.__init__'.format(pkg_name)
+ module_name = 'mod'
+ absolute_name = '{0}.{1}'.format(pkg_name, module_name)
+ relative_name = '.{0}'.format(module_name)
+ with mock_modules(pkg_long_name, absolute_name) as mock:
+ with import_state(meta_path=[mock]):
+ module = importlib.import_module(relative_name, pkg_name)
+ self.assertEqual(module.__name__, absolute_name)
+
+ def test_absolute_import_with_package(self):
+ # Test importing a module from a package with an absolute name with
+ # the 'package' argument given.
+ pkg_name = 'pkg'
+ pkg_long_name = '{0}.__init__'.format(pkg_name)
+ name = '{0}.mod'.format(pkg_name)
+ with mock_modules(pkg_long_name, name) as mock:
+ with import_state(meta_path=[mock]):
+ module = importlib.import_module(name, pkg_name)
+ self.assertEqual(module.__name__, name)
+
+ def test_relative_import_wo_package(self):
+ # Relative imports cannot happen without the 'package' argument being
+ # set.
+ self.assertRaises(TypeError, importlib.import_module, '.support')
+
+
+def test_main():
+ from test.test_support import run_unittest
+ run_unittest(ImportModuleTests)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Misc/NEWS b/Misc/NEWS
index 9878199e67..da016c1d18 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -145,6 +145,9 @@ Core and Builtins
Library
-------
+- Backport importlib from Python 3.1. Only the import_module() function has
+ been backported to help facilitate transitions from 2.7 to 3.1.
+
- Issue #1885: distutils. When running sdist with --formats=tar,gztar
the tar file was overriden by the gztar one.