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:
parent
a29eb08fb9
commit
7dda421bff
|
@ -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.
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
22
Lib/runpy.py
22
Lib/runpy.py
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue