]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #14285: Do not catch exceptions initializing any ancestor package
authorMartin Panter <vadmium+py@gmail.com>
Thu, 10 Dec 2015 06:47:06 +0000 (06:47 +0000)
committerMartin Panter <vadmium+py@gmail.com>
Thu, 10 Dec 2015 06:47:06 +0000 (06:47 +0000)
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.

Doc/library/runpy.rst
Doc/using/cmdline.rst
Lib/runpy.py
Lib/test/test_cmd_line_script.py
Lib/test/test_runpy.py

index 7293f159e09ad69e0eea176391d0bda95360c5aa..78d01432a2767d5b457b59a1faa31b95dcb558b4 100644 (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.
index c7210a01346b35b02c23e460931422073015f4ef..af8605572ac20eda3921c236b367da70eed57f8b 100644 (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).
 
index bbcbf3aac09299311e8f648cf0901d400ce42c4a..c98af19af4e3a88803bda80087f1d4613221b402 100644 (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
index 77cb95c0a5b9eea3e63eaeeb03874e9adc280263..96711d693c6995a2bda98a892cf8674ee7229b01 100644 (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):
index 01f6abd8c57fcada7da7d490760013da3028a453..87c83ecfaed81cafb3630ca0ef23893cdd68e2a2 100644 (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):