Commit 2c318a13 authored by Brett Cannon's avatar Brett Cannon

Rewrite the code implementing __import__ for importlib. Now it is much simpler

and relies much more on meta path finders to abstract out various parts of
import.

As part of this the semantics for import_module tightened up and now follow
__import__ much more closely (biggest thing is that the 'package' argument must
now already be imported, else a SystemError is raised).
parent 887b3f26
......@@ -72,11 +72,15 @@ Functions
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
set 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.
``pkg.mod``).
The :func:`import_module` function acts as a simplifying wrapper around
:func:`__import__`. This means all semantics of the function are derived
from :func:`__import__`, including requiring the package where an import is
occuring from to already be imported (i.e., *package* must already be
imported).
:mod:`importlib.machinery` -- Importers and path hooks
------------------------------------------------------
......
to do
/////
* Create sandbox directory for a distutils packaging of what is in Python 2.7.
* Use rpartition for getting the package of a module.
+ Make sure that an empty string is acceptable for __package__.
* Create meta_path importer for sys.path.
+ Document.
* Refactor __import__.
+ Create a greatest common denominator function for __import__/import_module
that takes in an absolute module name and performs the import.
- Needs of __import__
* Figure out caller's package.
* Import module.
* Set __package__.
* Figure out what module to return.
- Needs of import_module
* Resolve name/level.
* Import module.
+ Use GCD import for __import__.
+ Use GCD import for import_module.
+ Make sure there is a test for the empty string as acceptable for
__package__.
* Implement PEP 302 protocol for loaders (should just be a matter of testing).
......@@ -66,13 +41,11 @@ to do
* source_path
* bytecode_path
* write_bytecode
* write_bytecode (not abstract)
+ util
- get_module decorator (new name)
- check_name decorator (new name)
- resolve_name
- get_module decorator (rename: module_for_loader)
+ machinery
......@@ -88,6 +61,8 @@ to do
* SourceFinder
* (?) Loader
- PathFinder
* Write benchmark suite.
* OPTIMIZE!
......
......@@ -29,7 +29,7 @@ def _set__import__():
"""Set __import__ to an instance of Import."""
global original__import__
original__import__ = __import__
__builtins__['__import__'] = Import()
__builtins__['__import__'] = _bootstrap._import
def _reset__import__():
......@@ -114,7 +114,7 @@ marshal._r_long = _r_long
# Public API #########################################################
__import__ = _bootstrap.Import().__call__
__import__ = _bootstrap._import
def import_module(name, package=None):
......@@ -125,17 +125,15 @@ def import_module(name, package=None):
relative import to an absolute import.
"""
level = 0
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 = Import._resolve_name(name[level:], package, level)
__import__(name)
return sys.modules[name]
return _bootstrap._gcd_import(name[level:], package, level)
# XXX This should go away once the public API is done.
......
This diff is collapsed.
......@@ -38,8 +38,9 @@ class Using__package__(unittest.TestCase):
with util.mock_modules('pkg.__init__', 'pkg.fake') as importer:
with util.import_state(meta_path=[importer]):
import_util.import_('pkg.fake')
module = import_util.import_('', globals={'__package__': 'pkg.fake'},
fromlist=['attr'], level=2)
module = import_util.import_('',
globals={'__package__': 'pkg.fake'},
fromlist=['attr'], level=2)
self.assertEquals(module.__name__, 'pkg')
def test_using___name__(self):
......@@ -82,7 +83,7 @@ class Setting__package__(unittest.TestCase):
with util.import_state(meta_path=[mock]):
del mock['top_level'].__package__
module = import_util.import_('top_level')
self.assert_(module.__package__ is None)
self.assertEqual(module.__package__, '')
# [package]
def test_package(self):
......
......@@ -64,7 +64,8 @@ class UseCache(unittest.TestCase):
with util.import_state(meta_path=[importer]):
module = import_util.import_('pkg', fromlist=['module'])
self.assert_(hasattr(module, 'module'))
self.assertEquals(id(module.module), id(sys.modules['pkg.module']))
self.assertEquals(id(module.module),
id(sys.modules['pkg.module']))
def test_main():
......
......@@ -10,148 +10,6 @@ from types import MethodType
import unittest
class BaseTests(unittest.TestCase):
"""When sys.meta_path cannot find the desired module, sys.path is
consulted. For each entry on the sequence [order], sys.path_importer_cache
is checked to see if it contains a key for the entry [cache check]. If an
importer is found then it is consulted before trying the next entry in
sys.path [cache use]. The 'path' argument to find_module() is never used
when trying to find a module [path not used].
If an entry from sys.path is not in sys.path_importer_cache, sys.path_hooks
is called in turn [hooks order]. If a path hook cannot handle an entry,
ImportError is raised [hook failure]. Otherwise the resulting object is
cached in sys.path_importer_cache and then consulted [hook success]. If no
hook is found, None is set in sys.path_importer_cache and the default
importer is tried [no hook].
For use of __path__ in a package, the above is all true, just substitute
"sys.path" for "__path__".
"""
def order_test(self, to_import, entry, search_path, path=[]):
# [order]
log = []
class LogFindModule(util.mock_modules):
def find_module(self, fullname):
log.append(self)
return super().find_module(fullname)
assert len(search_path) == 2
misser = LogFindModule(search_path[0])
hitter = LogFindModule(to_import)
with nested(misser, hitter):
cache = dict(zip(search_path, (misser, hitter)))
with util.import_state(path=path, path_importer_cache=cache):
import_util.import_(to_import)
self.assertEquals(log[0], misser)
self.assertEquals(log[1], hitter)
@import_util.importlib_only # __import__ uses PyDict_GetItem(), bypassing log.
def cache_use_test(self, to_import, entry, path=[]):
# [cache check], [cache use]
log = []
class LoggingDict(dict):
def __getitem__(self, item):
log.append(item)
return super(LoggingDict, self).__getitem__(item)
with util.mock_modules(to_import) as importer:
cache = LoggingDict()
cache[entry] = importer
with util.import_state(path=[entry], path_importer_cache=cache):
module = import_util.import_(to_import, fromlist=['a'])
self.assert_(module is importer[to_import])
self.assertEquals(len(cache), 1)
self.assertEquals([entry], log)
def hooks_order_test(self, to_import, entry, path=[]):
# [hooks order], [hooks failure], [hook success]
log = []
def logging_hook(entry):
log.append(entry)
raise ImportError
with util.mock_modules(to_import) as importer:
hitter = import_util.mock_path_hook(entry, importer=importer)
path_hooks = [logging_hook, logging_hook, hitter]
with util.import_state(path_hooks=path_hooks, path=path):
import_util.import_(to_import)
self.assertEquals(sys.path_importer_cache[entry], importer)
self.assertEquals(len(log), 2)
# [no hook] XXX Worry about after deciding how to handle the default hook.
def path_argument_test(self, to_import):
# [path not used]
class BadImporter:
"""Class to help detect TypeError from calling find_module() with
an improper number of arguments."""
def find_module(name):
raise ImportError
try:
import_util.import_(to_import)
except ImportError:
pass
class PathTests(BaseTests):
"""Tests for sys.path."""
def test_order(self):
self.order_test('hit', 'second', ['first', 'second'],
['first', 'second'])
def test_cache_use(self):
entry = "found!"
self.cache_use_test('hit', entry, [entry])
def test_hooks_order(self):
entry = "found!"
self.hooks_order_test('hit', entry, [entry])
def test_path_argument(self):
name = 'total junk'
with util.uncache(name):
self.path_argument_test(name)
class __path__Tests(BaseTests):
"""Tests for __path__."""
def run_test(self, test, entry, path, *args):
with util.mock_modules('pkg.__init__') as importer:
importer['pkg'].__path__ = path
importer.load_module('pkg')
test('pkg.hit', entry, *args)
@import_util.importlib_only # XXX Unknown reason why this fails.
def test_order(self):
self.run_test(self.order_test, 'second', ('first', 'second'), ['first',
'second'])
def test_cache_use(self):
location = "I'm here!"
self.run_test(self.cache_use_test, location, [location])
def test_hooks_order(self):
location = "I'm here!"
self.run_test(self.hooks_order_test, location, [location])
def test_path_argument(self):
module = imp.new_module('pkg')
module.__path__ = ['random __path__']
name = 'pkg.whatever'
sys.modules['pkg'] = module
with util.uncache('pkg', name):
self.path_argument_test(name)
class FinderTests(unittest.TestCase):
"""Tests for SysPathImporter."""
......
import functools
import importlib
import importlib._bootstrap
using___import__ = False
......@@ -9,7 +9,8 @@ def import_(*args, **kwargs):
"""Delegate to allow for injecting different implementations of import."""
if using___import__:
return __import__(*args, **kwargs)
return importlib.Import()(*args, **kwargs)
#return importlib.Import()(*args, **kwargs)
return importlib._bootstrap._import(*args, **kwargs)
def importlib_only(fxn):
......
import unittest
import importlib
from . import util
import imp
import importlib
import sys
import unittest
class ImportModuleTests(unittest.TestCase):
......@@ -33,6 +35,7 @@ class ImportModuleTests(unittest.TestCase):
relative_name = '.{0}'.format(module_name)
with util.mock_modules(pkg_long_name, absolute_name) as mock:
with util.import_state(meta_path=[mock]):
importlib.import_module(pkg_name)
module = importlib.import_module(relative_name, pkg_name)
self.assertEqual(module.__name__, absolute_name)
......@@ -44,6 +47,7 @@ class ImportModuleTests(unittest.TestCase):
name = '{0}.mod'.format(pkg_name)
with util.mock_modules(pkg_long_name, name) as mock:
with util.import_state(meta_path=[mock]):
importlib.import_module(pkg_name)
module = importlib.import_module(name, pkg_name)
self.assertEqual(module.__name__, name)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment