Take the first step in resolving the messy pkgutil vs importlib edge cases by basing pkgutil explicitly on importlib, deprecating its internal import emulation and setting __main__.__loader__ correctly so that runpy still works (Affects #15343, #15314, #15357)

This commit is contained in:
Nick Coghlan 2012-07-15 18:09:52 +10:00
parent f96cf911a0
commit 85e729ec3b
7 changed files with 260 additions and 142 deletions

View File

@ -56,21 +56,32 @@ support.
Note that :class:`ImpImporter` does not currently support being used by Note that :class:`ImpImporter` does not currently support being used by
placement on :data:`sys.meta_path`. placement on :data:`sys.meta_path`.
.. deprecated:: 3.3
This emulation is no longer needed, as the standard import mechanism
is now fully PEP 302 compliant and available in :mod:`importlib`
.. class:: ImpLoader(fullname, file, filename, etc) .. class:: ImpLoader(fullname, file, filename, etc)
:pep:`302` Loader that wraps Python's "classic" import algorithm. :pep:`302` Loader that wraps Python's "classic" import algorithm.
.. deprecated:: 3.3
This emulation is no longer needed, as the standard import mechanism
is now fully PEP 302 compliant and available in :mod:`importlib`
.. function:: find_loader(fullname) .. function:: find_loader(fullname)
Find a :pep:`302` "loader" object for *fullname*. Retrieve a :pep:`302` module loader for the given *fullname*.
If *fullname* contains dots, path must be the containing package's This is a convenience wrapper around :func:`importlib.find_loader` that
``__path__``. Returns ``None`` if the module cannot be found or imported. sets the *path* argument correctly when searching for submodules, and
This function uses :func:`iter_importers`, and is thus subject to the same also ensures parent packages (if any) are imported before searching for
limitations regarding platform-specific special import locations such as the submodules.
Windows registry.
.. versionchanged:: 3.3
Updated to be based directly on :mod:`importlib` rather than relying
on a package internal PEP 302 import emulation.
.. function:: get_importer(path_item) .. function:: get_importer(path_item)
@ -80,13 +91,13 @@ support.
The returned importer is cached in :data:`sys.path_importer_cache` if it was The returned importer is cached in :data:`sys.path_importer_cache` if it was
newly created by a path hook. newly created by a path hook.
If there is no importer, a wrapper around the basic import machinery is
returned. This wrapper is never inserted into the importer cache (``None``
is inserted instead).
The cache (or part of it) can be cleared manually if a rescan of The cache (or part of it) can be cleared manually if a rescan of
:data:`sys.path_hooks` is necessary. :data:`sys.path_hooks` is necessary.
.. versionchanged:: 3.3
Updated to be based directly on :mod:`importlib` rather than relying
on a package internal PEP 302 import emulation.
.. function:: get_loader(module_or_name) .. function:: get_loader(module_or_name)
@ -102,31 +113,27 @@ support.
limitations regarding platform-specific special import locations such as the limitations regarding platform-specific special import locations such as the
Windows registry. Windows registry.
.. versionchanged:: 3.3
Updated to be based directly on :mod:`importlib` rather than relying
on a package internal PEP 302 import emulation.
.. function:: iter_importers(fullname='') .. function:: iter_importers(fullname='')
Yield :pep:`302` importers for the given module name. Yield :pep:`302` importers for the given module name.
If fullname contains a '.', the importers will be for the package containing If fullname contains a '.', the importers will be for the package
fullname, otherwise they will be importers for :data:`sys.meta_path`, containing fullname, otherwise they will be all registered top level
:data:`sys.path`, and Python's "classic" import machinery, in that order. If importers (i.e. those on both sys.meta_path and sys.path_hooks).
the named module is in a package, that package is imported as a side effect
of invoking this function.
Non-:pep:`302` mechanisms (e.g. the Windows registry) used by the standard If the named module is in a package, that package is imported as a side
import machinery to find files in alternative locations are partially effect of invoking this function.
supported, but are searched *after* :data:`sys.path`. Normally, these
locations are searched *before* :data:`sys.path`, preventing :data:`sys.path`
entries from shadowing them.
For this to cause a visible difference in behaviour, there must be a module If no module name is specified, all top level importers are produced.
or package name that is accessible via both :data:`sys.path` and one of the
non-:pep:`302` file system mechanisms. In this case, the emulation will find
the former version, while the builtin import mechanism will find the latter.
Items of the following types can be affected by this discrepancy: .. versionchanged:: 3.3
``imp.C_EXTENSION``, ``imp.PY_SOURCE``, ``imp.PY_COMPILED``, Updated to be based directly on :mod:`importlib` rather than relying
``imp.PKG_DIRECTORY``. on a package internal PEP 302 import emulation.
.. function:: iter_modules(path=None, prefix='') .. function:: iter_modules(path=None, prefix='')

View File

@ -3,7 +3,9 @@
import os import os
import sys import sys
import imp import imp
import importlib
import os.path import os.path
from warnings import warn
from types import ModuleType from types import ModuleType
__all__ = [ __all__ = [
@ -168,6 +170,8 @@ class ImpImporter:
""" """
def __init__(self, path=None): def __init__(self, path=None):
warn("This emulation is deprecated, use 'importlib' instead",
DeprecationWarning)
self.path = path self.path = path
def find_module(self, fullname, path=None): def find_module(self, fullname, path=None):
@ -232,6 +236,8 @@ class ImpLoader:
code = source = None code = source = None
def __init__(self, fullname, file, filename, etc): def __init__(self, fullname, file, filename, etc):
warn("This emulation is deprecated, use 'importlib' instead",
DeprecationWarning)
self.file = file self.file = file
self.filename = filename self.filename = filename
self.fullname = fullname self.fullname = fullname
@ -366,10 +372,6 @@ def get_importer(path_item):
The returned importer is cached in sys.path_importer_cache The returned importer is cached in sys.path_importer_cache
if it was newly created by a path hook. if it was newly created by a path hook.
If there is no importer, a wrapper around the basic import
machinery is returned. This wrapper is never inserted into
the importer cache (None is inserted instead).
The cache (or part of it) can be cleared manually if a The cache (or part of it) can be cleared manually if a
rescan of sys.path_hooks is necessary. rescan of sys.path_hooks is necessary.
""" """
@ -384,10 +386,7 @@ def get_importer(path_item):
except ImportError: except ImportError:
pass pass
else: else:
try: importer = None
importer = ImpImporter(path_item)
except ImportError:
importer = None
return importer return importer
@ -395,55 +394,37 @@ def iter_importers(fullname=""):
"""Yield PEP 302 importers for the given module name """Yield PEP 302 importers for the given module name
If fullname contains a '.', the importers will be for the package If fullname contains a '.', the importers will be for the package
containing fullname, otherwise they will be importers for sys.meta_path, containing fullname, otherwise they will be all registered top level
sys.path, and Python's "classic" import machinery, in that order. If importers (i.e. those on both sys.meta_path and sys.path_hooks).
the named module is in a package, that package is imported as a side
If the named module is in a package, that package is imported as a side
effect of invoking this function. effect of invoking this function.
Non PEP 302 mechanisms (e.g. the Windows registry) used by the If no module name is specified, all top level importers are produced.
standard import machinery to find files in alternative locations
are partially supported, but are searched AFTER sys.path. Normally,
these locations are searched BEFORE sys.path, preventing sys.path
entries from shadowing them.
For this to cause a visible difference in behaviour, there must
be a module or package name that is accessible via both sys.path
and one of the non PEP 302 file system mechanisms. In this case,
the emulation will find the former version, while the builtin
import mechanism will find the latter.
Items of the following types can be affected by this discrepancy:
imp.C_EXTENSION, imp.PY_SOURCE, imp.PY_COMPILED, imp.PKG_DIRECTORY
""" """
if fullname.startswith('.'): if fullname.startswith('.'):
raise ImportError("Relative module names not supported") msg = "Relative module name {!r} not supported".format(fullname)
raise ImportError(msg)
if '.' in fullname: if '.' in fullname:
# Get the containing package's __path__ # Get the containing package's __path__
pkg = '.'.join(fullname.split('.')[:-1]) pkg_name = fullname.rpartition(".")[0]
if pkg not in sys.modules: pkg = importlib.import_module(pkg)
__import__(pkg) path = getattr(sys.modules[pkg], '__path__', None)
path = getattr(sys.modules[pkg], '__path__', None) or [] if path is None:
return
else: else:
for importer in sys.meta_path: for importer in sys.meta_path:
yield importer yield importer
path = sys.path path = sys.path
for item in path: for item in path:
yield get_importer(item) yield get_importer(item)
if '.' not in fullname:
yield ImpImporter()
def get_loader(module_or_name): def get_loader(module_or_name):
"""Get a PEP 302 "loader" object for module_or_name """Get a PEP 302 "loader" object for module_or_name
If the module or package is accessible via the normal import Returns None if the module cannot be found or imported.
mechanism, a wrapper around the relevant part of that machinery
is returned. Returns None if the module cannot be found or imported.
If the named module is not already imported, its containing package If the named module is not already imported, its containing package
(if any) is imported, in order to establish the package __path__. (if any) is imported, in order to establish the package __path__.
This function uses iter_importers(), and is thus subject to the same
limitations regarding platform-specific special import locations such
as the Windows registry.
""" """
if module_or_name in sys.modules: if module_or_name in sys.modules:
module_or_name = sys.modules[module_or_name] module_or_name = sys.modules[module_or_name]
@ -457,22 +438,33 @@ def get_loader(module_or_name):
fullname = module_or_name fullname = module_or_name
return find_loader(fullname) return find_loader(fullname)
def find_loader(fullname): def find_loader(fullname):
"""Find a PEP 302 "loader" object for fullname """Find a PEP 302 "loader" object for fullname
If fullname contains dots, path must be the containing package's __path__. This is s convenience wrapper around :func:`importlib.find_loader` that
Returns None if the module cannot be found or imported. This function uses sets the *path* argument correctly when searching for submodules, and
iter_importers(), and is thus subject to the same limitations regarding also ensures parent packages (if any) are imported before searching for
platform-specific special import locations such as the Windows registry. submodules.
""" """
for importer in iter_importers(fullname): if fullname.startswith('.'):
if importer is None: msg = "Relative module name {!r} not supported".format(fullname)
continue raise ImportError(msg)
loader = importer.find_module(fullname) path = None
if loader is not None: pkg_name = fullname.rpartition(".")[0]
return loader if pkg_name:
pkg = importlib.import_module(pkg_name)
return None path = getattr(pkg, "__path__", None)
if path is None:
return None
try:
return importlib.find_loader(fullname, path)
except (ImportError, AttributeError, TypeError, ValueError) as ex:
# This hack fixes an impedance mismatch between pkgutil and
# importlib, where the latter throws other errors for cases where
# pkgutil previously threw ImportError
msg = "Error while finding loader for {!r} ({}: {})"
raise ImportError(msg.format(fullname, type(ex), ex)) from ex
def extend_path(path, name): def extend_path(path, name):

View File

@ -13,11 +13,8 @@ importers when locating support scripts as well as when importing modules.
import os import os
import sys import sys
import imp import imp
from pkgutil import read_code import importlib.machinery
try: from pkgutil import read_code, get_loader, get_importer
from imp import get_loader
except ImportError:
from pkgutil import get_loader
__all__ = [ __all__ = [
"run_module", "run_path", "run_module", "run_path",
@ -154,6 +151,7 @@ def _run_module_as_main(mod_name, alter_argv=True):
# know what the code was looking for # know what the code was looking for
info = "can't find '__main__' module in %r" % sys.argv[0] info = "can't find '__main__' module in %r" % sys.argv[0]
msg = "%s: %s" % (sys.executable, info) msg = "%s: %s" % (sys.executable, info)
raise
sys.exit(msg) sys.exit(msg)
pkg_name = mod_name.rpartition('.')[0] pkg_name = mod_name.rpartition('.')[0]
main_globals = sys.modules["__main__"].__dict__ main_globals = sys.modules["__main__"].__dict__
@ -183,36 +181,23 @@ def run_module(mod_name, init_globals=None,
def _get_main_module_details(): def _get_main_module_details():
# Helper that gives a nicer error message when attempting to # Helper that gives a nicer error message when attempting to
# execute a zipfile or directory by invoking __main__.py # execute a zipfile or directory by invoking __main__.py
# Also moves the standard __main__ out of the way so that the
# preexisting __loader__ entry doesn't cause issues
main_name = "__main__" main_name = "__main__"
saved_main = sys.modules[main_name]
del sys.modules[main_name]
try: try:
return _get_module_details(main_name) return _get_module_details(main_name)
except ImportError as exc: except ImportError as exc:
if main_name in str(exc): if main_name in str(exc):
raise ImportError("can't find %r module in %r" % raise ImportError("can't find %r module in %r" %
(main_name, sys.path[0])) (main_name, sys.path[0])) from exc
raise raise
finally:
sys.modules[main_name] = saved_main
# XXX (ncoghlan): Perhaps expose the C API function def _get_code_from_file(run_name, fname):
# as imp.get_importer instead of reimplementing it in Python?
def _get_importer(path_name):
"""Python version of PyImport_GetImporter C API function"""
cache = sys.path_importer_cache
try:
importer = cache[path_name]
except KeyError:
for hook in sys.path_hooks:
try:
importer = hook(path_name)
break
except ImportError:
pass
else:
importer = None
cache[path_name] = importer
return importer
def _get_code_from_file(fname):
# Check for a compiled file first # Check for a compiled file first
with open(fname, "rb") as f: with open(fname, "rb") as f:
code = read_code(f) code = read_code(f)
@ -220,7 +205,10 @@ def _get_code_from_file(fname):
# That didn't work, so try it as normal source code # That didn't work, so try it as normal source code
with open(fname, "rb") as f: with open(fname, "rb") as f:
code = compile(f.read(), fname, 'exec') code = compile(f.read(), fname, 'exec')
return code loader = importlib.machinery.SourceFileLoader(run_name, fname)
else:
loader = importlib.machinery.SourcelessFileLoader(run_name, fname)
return code, loader
def run_path(path_name, init_globals=None, run_name=None): def run_path(path_name, init_globals=None, run_name=None):
"""Execute code located at the specified filesystem location """Execute code located at the specified filesystem location
@ -235,13 +223,13 @@ def run_path(path_name, init_globals=None, run_name=None):
if run_name is None: if run_name is None:
run_name = "<run_path>" run_name = "<run_path>"
pkg_name = run_name.rpartition(".")[0] pkg_name = run_name.rpartition(".")[0]
importer = _get_importer(path_name) importer = get_importer(path_name)
if isinstance(importer, (type(None), imp.NullImporter)): if isinstance(importer, (type(None), imp.NullImporter)):
# Not a valid sys.path entry, so run the code directly # Not a valid sys.path entry, so run the code directly
# execfile() doesn't help as we want to allow compiled files # execfile() doesn't help as we want to allow compiled files
code = _get_code_from_file(path_name) code, mod_loader = _get_code_from_file(run_name, path_name)
return _run_module_code(code, init_globals, run_name, path_name, return _run_module_code(code, init_globals, run_name, path_name,
pkg_name=pkg_name) mod_loader, pkg_name)
else: else:
# Importer is defined for path, so add it to # Importer is defined for path, so add it to
# the start of sys.path # the start of sys.path
@ -253,13 +241,7 @@ def run_path(path_name, init_globals=None, run_name=None):
# have no choice and we have to remove it even while we read the # have no choice and we have to remove it even while we read the
# code. If we don't do this, a __loader__ attribute in the # code. If we don't do this, a __loader__ attribute in the
# existing __main__ module may prevent location of the new module. # existing __main__ module may prevent location of the new module.
main_name = "__main__" mod_name, loader, code, fname = _get_main_module_details()
saved_main = sys.modules[main_name]
del sys.modules[main_name]
try:
mod_name, loader, code, fname = _get_main_module_details()
finally:
sys.modules[main_name] = saved_main
with _TempModule(run_name) as temp_module, \ with _TempModule(run_name) as temp_module, \
_ModifiedArgv0(path_name): _ModifiedArgv0(path_name):
mod_globals = temp_module.module.__dict__ mod_globals = temp_module.module.__dict__

View File

@ -1,6 +1,8 @@
# tests command line execution of scripts # tests command line execution of scripts
import importlib import importlib
import importlib.machinery
import zipimport
import unittest import unittest
import sys import sys
import os import os
@ -11,7 +13,8 @@ import textwrap
from test import support from test import support
from test.script_helper import ( from test.script_helper import (
make_pkg, make_script, make_zip_pkg, make_zip_script, make_pkg, make_script, make_zip_pkg, make_zip_script,
assert_python_ok, assert_python_failure, temp_dir) assert_python_ok, assert_python_failure, temp_dir,
spawn_python, kill_python)
verbose = support.verbose verbose = support.verbose
@ -34,6 +37,8 @@ f()
assertEqual(result, ['Top level assignment', 'Lower level reference']) assertEqual(result, ['Top level assignment', 'Lower level reference'])
# Check population of magic variables # Check population of magic variables
assertEqual(__name__, '__main__') assertEqual(__name__, '__main__')
_loader = __loader__ if isinstance(__loader__, type) else type(__loader__)
print('__loader__==%a' % _loader)
print('__file__==%a' % __file__) print('__file__==%a' % __file__)
assertEqual(__cached__, None) assertEqual(__cached__, None)
print('__package__==%r' % __package__) print('__package__==%r' % __package__)
@ -85,11 +90,13 @@ def _make_launch_script(script_dir, script_basename, module_name, path=None):
class CmdLineTest(unittest.TestCase): class CmdLineTest(unittest.TestCase):
def _check_output(self, script_name, exit_code, data, def _check_output(self, script_name, exit_code, data,
expected_file, expected_argv0, expected_file, expected_argv0,
expected_path0, expected_package): expected_path0, expected_package,
expected_loader):
if verbose > 1: if verbose > 1:
print("Output from test script %r:" % script_name) print("Output from test script %r:" % script_name)
print(data) print(data)
self.assertEqual(exit_code, 0) self.assertEqual(exit_code, 0)
printed_loader = '__loader__==%a' % expected_loader
printed_file = '__file__==%a' % expected_file printed_file = '__file__==%a' % expected_file
printed_package = '__package__==%r' % expected_package printed_package = '__package__==%r' % expected_package
printed_argv0 = 'sys.argv[0]==%a' % expected_argv0 printed_argv0 = 'sys.argv[0]==%a' % expected_argv0
@ -101,6 +108,7 @@ class CmdLineTest(unittest.TestCase):
print(printed_package) print(printed_package)
print(printed_argv0) print(printed_argv0)
print(printed_cwd) print(printed_cwd)
self.assertIn(printed_loader.encode('utf-8'), data)
self.assertIn(printed_file.encode('utf-8'), data) self.assertIn(printed_file.encode('utf-8'), data)
self.assertIn(printed_package.encode('utf-8'), data) self.assertIn(printed_package.encode('utf-8'), data)
self.assertIn(printed_argv0.encode('utf-8'), data) self.assertIn(printed_argv0.encode('utf-8'), data)
@ -109,14 +117,15 @@ class CmdLineTest(unittest.TestCase):
def _check_script(self, script_name, expected_file, def _check_script(self, script_name, expected_file,
expected_argv0, expected_path0, expected_argv0, expected_path0,
expected_package, expected_package, expected_loader,
*cmd_line_switches): *cmd_line_switches):
if not __debug__: if not __debug__:
cmd_line_switches += ('-' + 'O' * sys.flags.optimize,) cmd_line_switches += ('-' + 'O' * sys.flags.optimize,)
run_args = cmd_line_switches + (script_name,) + tuple(example_args) run_args = cmd_line_switches + (script_name,) + tuple(example_args)
rc, out, err = assert_python_ok(*run_args) rc, out, err = assert_python_ok(*run_args)
self._check_output(script_name, rc, out + err, expected_file, self._check_output(script_name, rc, out + err, expected_file,
expected_argv0, expected_path0, expected_package) expected_argv0, expected_path0,
expected_package, expected_loader)
def _check_import_error(self, script_name, expected_msg, def _check_import_error(self, script_name, expected_msg,
*cmd_line_switches): *cmd_line_switches):
@ -128,11 +137,27 @@ class CmdLineTest(unittest.TestCase):
print('Expected output: %r' % expected_msg) print('Expected output: %r' % expected_msg)
self.assertIn(expected_msg.encode('utf-8'), err) self.assertIn(expected_msg.encode('utf-8'), err)
def test_dash_c_loader(self):
rc, out, err = assert_python_ok("-c", "print(__loader__)")
expected = repr(importlib.machinery.BuiltinImporter).encode("utf-8")
self.assertIn(expected, out)
def test_stdin_loader(self):
p = spawn_python()
try:
p.stdin.write(b"print(__loader__)\n")
p.stdin.flush()
finally:
out = kill_python(p)
expected = repr(importlib.machinery.BuiltinImporter).encode("utf-8")
self.assertIn(expected, out)
def test_basic_script(self): def test_basic_script(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
script_name = _make_test_script(script_dir, 'script') script_name = _make_test_script(script_dir, 'script')
self._check_script(script_name, script_name, script_name, self._check_script(script_name, script_name, script_name,
script_dir, None) script_dir, None,
importlib.machinery.SourceFileLoader)
def test_script_compiled(self): def test_script_compiled(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
@ -141,13 +166,15 @@ class CmdLineTest(unittest.TestCase):
os.remove(script_name) os.remove(script_name)
pyc_file = support.make_legacy_pyc(script_name) pyc_file = support.make_legacy_pyc(script_name)
self._check_script(pyc_file, pyc_file, self._check_script(pyc_file, pyc_file,
pyc_file, script_dir, None) pyc_file, script_dir, None,
importlib.machinery.SourcelessFileLoader)
def test_directory(self): def test_directory(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
script_name = _make_test_script(script_dir, '__main__') script_name = _make_test_script(script_dir, '__main__')
self._check_script(script_dir, script_name, script_dir, self._check_script(script_dir, script_name, script_dir,
script_dir, '') script_dir, '',
importlib.machinery.SourceFileLoader)
def test_directory_compiled(self): def test_directory_compiled(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
@ -156,7 +183,8 @@ class CmdLineTest(unittest.TestCase):
os.remove(script_name) os.remove(script_name)
pyc_file = support.make_legacy_pyc(script_name) pyc_file = support.make_legacy_pyc(script_name)
self._check_script(script_dir, pyc_file, script_dir, self._check_script(script_dir, pyc_file, script_dir,
script_dir, '') script_dir, '',
importlib.machinery.SourcelessFileLoader)
def test_directory_error(self): def test_directory_error(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
@ -167,14 +195,16 @@ class CmdLineTest(unittest.TestCase):
with temp_dir() as script_dir: with temp_dir() as script_dir:
script_name = _make_test_script(script_dir, '__main__') script_name = _make_test_script(script_dir, '__main__')
zip_name, run_name = make_zip_script(script_dir, 'test_zip', script_name) zip_name, run_name = make_zip_script(script_dir, 'test_zip', script_name)
self._check_script(zip_name, run_name, zip_name, zip_name, '') self._check_script(zip_name, run_name, zip_name, zip_name, '',
zipimport.zipimporter)
def test_zipfile_compiled(self): def test_zipfile_compiled(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
script_name = _make_test_script(script_dir, '__main__') script_name = _make_test_script(script_dir, '__main__')
compiled_name = py_compile.compile(script_name, doraise=True) compiled_name = py_compile.compile(script_name, doraise=True)
zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name) zip_name, run_name = make_zip_script(script_dir, 'test_zip', compiled_name)
self._check_script(zip_name, run_name, zip_name, zip_name, '') self._check_script(zip_name, run_name, zip_name, zip_name, '',
zipimport.zipimporter)
def test_zipfile_error(self): def test_zipfile_error(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
@ -189,19 +219,24 @@ class CmdLineTest(unittest.TestCase):
make_pkg(pkg_dir) make_pkg(pkg_dir)
script_name = _make_test_script(pkg_dir, 'script') script_name = _make_test_script(pkg_dir, 'script')
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.script') launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.script')
self._check_script(launch_name, script_name, script_name, script_dir, 'test_pkg') self._check_script(launch_name, script_name, script_name,
script_dir, 'test_pkg',
importlib.machinery.SourceFileLoader)
def test_module_in_package_in_zipfile(self): def test_module_in_package_in_zipfile(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
zip_name, run_name = _make_test_zip_pkg(script_dir, 'test_zip', 'test_pkg', 'script') zip_name, run_name = _make_test_zip_pkg(script_dir, 'test_zip', 'test_pkg', 'script')
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.script', zip_name) launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.script', zip_name)
self._check_script(launch_name, run_name, run_name, zip_name, 'test_pkg') self._check_script(launch_name, run_name, run_name,
zip_name, 'test_pkg', zipimport.zipimporter)
def test_module_in_subpackage_in_zipfile(self): def test_module_in_subpackage_in_zipfile(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
zip_name, run_name = _make_test_zip_pkg(script_dir, 'test_zip', 'test_pkg', 'script', depth=2) zip_name, run_name = _make_test_zip_pkg(script_dir, 'test_zip', 'test_pkg', 'script', depth=2)
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.test_pkg.script', zip_name) launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.test_pkg.script', zip_name)
self._check_script(launch_name, run_name, run_name, zip_name, 'test_pkg.test_pkg') self._check_script(launch_name, run_name, run_name,
zip_name, 'test_pkg.test_pkg',
zipimport.zipimporter)
def test_package(self): def test_package(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
@ -210,7 +245,8 @@ class CmdLineTest(unittest.TestCase):
script_name = _make_test_script(pkg_dir, '__main__') script_name = _make_test_script(pkg_dir, '__main__')
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg') launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg')
self._check_script(launch_name, script_name, self._check_script(launch_name, script_name,
script_name, script_dir, 'test_pkg') script_name, script_dir, 'test_pkg',
importlib.machinery.SourceFileLoader)
def test_package_compiled(self): def test_package_compiled(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
@ -222,7 +258,8 @@ class CmdLineTest(unittest.TestCase):
pyc_file = support.make_legacy_pyc(script_name) pyc_file = support.make_legacy_pyc(script_name)
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg') launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg')
self._check_script(launch_name, pyc_file, self._check_script(launch_name, pyc_file,
pyc_file, script_dir, 'test_pkg') pyc_file, script_dir, 'test_pkg',
importlib.machinery.SourcelessFileLoader)
def test_package_error(self): def test_package_error(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:
@ -259,7 +296,8 @@ class CmdLineTest(unittest.TestCase):
expected = "init_argv0==%r" % '-m' expected = "init_argv0==%r" % '-m'
self.assertIn(expected.encode('utf-8'), out) self.assertIn(expected.encode('utf-8'), out)
self._check_output(script_name, rc, out, self._check_output(script_name, rc, out,
script_name, script_name, '', 'test_pkg') script_name, script_name, '', 'test_pkg',
importlib.machinery.SourceFileLoader)
def test_issue8202_dash_c_file_ignored(self): def test_issue8202_dash_c_file_ignored(self):
# Make sure a "-c" file in the current directory # Make sure a "-c" file in the current directory
@ -285,7 +323,8 @@ class CmdLineTest(unittest.TestCase):
f.write("data") f.write("data")
rc, out, err = assert_python_ok('-m', 'other', *example_args) rc, out, err = assert_python_ok('-m', 'other', *example_args)
self._check_output(script_name, rc, out, self._check_output(script_name, rc, out,
script_name, script_name, '', '') script_name, script_name, '', '',
importlib.machinery.SourceFileLoader)
def test_dash_m_error_code_is_one(self): def test_dash_m_error_code_is_one(self):
# If a module is invoked with the -m command line flag # If a module is invoked with the -m command line flag

View File

@ -1,4 +1,4 @@
from test.support import run_unittest, unload from test.support import run_unittest, unload, check_warnings
import unittest import unittest
import sys import sys
import imp import imp
@ -255,12 +255,51 @@ class NestedNamespacePackageTest(unittest.TestCase):
self.assertEqual(d, 2) self.assertEqual(d, 2)
class ImportlibMigrationTests(unittest.TestCase):
# With full PEP 302 support in the standard import machinery, the
# PEP 302 emulation in this module is in the process of being
# deprecated in favour of importlib proper
def check_deprecated(self):
return check_warnings(
("This emulation is deprecated, use 'importlib' instead",
DeprecationWarning))
def test_importer_deprecated(self):
with self.check_deprecated():
x = pkgutil.ImpImporter("")
def test_loader_deprecated(self):
with self.check_deprecated():
x = pkgutil.ImpLoader("", "", "", "")
def test_get_loader_avoids_emulation(self):
with check_warnings() as w:
self.assertIsNotNone(pkgutil.get_loader("sys"))
self.assertIsNotNone(pkgutil.get_loader("os"))
self.assertIsNotNone(pkgutil.get_loader("test.support"))
self.assertEqual(len(w.warnings), 0)
def test_get_importer_avoids_emulation(self):
with check_warnings() as w:
self.assertIsNotNone(pkgutil.get_importer(sys.path[0]))
self.assertEqual(len(w.warnings), 0)
def test_iter_importers_avoids_emulation(self):
with check_warnings() as w:
for importer in pkgutil.iter_importers(): pass
self.assertEqual(len(w.warnings), 0)
def test_main(): def test_main():
run_unittest(PkgutilTests, PkgutilPEP302Tests, ExtendPathTests, run_unittest(PkgutilTests, PkgutilPEP302Tests, ExtendPathTests,
NestedNamespacePackageTest) NestedNamespacePackageTest, ImportlibMigrationTests)
# this is necessary if test is run repeated (like when finding leaks) # this is necessary if test is run repeated (like when finding leaks)
import zipimport import zipimport
import importlib
zipimport._zip_directory_cache.clear() zipimport._zip_directory_cache.clear()
importlib.invalidate_caches()
if __name__ == '__main__': if __name__ == '__main__':
test_main() test_main()

View File

@ -10,6 +10,9 @@ What's New in Python 3.3.0 Beta 2?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #15314: __main__.__loader__ is now set correctly during
interpreter startup
- Issue #15111: When a module imported using 'from import' has an ImportError - Issue #15111: When a module imported using 'from import' has an ImportError
inside itself, don't mask that fact behind a generic ImportError for the inside itself, don't mask that fact behind a generic ImportError for the
module itself. module itself.
@ -31,10 +34,15 @@ Core and Builtins
- Issue #15229: An OSError subclass whose __init__ doesn't call back - Issue #15229: An OSError subclass whose __init__ doesn't call back
OSError.__init__ could produce incomplete instances, leading to crashes OSError.__init__ could produce incomplete instances, leading to crashes
when calling str() on them. when calling str() on them.
Library Library
------- -------
- Issue #15314: runpy now sets __main__.__loader__ correctly
- Issue #15357: The import emulation in pkgutil is now deprecated. pkgutil
uses importlib internally rather than the emulation
- Issue #15233: Python now guarantees that callables registered with - Issue #15233: Python now guarantees that callables registered with
the atexit module will be called in a deterministic order. the atexit module will be called in a deterministic order.

View File

@ -52,7 +52,7 @@ extern wchar_t *Py_GetPath(void);
extern grammar _PyParser_Grammar; /* From graminit.c */ extern grammar _PyParser_Grammar; /* From graminit.c */
/* Forward */ /* Forward */
static void initmain(void); static void initmain(PyInterpreterState *interp);
static int initfsencoding(PyInterpreterState *interp); static int initfsencoding(PyInterpreterState *interp);
static void initsite(void); static void initsite(void);
static int initstdio(void); static int initstdio(void);
@ -376,7 +376,7 @@ _Py_InitializeEx_Private(int install_sigs, int install_importlib)
if (install_sigs) if (install_sigs)
initsigs(); /* Signal handling stuff, including initintr() */ initsigs(); /* Signal handling stuff, including initintr() */
initmain(); /* Module __main__ */ initmain(interp); /* Module __main__ */
if (initstdio() < 0) if (initstdio() < 0)
Py_FatalError( Py_FatalError(
"Py_Initialize: can't initialize sys standard streams"); "Py_Initialize: can't initialize sys standard streams");
@ -728,7 +728,7 @@ Py_NewInterpreter(void)
if (initstdio() < 0) if (initstdio() < 0)
Py_FatalError( Py_FatalError(
"Py_Initialize: can't initialize sys standard streams"); "Py_Initialize: can't initialize sys standard streams");
initmain(); initmain(interp);
if (!Py_NoSiteFlag) if (!Py_NoSiteFlag)
initsite(); initsite();
} }
@ -825,7 +825,7 @@ Py_GetPythonHome(void)
/* Create __main__ module */ /* Create __main__ module */
static void static void
initmain(void) initmain(PyInterpreterState *interp)
{ {
PyObject *m, *d; PyObject *m, *d;
m = PyImport_AddModule("__main__"); m = PyImport_AddModule("__main__");
@ -834,11 +834,31 @@ initmain(void)
d = PyModule_GetDict(m); d = PyModule_GetDict(m);
if (PyDict_GetItemString(d, "__builtins__") == NULL) { if (PyDict_GetItemString(d, "__builtins__") == NULL) {
PyObject *bimod = PyImport_ImportModule("builtins"); PyObject *bimod = PyImport_ImportModule("builtins");
if (bimod == NULL || if (bimod == NULL) {
PyDict_SetItemString(d, "__builtins__", bimod) != 0) Py_FatalError("Failed to retrieve builtins module");
Py_FatalError("can't add __builtins__ to __main__"); }
if (PyDict_SetItemString(d, "__builtins__", bimod) < 0) {
Py_FatalError("Failed to initialize __main__.__builtins__");
}
Py_DECREF(bimod); Py_DECREF(bimod);
} }
/* Main is a little special - imp.is_builtin("__main__") will return
* False, but BuiltinImporter is still the most appropriate initial
* setting for its __loader__ attribute. A more suitable value will
* be set if __main__ gets further initialized later in the startup
* process.
*/
if (PyDict_GetItemString(d, "__loader__") == NULL) {
PyObject *loader = PyObject_GetAttrString(interp->importlib,
"BuiltinImporter");
if (loader == NULL) {
Py_FatalError("Failed to retrieve BuiltinImporter");
}
if (PyDict_SetItemString(d, "__loader__", loader) < 0) {
Py_FatalError("Failed to initialize __main__.__loader__");
}
Py_DECREF(loader);
}
} }
static int static int
@ -1330,6 +1350,24 @@ maybe_pyc_file(FILE *fp, const char* filename, const char* ext, int closeit)
return 0; return 0;
} }
int
set_main_loader(PyObject *d, const char *filename, const char *loader_name)
{
PyInterpreterState *interp;
PyThreadState *tstate;
PyObject *loader;
/* Get current thread state and interpreter pointer */
tstate = PyThreadState_GET();
interp = tstate->interp;
loader = PyObject_GetAttrString(interp->importlib, loader_name);
if (loader == NULL ||
(PyDict_SetItemString(d, "__loader__", loader) < 0)) {
return -1;
}
Py_DECREF(loader);
return 0;
}
int int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit, PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
PyCompilerFlags *flags) PyCompilerFlags *flags)
@ -1373,8 +1411,21 @@ PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
/* Turn on optimization if a .pyo file is given */ /* Turn on optimization if a .pyo file is given */
if (strcmp(ext, ".pyo") == 0) if (strcmp(ext, ".pyo") == 0)
Py_OptimizeFlag = 1; Py_OptimizeFlag = 1;
if (set_main_loader(d, filename, "SourcelessFileLoader") < 0) {
fprintf(stderr, "python: failed to set __main__.__loader__\n");
ret = -1;
goto done;
}
v = run_pyc_file(fp, filename, d, d, flags); v = run_pyc_file(fp, filename, d, d, flags);
} else { } else {
/* When running from stdin, leave __main__.__loader__ alone */
if (strcmp(filename, "<stdin>") != 0 &&
set_main_loader(d, filename, "SourceFileLoader") < 0) {
fprintf(stderr, "python: failed to set __main__.__loader__\n");
ret = -1;
goto done;
}
v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d, v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
closeit, flags); closeit, flags);
} }