FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Issue #27487: Warn if submodule already imported before runpy execution · python/cpython@9c8aa9b · GitHub

Repository navigation

Commit 9c8aa9b

Browse files
committed
Issue #27487: Warn if submodule already imported before runpy execution
Also try to clarify the find_spec() error message.
1 parent 9f95770 commit 9c8aa9b

4 files changed

Lines changed: 52 additions & 11 deletions

File tree

‎Lib/runpy.py‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,23 @@ def _get_module_details(mod_name, error=ImportError):
114114
if e.name is None or (e.name != pkg_name and
115115
not pkg_name.startswith(e.name + ".")):
116116
raise
117+
# Warn if the module has already been imported under its normal name
118+
existing = sys.modules.get(mod_name)
119+
if existing is not None and not hasattr(existing, "__path__"):
120+
from warnings import warn
121+
msg = "{mod_name!r} found in sys.modules after import of " \
122+
"package {pkg_name!r}, but prior to execution of " \
123+
"{mod_name!r}; this may result in unpredictable " \
124+
"behaviour".format(mod_name=mod_name, pkg_name=pkg_name)
125+
warn(RuntimeWarning(msg))
117126

118127
try:
119128
spec = importlib.util.find_spec(mod_name)
120129
except (ImportError, AttributeError, TypeError, ValueError) as ex:
121130
# This hack fixes an impedance mismatch between pkgutil and
122131
# importlib, where the latter raises other errors for cases where
123132
# pkgutil previously raised ImportError
124-
msg = "Error while finding spec for {!r} ({}: {})"
133+
msg = "Error while finding module specification for {!r} ({}: {})"
125134
raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
126135
if spec is None:
127136
raise error("No module named %s" % mod_name)

