Skip to content

Commit

Permalink
Issue #15576: Allow extension modules to be a package's __init__
Browse files Browse the repository at this point in the history
module again. Also took the opportunity to stop accidentally exporting
_imp.extension_suffixes() as public.
  • Loading branch information
brettcannon committed Aug 10, 2012
1 parent 4425502 commit 83d1e49
Show file tree
Hide file tree
Showing 13 changed files with 3,676 additions and 3,664 deletions.
8 changes: 4 additions & 4 deletions Doc/library/importlib.rst
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions Lib/imp.py
Expand Up @@ -9,7 +9,7 @@
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
Expand Down Expand Up @@ -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]

Expand Down
38 changes: 23 additions & 15 deletions Lib/importlib/_bootstrap.py
Expand Up @@ -1067,6 +1067,10 @@ def get_source(self, fullname):
return None


# Filled in by _setup().
EXTENSION_SUFFIXES = []


class ExtensionFileLoader:

"""Loader for extension modules.
Expand All @@ -1089,6 +1093,8 @@ def load_module(self, fullname):
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:
Expand All @@ -1097,7 +1103,12 @@ def load_module(self, fullname):

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."""
Expand Down Expand Up @@ -1283,14 +1294,10 @@ def __init__(self, path, *details):
"""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
Expand Down Expand Up @@ -1336,7 +1343,7 @@ def find_loader(self, fullname):
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):
Expand All @@ -1346,7 +1353,7 @@ def find_loader(self, fullname):
# 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):
Expand Down Expand Up @@ -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]


Expand Down Expand Up @@ -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


Expand Down
4 changes: 2 additions & 2 deletions Lib/importlib/machinery.py
Expand Up @@ -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
Expand All @@ -13,7 +14,6 @@
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"""
Expand Down
3 changes: 1 addition & 2 deletions Lib/test/test_importlib/extension/test_case_sensitivity.py
Expand Up @@ -16,8 +16,7 @@ def find_module(self):
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):
Expand Down
16 changes: 6 additions & 10 deletions Lib/test/test_importlib/extension/test_finder.py
@@ -1,34 +1,32 @@
from importlib import _bootstrap
from importlib import machinery
from .. import abc
from . import util

import imp
import unittest

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):
# No extension module in a package available for testing.
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):
Expand All @@ -38,8 +36,6 @@ def test_package_over_module(self):
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
Expand Down
12 changes: 10 additions & 2 deletions Lib/test/test_importlib/extension/test_loader.py
Expand Up @@ -3,6 +3,7 @@
from .. import abc
from .. import util

import os.path
import sys
import unittest

Expand Down Expand Up @@ -38,11 +39,11 @@ def test_module(self):
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):
Expand All @@ -61,6 +62,13 @@ def test_unloadable(self):
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
Expand Down
6 changes: 3 additions & 3 deletions Lib/test/test_importlib/extension/test_path_hook.py
@@ -1,4 +1,4 @@
from importlib import _bootstrap
from importlib import machinery
from . import util

import collections
Expand All @@ -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
Expand Down
6 changes: 2 additions & 4 deletions Lib/test/test_importlib/source/test_case_sensitivity.py
Expand Up @@ -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):
Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_importlib/source/test_finder.py
Expand Up @@ -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)

Expand Down Expand Up @@ -120,7 +120,7 @@ def test_failure(self):
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:
Expand All @@ -132,7 +132,7 @@ def test_empty_string_for_dir(self):
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)
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_importlib/source/test_path_hook.py
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions Misc/NEWS
Expand Up @@ -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.

Expand Down

0 comments on commit 83d1e49

Please sign in to comment.