[Python-checkins] cpython (3.5): Issue #27487: Warn if submodule already imported before runpy execution

martin.panter python-checkins at python.org
Sun Aug 21 01:37:04 EDT 2016


https://hg.python.org/cpython/rev/43ae044eaccc
changeset:   102818:43ae044eaccc
branch:      3.5
parent:      102816:37bc126f3473
user:        Martin Panter <vadmium+py at gmail.com>
date:        Sun Aug 21 04:07:58 2016 +0000
summary:
  Issue #27487: Warn if submodule already imported before runpy execution

Also try to clarify the find_spec() error message.

files:
  Lib/runpy.py                     |  11 +++++-
  Lib/test/test_cmd_line_script.py |   8 ++-
  Lib/test/test_runpy.py           |  40 ++++++++++++++++---
  Misc/NEWS                        |   4 ++
  4 files changed, 52 insertions(+), 11 deletions(-)


diff --git a/Lib/runpy.py b/Lib/runpy.py
--- a/Lib/runpy.py
+++ b/Lib/runpy.py
@@ -114,6 +114,15 @@
             if e.name is None or (e.name != pkg_name and
                     not pkg_name.startswith(e.name + ".")):
                 raise
+        # Warn if the module has already been imported under its normal name
+        existing = sys.modules.get(mod_name)
+        if existing is not None and not hasattr(existing, "__path__"):
+            from warnings import warn
+            msg = "{mod_name!r} found in sys.modules after import of " \
+                "package {pkg_name!r}, but prior to execution of " \
+                "{mod_name!r}; this may result in unpredictable " \
+                "behaviour".format(mod_name=mod_name, pkg_name=pkg_name)
+            warn(RuntimeWarning(msg))
 
     try:
         spec = importlib.util.find_spec(mod_name)
@@ -121,7 +130,7 @@
         # This hack fixes an impedance mismatch between pkgutil and
         # importlib, where the latter raises other errors for cases where
         # pkgutil previously raised ImportError
-        msg = "Error while finding spec for {!r} ({}: {})"
+        msg = "Error while finding module specification for {!r} ({}: {})"
         raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
     if spec is None:
         raise error("No module named %s" % mod_name)
diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py
--- a/Lib/test/test_cmd_line_script.py
+++ b/Lib/test/test_cmd_line_script.py
@@ -426,8 +426,9 @@
         # Exercise error reporting for various invalid package executions
         tests = (
             ('builtins', br'No code object available'),
-            ('builtins.x', br'Error while finding spec.*AttributeError'),
-            ('builtins.x.y', br'Error while finding spec.*'
+            ('builtins.x', br'Error while finding module specification.*'
+                br'AttributeError'),
+            ('builtins.x.y', br'Error while finding module specification.*'
                 br'ImportError.*No module named.*not a package'),
             ('os.path', br'loader.*cannot handle'),
             ('importlib', br'No module named.*'
@@ -450,7 +451,8 @@
             with open('test_pkg/__init__.pyc', 'wb'):
                 pass
             err = self.check_dash_m_failure('test_pkg')
-            self.assertRegex(err, br'Error while finding spec.*'
+            self.assertRegex(err,
+                br'Error while finding module specification.*'
                 br'ImportError.*bad magic number')
             self.assertNotIn(b'is a package', err)
             self.assertNotIn(b'Traceback', err)
diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py
--- a/Lib/test/test_runpy.py
+++ b/Lib/test/test_runpy.py
@@ -7,6 +7,7 @@
 import tempfile
 import importlib, importlib.machinery, importlib.util
 import py_compile
+import warnings
 from test.support import (
     forget, make_legacy_pyc, unload, verbose, no_tracing,
     create_empty_file, temp_dir)
@@ -246,7 +247,7 @@
                                                           mod_fname)
         return pkg_dir, mod_fname, mod_name, mod_spec
 
-    def _del_pkg(self, top, depth, mod_name):
+    def _del_pkg(self, top):
         for entry in list(sys.modules):
             if entry.startswith("__runpy_pkg__"):
                 del sys.modules[entry]
@@ -320,7 +321,7 @@
                 self._fix_ns_for_legacy_pyc(expected_ns, alter_sys)
                 self.check_code_execution(create_ns, expected_ns)
         finally:
-            self._del_pkg(pkg_dir, depth, mod_name)
+            self._del_pkg(pkg_dir)
         if verbose > 1: print("Module executed successfully")
 
     def _check_package(self, depth, alter_sys=False,
@@ -361,7 +362,7 @@
                 self._fix_ns_for_legacy_pyc(expected_ns, alter_sys)
                 self.check_code_execution(create_ns, expected_ns)
         finally:
-            self._del_pkg(pkg_dir, depth, pkg_name)
+            self._del_pkg(pkg_dir)
         if verbose > 1: print("Package executed successfully")
 
     def _add_relative_modules(self, base_dir, source, depth):
@@ -424,7 +425,7 @@
                 self.assertIn("nephew", d2)
                 del d2 # Ensure __loader__ entry doesn't keep file open
         finally:
-            self._del_pkg(pkg_dir, depth, mod_name)
+            self._del_pkg(pkg_dir)
         if verbose > 1: print("Module executed successfully")
 
     def test_run_module(self):
@@ -447,7 +448,7 @@
         result = self._make_pkg("", 1, "__main__")
         pkg_dir, _, mod_name, _ = result
         mod_name = mod_name.replace(".__main__", "")
-        self.addCleanup(self._del_pkg, pkg_dir, 1, mod_name)
+        self.addCleanup(self._del_pkg, pkg_dir)
         init = os.path.join(pkg_dir, "__runpy_pkg__", "__init__.py")
 
         exceptions = (ImportError, AttributeError, TypeError, ValueError)
@@ -470,6 +471,31 @@
                 else:
                     self.fail("Nothing raised; expected {}".format(name))
 
+    def test_submodule_imported_warning(self):
+        pkg_dir, _, mod_name, _ = self._make_pkg("", 1)
+        try:
+            __import__(mod_name)
+            with self.assertWarnsRegex(RuntimeWarning,
+                    r"found in sys\.modules"):
+                run_module(mod_name)
+        finally:
+            self._del_pkg(pkg_dir)
+
+    def test_package_imported_no_warning(self):
+        pkg_dir, _, mod_name, _ = self._make_pkg("", 1, "__main__")
+        self.addCleanup(self._del_pkg, pkg_dir)
+        package = mod_name.replace(".__main__", "")
+        # No warning should occur if we only imported the parent package
+        __import__(package)
+        self.assertIn(package, sys.modules)
+        with warnings.catch_warnings():
+            warnings.simplefilter("error", RuntimeWarning)
+            run_module(package)
+        # But the warning should occur if we imported the __main__ submodule
+        __import__(mod_name)
+        with self.assertWarnsRegex(RuntimeWarning, r"found in sys\.modules"):
+            run_module(package)
+
     def test_run_package_in_namespace_package(self):
         for depth in range(1, 4):
             if verbose > 1: print("Testing package depth:", depth)
@@ -524,7 +550,7 @@
         try:
             self.check_code_execution(create_ns, expected_ns)
         finally:
-            self._del_pkg(pkg_dir, depth, mod_name)
+            self._del_pkg(pkg_dir)
 
     def test_pkgutil_walk_packages(self):
         # This is a dodgy hack to use the test_runpy infrastructure to test
@@ -548,7 +574,7 @@
         expected_modules.add(pkg_name + ".runpy_test")
         pkg_dir, mod_fname, mod_name, mod_spec = (
                self._make_pkg("", max_depth))
-        self.addCleanup(self._del_pkg, pkg_dir, max_depth, mod_name)
+        self.addCleanup(self._del_pkg, pkg_dir)
         for depth in range(2, max_depth+1):
             self._add_relative_modules(pkg_dir, "", depth)
         for finder, mod_name, ispkg in pkgutil.walk_packages([pkg_dir]):
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,10 @@
 Core and Builtins
 -----------------
 
+- Issue #27487: Warn if a submodule argument to "python -m" or
+  runpy.run_module() is found in sys.modules after parent packages are
+  imported, but before the submodule is executed.
+
 - Issue #27558: Fix a SystemError in the implementation of "raise" statement.
   In a brand new thread, raise a RuntimeError since there is no active
   exception to reraise. Patch written by Xiang Zhang.

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list