mirror of
https://github.com/python/cpython.git
synced 2025-09-27 18:59:43 +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::
|
.. toctree::
|
||||||
|
|
||||||
imp.rst
|
imp.rst
|
||||||
|
importlib.rst
|
||||||
imputil.rst
|
imputil.rst
|
||||||
zipimport.rst
|
zipimport.rst
|
||||||
pkgutil.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
|
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
|
- Issue #1885: distutils. When running sdist with --formats=tar,gztar
|
||||||
the tar file was overriden by the gztar one.
|
the tar file was overriden by the gztar one.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue