Issue #15576: Allow extension modules to be a package's __init__

module again. Also took the opportunity to stop accidentally exporting
_imp.extension_suffixes() as public.
This commit is contained in:
Brett Cannon 2012-08-10 13:47:54 -04:00
parent f4dc9204cc
commit ac9f2f3de3
13 changed files with 3700 additions and 3688 deletions

View File

@ -671,9 +671,8 @@ find and load modules.
The *path* argument is the directory for which the finder is in charge of
searching.
The *loader_details* argument is a variable number of 3-item tuples each
containing a loader, file suffixes the loader recognizes, and a boolean
representing whether the loader handles packages.
The *loader_details* argument is a variable number of 2-item tuples each
containing a loader and a sequence of file suffixes the loader recognizes.
The finder will cache the directory contents as necessary, making stat calls
for each module search to verify the cache is not outdated. Because cache
@ -798,7 +797,8 @@ find and load modules.
.. method:: is_package(fullname)
Returns ``False`` as extension modules can never be packages.
Returns ``True`` if the file path points to a package's ``__init__``
module based on :attr:`EXTENSION_SUFFIXES`.
.. method:: get_code(fullname)

View File

@ -9,7 +9,7 @@ functionality over this module.
from _imp import (lock_held, acquire_lock, release_lock,
load_dynamic, get_frozen_object, is_frozen_package,
init_builtin, init_frozen, is_builtin, is_frozen,
_fix_co_filename, extension_suffixes)
_fix_co_filename)
# Directly exposed by this module
from importlib._bootstrap import new_module
@ -51,7 +51,7 @@ def get_suffixes():
warnings.warn('imp.get_suffixes() is deprecated; use the constants '
'defined on importlib.machinery instead',
DeprecationWarning, 2)
extensions = [(s, 'rb', C_EXTENSION) for s in extension_suffixes()]
extensions = [(s, 'rb', C_EXTENSION) for s in machinery.EXTENSION_SUFFIXES]
source = [(s, 'U', PY_SOURCE) for s in machinery.SOURCE_SUFFIXES]
bytecode = [(s, 'rb', PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES]

View File

@ -1067,6 +1067,10 @@ class SourcelessFileLoader(FileLoader, _LoaderBasics):
return None
# Filled in by _setup().
EXTENSION_SUFFIXES = []
class ExtensionFileLoader:
"""Loader for extension modules.
@ -1089,6 +1093,8 @@ class ExtensionFileLoader:
module = _call_with_frames_removed(_imp.load_dynamic,
fullname, self.path)
_verbose_message('extension module loaded from {!r}', self.path)
if self.is_package(fullname):
module.__path__ = [_path_split(self.path)[0]]
return module
except:
if not is_reload and fullname in sys.modules:
@ -1097,7 +1103,12 @@ class ExtensionFileLoader:
def is_package(self, fullname):
"""Return False as an extension module can never be a package."""
return False
file_name = _path_split(self.path)[1]
for suffix in EXTENSION_SUFFIXES:
if file_name == '__init__' + suffix:
return True
else:
return False
def get_code(self, fullname):
"""Return None as an extension module cannot create a code object."""
@ -1283,14 +1294,10 @@ class FileFinder:
"""Initialize with the path to search on and a variable number of
3-tuples containing the loader, file suffixes the loader recognizes,
and a boolean of whether the loader handles packages."""
packages = []
modules = []
for loader, suffixes, supports_packages in details:
modules.extend((suffix, loader) for suffix in suffixes)
if supports_packages:
packages.extend((suffix, loader) for suffix in suffixes)
self.packages = packages
self.modules = modules
loaders = []
for loader, suffixes in details:
loaders.extend((suffix, loader) for suffix in suffixes)
self._loaders = loaders
# Base (directory) path
self.path = path or '.'
self._path_mtime = -1
@ -1336,7 +1343,7 @@ class FileFinder:
if cache_module in cache:
base_path = _path_join(self.path, tail_module)
if _path_isdir(base_path):
for suffix, loader in self.packages:
for suffix, loader in self._loaders:
init_filename = '__init__' + suffix
full_path = _path_join(base_path, init_filename)
if _path_isfile(full_path):
@ -1346,7 +1353,7 @@ class FileFinder:
# find a module in the next section.
is_namespace = True
# Check for a file w/ a proper suffix exists.
for suffix, loader in self.modules:
for suffix, loader in self._loaders:
if cache_module + suffix in cache:
full_path = _path_join(self.path, tail_module + suffix)
if _path_isfile(full_path):
@ -1589,9 +1596,9 @@ def _get_supported_file_loaders():
Each item is a tuple (loader, suffixes, allow_packages).
"""
extensions = ExtensionFileLoader, _imp.extension_suffixes(), False
source = SourceFileLoader, SOURCE_SUFFIXES, True
bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES, True
extensions = ExtensionFileLoader, _imp.extension_suffixes()
source = SourceFileLoader, SOURCE_SUFFIXES
bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES
return [extensions, source, bytecode]
@ -1689,9 +1696,10 @@ def _setup(sys_module, _imp_module):
setattr(self_module, 'path_separators', set(path_separators))
# Constants
setattr(self_module, '_relax_case', _make_relax_case())
EXTENSION_SUFFIXES.extend(_imp.extension_suffixes())
if builtin_os == 'nt':
SOURCE_SUFFIXES.append('.pyw')
if '_d.pyd' in _imp.extension_suffixes():
if '_d.pyd' in EXTENSION_SUFFIXES:
WindowsRegistryFinder.DEBUG_BUILD = True

View File

@ -3,7 +3,8 @@
import _imp
from ._bootstrap import (SOURCE_SUFFIXES, DEBUG_BYTECODE_SUFFIXES,
OPTIMIZED_BYTECODE_SUFFIXES, BYTECODE_SUFFIXES)
OPTIMIZED_BYTECODE_SUFFIXES, BYTECODE_SUFFIXES,
EXTENSION_SUFFIXES)
from ._bootstrap import BuiltinImporter
from ._bootstrap import FrozenImporter
from ._bootstrap import WindowsRegistryFinder
@ -13,7 +14,6 @@ from ._bootstrap import SourceFileLoader
from ._bootstrap import SourcelessFileLoader
from ._bootstrap import ExtensionFileLoader
EXTENSION_SUFFIXES = _imp.extension_suffixes()
def all_suffixes():
"""Returns a list of all recognized module suffixes for this process"""

View File

@ -16,8 +16,7 @@ class ExtensionModuleCaseSensitivityTest(unittest.TestCase):
assert good_name != bad_name
finder = _bootstrap.FileFinder(ext_util.PATH,
(_bootstrap.ExtensionFileLoader,
imp.extension_suffixes(),
False))
_bootstrap.EXTENSION_SUFFIXES))
return finder.find_module(bad_name)
def test_case_sensitive(self):

View File

@ -1,8 +1,7 @@
from importlib import _bootstrap
from importlib import machinery
from .. import abc
from . import util
import imp
import unittest
class FinderTests(abc.FinderTests):
@ -10,17 +9,16 @@ class FinderTests(abc.FinderTests):
"""Test the finder for extension modules."""
def find_module(self, fullname):
importer = _bootstrap.FileFinder(util.PATH,
(_bootstrap.ExtensionFileLoader,
imp.extension_suffixes(),
False))
importer = machinery.FileFinder(util.PATH,
(machinery.ExtensionFileLoader,
machinery.EXTENSION_SUFFIXES))
return importer.find_module(fullname)
def test_module(self):
self.assertTrue(self.find_module(util.NAME))
def test_package(self):
# Extension modules cannot be an __init__ for a package.
# No extension module as an __init__ available for testing.
pass
def test_module_in_package(self):
@ -28,7 +26,7 @@ class FinderTests(abc.FinderTests):
pass
def test_package_in_package(self):
# Extension modules cannot be an __init__ for a package.
# No extension module as an __init__ available for testing.
pass
def test_package_over_module(self):
@ -38,8 +36,6 @@ class FinderTests(abc.FinderTests):
def test_failure(self):
self.assertIsNone(self.find_module('asdfjkl;'))
# XXX Raise an exception if someone tries to use the 'path' argument?
def test_main():
from test.support import run_unittest

View File

@ -3,6 +3,7 @@ from . import util as ext_util
from .. import abc
from .. import util
import os.path
import sys
import unittest
@ -38,11 +39,11 @@ class LoaderTests(abc.LoaderTests):
machinery.ExtensionFileLoader)
def test_package(self):
# Extensions are not found in packages.
# No extension module as __init__ available for testing.
pass
def test_lacking_parent(self):
# Extensions are not found in packages.
# No extension module in a package available for testing.
pass
def test_module_reuse(self):
@ -61,6 +62,13 @@ class LoaderTests(abc.LoaderTests):
self.load_module(name)
self.assertEqual(cm.exception.name, name)
def test_is_package(self):
self.assertFalse(self.loader.is_package(ext_util.NAME))
for suffix in machinery.EXTENSION_SUFFIXES:
path = os.path.join('some', 'path', 'pkg', '__init__' + suffix)
loader = machinery.ExtensionFileLoader('pkg', path)
self.assertTrue(loader.is_package('pkg'))
def test_main():
from test.support import run_unittest

View File

@ -1,4 +1,4 @@
from importlib import _bootstrap
from importlib import machinery
from . import util
import collections
@ -14,8 +14,8 @@ class PathHookTests(unittest.TestCase):
# XXX Should it only work for directories containing an extension module?
def hook(self, entry):
return _bootstrap.FileFinder.path_hook((_bootstrap.ExtensionFileLoader,
imp.extension_suffixes(), False))(entry)
return machinery.FileFinder.path_hook((machinery.ExtensionFileLoader,
machinery.EXTENSION_SUFFIXES))(entry)
def test_success(self):
# Path hook should handle a directory where a known extension module

View File

@ -23,11 +23,9 @@ class CaseSensitivityTest(unittest.TestCase):
def find(self, path):
finder = machinery.FileFinder(path,
(machinery.SourceFileLoader,
machinery.SOURCE_SUFFIXES,
True),
machinery.SOURCE_SUFFIXES),
(machinery.SourcelessFileLoader,
machinery.BYTECODE_SUFFIXES,
True))
machinery.BYTECODE_SUFFIXES))
return finder.find_module(self.name)
def sensitivity_test(self):

View File

@ -37,9 +37,9 @@ class FinderTests(abc.FinderTests):
def import_(self, root, module):
loader_details = [(machinery.SourceFileLoader,
machinery.SOURCE_SUFFIXES, True),
machinery.SOURCE_SUFFIXES),
(machinery.SourcelessFileLoader,
machinery.BYTECODE_SUFFIXES, True)]
machinery.BYTECODE_SUFFIXES)]
finder = machinery.FileFinder(root, *loader_details)
return finder.find_module(module)
@ -120,7 +120,7 @@ class FinderTests(abc.FinderTests):
def test_empty_string_for_dir(self):
# The empty string from sys.path means to search in the cwd.
finder = machinery.FileFinder('', (machinery.SourceFileLoader,
machinery.SOURCE_SUFFIXES, True))
machinery.SOURCE_SUFFIXES))
with open('mod.py', 'w') as file:
file.write("# test file for importlib")
try:
@ -132,7 +132,7 @@ class FinderTests(abc.FinderTests):
def test_invalidate_caches(self):
# invalidate_caches() should reset the mtime.
finder = machinery.FileFinder('', (machinery.SourceFileLoader,
machinery.SOURCE_SUFFIXES, True))
machinery.SOURCE_SUFFIXES))
finder._path_mtime = 42
finder.invalidate_caches()
self.assertEqual(finder._path_mtime, -1)

View File

@ -11,7 +11,7 @@ class PathHookTest(unittest.TestCase):
def path_hook(self):
return machinery.FileFinder.path_hook((machinery.SourceFileLoader,
machinery.SOURCE_SUFFIXES, True))
machinery.SOURCE_SUFFIXES))
def test_success(self):
with source_util.create_modules('dummy') as mapping:

View File

@ -80,6 +80,8 @@ Core and Builtins
Library
-------
- Issue #15576: Allow extension modules to act as a package's __init__ module.
- Issue #15502: Have importlib.invalidate_caches() work on sys.meta_path
instead of sys.path_importer_cache.

File diff suppressed because it is too large Load Diff