‎Lib/test/test_cmd_line_script.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,9 @@ def test_dash_m_errors(self):
426426
# Exercise error reporting for various invalid package executions
427427
tests = (
428428
('builtins', br'No code object available'),
429-
('builtins.x', br'Error while finding spec.*AttributeError'),
430-
('builtins.x.y', br'Error while finding spec.*'
429+
('builtins.x', br'Error while finding module specification.*'
430+
br'AttributeError'),
431+
('builtins.x.y', br'Error while finding module specification.*'
431432
br'ImportError.*No module named.*not a package'),
432433
('os.path', br'loader.*cannot handle'),
433434
('importlib', br'No module named.*'
@@ -450,7 +451,8 @@ def test_dash_m_bad_pyc(self):
450451
with open('test_pkg/__init__.pyc', 'wb'):
451452
pass
452453
err = self.check_dash_m_failure('test_pkg')
453-
self.assertRegex(err, br'Error while finding spec.*'
454+
self.assertRegex(err,
455+
br'Error while finding module specification.*'
454456
br'ImportError.*bad magic number')
455457
self.assertNotIn(b'is a package', err)
456458
self.assertNotIn(b'Traceback', err)

‎Lib/test/test_runpy.py‎

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import tempfile
88
import importlib, importlib.machinery, importlib.util
99
import py_compile
10+
import warnings
1011
from test.support import (
1112
forget, make_legacy_pyc, unload, verbose, no_tracing,
1213
create_empty_file, temp_dir)
@@ -246,7 +247,7 @@ def _make_pkg(self, source, depth, mod_base="runpy_test",
246247
mod_fname)
247248
return pkg_dir, mod_fname, mod_name, mod_spec
248249

249-
def _del_pkg(self, top, depth, mod_name):
250+
def _del_pkg(self, top):
250251
for entry in list(sys.modules):
251252
if entry.startswith("__runpy_pkg__"):
252253
del sys.modules[entry]
@@ -320,7 +321,7 @@ def create_ns(init_globals):
320321
self._fix_ns_for_legacy_pyc(expected_ns, alter_sys)
321322
self.check_code_execution(create_ns, expected_ns)
322323
finally:
323-
self._del_pkg(pkg_dir, depth, mod_name)
324+
self._del_pkg(pkg_dir)
324325
if verbose > 1: print("Module executed successfully")
325326

326327
def _check_package(self, depth, alter_sys=False,
@@ -361,7 +362,7 @@ def create_ns(init_globals):
361362
self._fix_ns_for_legacy_pyc(expected_ns, alter_sys)
362363
self.check_code_execution(create_ns, expected_ns)
363364
finally:
364-
self._del_pkg(pkg_dir, depth, pkg_name)
365+
self._del_pkg(pkg_dir)
365366
if verbose > 1: print("Package executed successfully")
366367

367368
def _add_relative_modules(self, base_dir, source, depth):
@@ -424,7 +425,7 @@ def _check_relative_imports(self, depth, run_name=None):
424425
self.assertIn("nephew", d2)
425426
del d2 # Ensure __loader__ entry doesn't keep file open
426427
finally:
427-
self._del_pkg(pkg_dir, depth, mod_name)
428+
self._del_pkg(pkg_dir)
428429
if verbose > 1: print("Module executed successfully")
429430

430431
def test_run_module(self):
@@ -447,7 +448,7 @@ def test_run_package_init_exceptions(self):
447448
result = self._make_pkg("", 1, "__main__")
448449
pkg_dir, _, mod_name, _ = result
449450
mod_name = mod_name.replace(".__main__", "")
450-
self.addCleanup(self._del_pkg, pkg_dir, 1, mod_name)
451+
self.addCleanup(self._del_pkg, pkg_dir)
451452
init = os.path.join(pkg_dir, "__runpy_pkg__", "__init__.py")
452453

453454
exceptions = (ImportError, AttributeError, TypeError, ValueError)
@@ -470,6 +471,31 @@ def test_run_package_init_exceptions(self):
470471
else:
471472
self.fail("Nothing raised; expected {}".format(name))
472473

474+
def test_submodule_imported_warning(self):
475+
pkg_dir, _, mod_name, _ = self._make_pkg("", 1)
476+
try:
477+
__import__(mod_name)
478+
with self.assertWarnsRegex(RuntimeWarning,
479+
r"found in sys\.modules"):
480+
run_module(mod_name)
481+
finally:
482+
self._del_pkg(pkg_dir)
483+
484+
def test_package_imported_no_warning(self):
485+
pkg_dir, _, mod_name, _ = self._make_pkg("", 1, "__main__")
486+
self.addCleanup(self._del_pkg, pkg_dir)
487+
package = mod_name.replace(".__main__", "")
488+
# No warning should occur if we only imported the parent package
489+
__import__(package)
490+
self.assertIn(package, sys.modules)
491+
with warnings.catch_warnings():
492+
warnings.simplefilter("error", RuntimeWarning)
493+
run_module(package)
494+
# But the warning should occur if we imported the __main__ submodule
495+
__import__(mod_name)
496+
with self.assertWarnsRegex(RuntimeWarning, r"found in sys\.modules"):
497+
run_module(package)
498+
473499
def test_run_package_in_namespace_package(self):
474500
for depth in range(1, 4):
475501
if verbose > 1: print("Testing package depth:", depth)
@@ -524,7 +550,7 @@ def create_ns(init_globals):
524550
try:
525551
self.check_code_execution(create_ns, expected_ns)
526552
finally:
527-
self._del_pkg(pkg_dir, depth, mod_name)
553+
self._del_pkg(pkg_dir)
528554

529555
def test_pkgutil_walk_packages(self):
530556
# This is a dodgy hack to use the test_runpy infrastructure to test
@@ -548,7 +574,7 @@ def test_pkgutil_walk_packages(self):
548574
expected_modules.add(pkg_name + ".runpy_test")
549575
pkg_dir, mod_fname, mod_name, mod_spec = (
550576
self._make_pkg("", max_depth))
551-
self.addCleanup(self._del_pkg, pkg_dir, max_depth, mod_name)
577+
self.addCleanup(self._del_pkg, pkg_dir)
552578
for depth in range(2, max_depth+1):
553579
self._add_relative_modules(pkg_dir, "", depth)
554580
for finder, mod_name, ispkg in pkgutil.walk_packages([pkg_dir]):

‎Misc/NEWS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ Release date: TBA
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #27487: Warn if a submodule argument to "python -m" or
14+
runpy.run_module() is found in sys.modules after parent packages are
15+
imported, but before the submodule is executed.
16+
1317
- Issue #27558: Fix a SystemError in the implementation of "raise" statement.
1418
In a brand new thread, raise a RuntimeError since there is no active
1519
exception to reraise. Patch written by Xiang Zhang.

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL