Issue #14285: Merge runpy fix from 3.5

This commit is contained in:
Martin Panter 2015-12-11 03:35:31 +00:00
commit 8c0b5b998a
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 import mechanism (refer to :pep:`302` for details) and then executed in a
fresh module namespace. 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 module, then that package is imported and the ``__main__`` submodule within
that package is then executed and the resulting module globals dictionary that package is then executed and the resulting module globals dictionary
returned. returned.

View File

@ -77,7 +77,7 @@ source.
the :mod:`__main__` module. the :mod:`__main__` module.
Since the argument is a *module* name, you must not give a file extension 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 the implementation may not always enforce this (e.g. it may allow you to
use a name that includes a hyphen). 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 # Helper to get the loader, code and filename for a module
def _get_module_details(mod_name, error=ImportError): 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: try:
spec = importlib.util.find_spec(mod_name) spec = importlib.util.find_spec(mod_name)
except (ImportError, AttributeError, TypeError, ValueError) as ex: 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 # importlib, where the latter raises other errors for cases where
# pkgutil previously raised ImportError # pkgutil previously raised ImportError
msg = "Error while finding spec for {!r} ({}: {})" 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: if spec is None:
raise error("No module named %s" % mod_name) raise error("No module named %s" % mod_name)
if spec.submodule_search_locations is not None: if spec.submodule_search_locations is not None:
if mod_name == "__main__" or mod_name.endswith(".__main__"): if mod_name == "__main__" or mod_name.endswith(".__main__"):
raise error("Cannot use package as __main__ module") raise error("Cannot use package as __main__ module")
__import__(mod_name) # Do not catch exceptions initializing package
try: try:
pkg_main_name = mod_name + ".__main__" pkg_main_name = mod_name + ".__main__"
return _get_module_details(pkg_main_name) return _get_module_details(pkg_main_name, error)
except ImportError as e: except error as e:
raise error(("%s; %r is a package and cannot " + raise error(("%s; %r is a package and cannot " +
"be directly executed") %(e, mod_name)) "be directly executed") %(e, mod_name))
loader = spec.loader loader = spec.loader

View File

@ -433,6 +433,7 @@ class CmdLineTest(unittest.TestCase):
('importlib', br'No module named.*' ('importlib', br'No module named.*'
br'is a package and cannot be directly executed'), br'is a package and cannot be directly executed'),
('importlib.nonexistant', br'No module named'), ('importlib.nonexistant', br'No module named'),
('.unittest', br'Relative module names not supported'),
) )
for name, regex in tests: for name, regex in tests:
with self.subTest(name): 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("sys.imp.eric")
self.expect_import_error("os.path.half") self.expect_import_error("os.path.half")
self.expect_import_error("a.bee") self.expect_import_error("a.bee")
# Relative names not allowed
self.expect_import_error(".howard") self.expect_import_error(".howard")
self.expect_import_error("..eaten") self.expect_import_error("..eaten")
self.expect_import_error(".test_runpy")
self.expect_import_error(".unittest")
# Package without __main__.py # Package without __main__.py
self.expect_import_error("multiprocessing") self.expect_import_error("multiprocessing")
@ -460,6 +463,12 @@ from ..uncle.cousin import nephew
self.assertNotIn("finding spec", format(err)) self.assertNotIn("finding spec", format(err))
else: else:
self.fail("Nothing raised; expected {}".format(name)) 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): def test_run_package_in_namespace_package(self):
for depth in range(1, 4): for depth in range(1, 4):