mirror of
https://github.com/python/cpython.git
synced 2025-09-27 10:50:04 +00:00
Backport importlib in the form of providing importlib.import_module(). This has
been done purely to help transitions from 2.7 to 3.1.
This commit is contained in:
parent
aaedcef578
commit
93881c6c58
5 changed files with 242 additions and 0 deletions
27
Doc/library/importlib.rst
Normal file
27
Doc/library/importlib.rst
Normal file
|
@ -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.
|
|
@ -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
|
||||
|
|
38
Lib/importlib.py
Normal file
38
Lib/importlib.py
Normal file
|
@ -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]
|
173
Lib/test/test_importlib.py
Normal file
173
Lib/test/test_importlib.py
Normal file
|
@ -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()
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue