[Python-checkins] r68953 - in python/trunk: Doc/library/importlib.rst Doc/library/modules.rst Lib/importlib.py Lib/test/test_importlib.py Misc/NEWS

brett.cannon python-checkins at python.org
Mon Jan 26 02:16:51 CET 2009


Author: brett.cannon
Date: Mon Jan 26 02:16:50 2009
New Revision: 68953

Log:
Backport importlib in the form of providing importlib.import_module(). This has
been done purely to help transitions from 2.7 to 3.1.


Added:
   python/trunk/Doc/library/importlib.rst
   python/trunk/Lib/importlib.py
   python/trunk/Lib/test/test_importlib.py
Modified:
   python/trunk/Doc/library/modules.rst
   python/trunk/Misc/NEWS

Added: python/trunk/Doc/library/importlib.rst
==============================================================================
--- (empty file)
+++ python/trunk/Doc/library/importlib.rst	Mon Jan 26 02:16:50 2009
@@ -0,0 +1,27 @@
+:mod:`importlib` -- Convenience wrappers for :func:`__import__`
+===============================================================
+
+.. module:: importlib
+   :synopsis: Convenience wrappers for __import__
+
+.. moduleauthor:: Brett Cannon <brett at python.org>
+.. sectionauthor:: Brett Cannon <brett at 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.

Modified: python/trunk/Doc/library/modules.rst
==============================================================================
--- python/trunk/Doc/library/modules.rst	(original)
+++ python/trunk/Doc/library/modules.rst	Mon Jan 26 02:16:50 2009
@@ -14,6 +14,7 @@
 .. toctree::
 
    imp.rst
+   importlib.rst
    imputil.rst
    zipimport.rst
    pkgutil.rst

Added: python/trunk/Lib/importlib.py
==============================================================================
--- (empty file)
+++ python/trunk/Lib/importlib.py	Mon Jan 26 02:16:50 2009
@@ -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]

Added: python/trunk/Lib/test/test_importlib.py
==============================================================================
--- (empty file)
+++ python/trunk/Lib/test/test_importlib.py	Mon Jan 26 02:16:50 2009
@@ -0,0 +1,173 @@
+import contextlib
+import imp
+import importlib
+import sys
+import unittest
+
+
+ at 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
+
+
+ at 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()

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Mon Jan 26 02:16:50 2009
@@ -145,6 +145,9 @@
 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.
 


More information about the Python-checkins mailing list