Issue #14285: Do not catch exceptions initializing any ancestor package

The previous fix only handled the case of the parent package of __main__
failing to initialize.

Also make the "Error while finding spec" formatting slightly more appealing,
and document and test that the module name must be absolute.
This commit is contained in:
Martin Panter 2015-12-10 06:47:06 +00:00
parent a29eb08fb9
commit 7dda421bff
5 changed files with 31 additions and 6 deletions

View File

@ -36,7 +36,8 @@ The :mod:`runpy` module provides two functions:
import mechanism (refer to :pep:`302` for details) and then executed in a
fresh module namespace.
If the supplied module name refers to a package rather than a normal
The *mod_name* argument should be an absolute module name.
If the module name refers to a package rather than a normal
module, then that package is imported and the ``__main__`` submodule within
that package is then executed and the resulting module globals dictionary
returned.

View File

@ -77,7 +77,7 @@ source.
the :mod:`__main__` module.
Since the argument is a *module* name, you must not give a file extension
(``.py``). The ``module-name`` should be a valid Python module name, but
(``.py``). The module name should be a valid absolute Python module name, but
the implementation may not always enforce this (e.g. it may allow you to
use a name that includes a hyphen).

View File

@ -100,6 +100,21 @@ def _run_module_code(code, init_globals=None,
# Helper to get the loader, code and filename for a module
def _get_module_details(mod_name, error=ImportError):
if mod_name.startswith("."):
raise error("Relative module names not supported")
pkg_name, _, _ = mod_name.rpartition(".")
if pkg_name:
# Try importing the parent to avoid catching initialization errors
try:
__import__(pkg_name)
except ImportError as e:
# If the parent or higher ancestor package is missing, let the
# error be raised by find_spec() below and then be caught. But do
# not allow other errors to be caught.
if e.name is None or (e.name != pkg_name and
not pkg_name.startswith(e.name + ".")):
raise
try:
spec = importlib.util.find_spec(mod_name)
except (ImportError, AttributeError, TypeError, ValueError) as ex:
@ -107,17 +122,16 @@ def _get_module_details(mod_name, error=ImportError):
# importlib, where the latter raises other errors for cases where
# pkgutil previously raised ImportError
msg = "Error while finding spec for {!r} ({}: {})"
raise error(msg.format(mod_name, type(ex), ex)) from ex
raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
if spec is None:
raise error("No module named %s" % mod_name)
if spec.submodule_search_locations is not None:
if mod_name == "__main__" or mod_name.endswith(".__main__"):
raise error("Cannot use package as __main__ module")
__import__(mod_name) # Do not catch exceptions initializing package
try:
pkg_main_name = mod_name + ".__main__"
return _get_module_details(pkg_main_name)
except ImportError as e:
return _get_module_details(pkg_main_name, error)
except error as e:
raise error(("%s; %r is a package and cannot " +
"be directly executed") %(e, mod_name))
loader = spec.loader

View File

@ -433,6 +433,7 @@ class CmdLineTest(unittest.TestCase):
('importlib', br'No module named.*'
br'is a package and cannot be directly executed'),
('importlib.nonexistant', br'No module named'),
('.unittest', br'Relative module names not supported'),
)
for name, regex in tests:
with self.subTest(name):

View File

@ -197,8 +197,11 @@ class RunModuleTestCase(unittest.TestCase, CodeExecutionMixin):
self.expect_import_error("sys.imp.eric")
self.expect_import_error("os.path.half")
self.expect_import_error("a.bee")
# Relative names not allowed
self.expect_import_error(".howard")
self.expect_import_error("..eaten")
self.expect_import_error(".test_runpy")
self.expect_import_error(".unittest")
# Package without __main__.py
self.expect_import_error("multiprocessing")
@ -460,6 +463,12 @@ from ..uncle.cousin import nephew
self.assertNotIn("finding spec", format(err))
else:
self.fail("Nothing raised; expected {}".format(name))
try:
run_module(mod_name + ".submodule")
except exception as err:
self.assertNotIn("finding spec", format(err))
else:
self.fail("Nothing raised; expected {}".format(name))
def test_run_package_in_namespace_package(self):
for depth in range(1, 4):