# HG changeset patch # Parent ea576db138278c9f80fe2be213e9d19669a802eb 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. diff -r ea576db13827 Doc/library/runpy.rst --- a/Doc/library/runpy.rst Thu Dec 03 22:27:31 2015 +0200 +++ b/Doc/library/runpy.rst Sun Dec 06 23:23:35 2015 +0000 @@ -36,7 +36,8 @@ 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. diff -r ea576db13827 Doc/using/cmdline.rst --- a/Doc/using/cmdline.rst Thu Dec 03 22:27:31 2015 +0200 +++ b/Doc/using/cmdline.rst Sun Dec 06 23:23:35 2015 +0000 @@ -77,7 +77,7 @@ 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). diff -r ea576db13827 Lib/runpy.py --- a/Lib/runpy.py Thu Dec 03 22:27:31 2015 +0200 +++ b/Lib/runpy.py Sun Dec 06 23:23:35 2015 +0000 @@ -100,6 +100,22 @@ # 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 err: + # If the parent or higher ancestor package is missing, this is an + # error that we may want to catch. But do not allow other errors + # to be caught. + if err.name is None or (err.name != pkg_name and + not pkg_name.startswith(err.name + ".")): + raise + raise error(format(err)) + try: spec = importlib.util.find_spec(mod_name) except (ImportError, AttributeError, TypeError, ValueError) as ex: @@ -107,17 +123,16 @@ # 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 diff -r ea576db13827 Lib/test/test_cmd_line_script.py --- a/Lib/test/test_cmd_line_script.py Thu Dec 03 22:27:31 2015 +0200 +++ b/Lib/test/test_cmd_line_script.py Sun Dec 06 23:23:35 2015 +0000 @@ -427,12 +427,12 @@ tests = ( ('builtins', br'No code object available'), ('builtins.x', br'Error while finding spec.*AttributeError'), - ('builtins.x.y', br'Error while finding spec.*' - br'ImportError.*No module named.*not a package'), + ('builtins.x.y', br'No module named.*not a package'), ('os.path', br'loader.*cannot handle'), ('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): diff -r ea576db13827 Lib/test/test_runpy.py --- a/Lib/test/test_runpy.py Thu Dec 03 22:27:31 2015 +0200 +++ b/Lib/test/test_runpy.py Sun Dec 06 23:23:35 2015 +0000 @@ -197,8 +197,11 @@ 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 @@ 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):