https://github.com/python/cpython/commit/f2eaf174b2505be1b3dfbe817db243adf7fd5d6b
commit: f2eaf174b2505be1b3dfbe817db243adf7fd5d6b
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-18T10:29:05+03:00
summary:
gh-123011: Fix warn_explicit() with the globals of the __main__ module
(GH-155318)
The __main__ module executed as a script or a command has __spec__ set to
None, so warn_explicit(module_globals=globals()) emitted a spurious
DeprecationWarning. It also raised ImportError when the loader was unable
to provide the source of the module: when the module was executed with -m
(the loader can only handle its own module name) or as a command (the
built-in importer has no source).
files:
A Misc/NEWS.d/next/Library/2026-08-07-01-10-00.gh-issue-123011.warnexp.rst
M Lib/importlib/_bootstrap_external.py
M Lib/test/test_warnings/__init__.py
M Python/_warnings.c
diff --git a/Lib/importlib/_bootstrap_external.py
b/Lib/importlib/_bootstrap_external.py
index a1cb729efb7fef2..176652230c092a8 100644
--- a/Lib/importlib/_bootstrap_external.py
+++ b/Lib/importlib/_bootstrap_external.py
@@ -634,6 +634,10 @@ def _bless_my_loader(module_globals):
loader = module_globals.get('__loader__', None)
spec = module_globals.get('__spec__', missing)
+ # The __main__ module of a script or the REPL has __spec__ set to None.
+ if spec is None and module_globals.get('__name__') == '__main__':
+ return loader
+
if loader is None:
if spec is missing:
# If working with a module:
diff --git a/Lib/test/test_warnings/__init__.py
b/Lib/test/test_warnings/__init__.py
index b86a8838c7a2457..5b5d5c2f70ffd7a 100644
--- a/Lib/test/test_warnings/__init__.py
+++ b/Lib/test/test_warnings/__init__.py
@@ -1717,6 +1717,59 @@ def test_issue_8766(self):
assert_python_ok('-c', 'pass', '-W', 'always', PYTHONPATH=cwd)
+class WarnExplicitMainTests(BaseTest):
+ # gh-123011: warn_explicit() with module globals of the __main__ module,
+ # no matter how it is executed.
+ code = ('import warnings\n'
+ 'warnings.warn_explicit("eggs", UserWarning, "bar", 1,\n'
+ ' module_globals=globals())\n')
+
+ def prepare_code(self):
+ """Make the subprocess use the tested implementation."""
+ if self.module is py_warnings:
+ return ("import sys\n"
+ "sys.modules['_warnings'] = None\n") + self.code
+ return self.code
+
+ def check(self, err):
+ lines = err.decode().splitlines()
+ # Only the Python implementation adds the source line.
+ if len(lines) > 1 and lines[1].startswith(' '):
+ del lines[1]
+ self.assertEqual(lines, ['bar:1: UserWarning: eggs'])
+
+ def make_script(self, dirname):
+ filename = os.path.join(dirname, 'spam.py')
+ with open(filename, 'w', encoding='utf-8') as f:
+ f.write(self.prepare_code())
+ return filename
+
+ def test_script(self):
+ # __main__ has __spec__ set to None.
+ with os_helper.temp_dir() as dirname:
+ filename = self.make_script(dirname)
+ rc, out, err = assert_python_ok(filename)
+ self.check(err)
+
+ def test_module(self):
+ # __main__ has __spec__ of the module executed with -m.
+ with os_helper.temp_dir() as dirname:
+ self.make_script(dirname)
+ rc, out, err = assert_python_ok('-m', 'spam', PYTHONPATH=dirname)
+ self.check(err)
+
+ def test_command(self):
+ # __main__ has the built-in importer as a loader.
+ rc, out, err = assert_python_ok('-c', self.prepare_code())
+ self.check(err)
+
+class CWarnExplicitMainTests(WarnExplicitMainTests, unittest.TestCase):
+ module = c_warnings
+
+class PyWarnExplicitMainTests(WarnExplicitMainTests, unittest.TestCase):
+ module = py_warnings
+
+
class FinalizationTest(unittest.TestCase):
def test_finalization(self):
# Issue #19421: warnings.warn() should not crash
diff --git
a/Misc/NEWS.d/next/Library/2026-08-07-01-10-00.gh-issue-123011.warnexp.rst
b/Misc/NEWS.d/next/Library/2026-08-07-01-10-00.gh-issue-123011.warnexp.rst
new file mode 100644
index 000000000000000..6c990b2eb938452
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-07-01-10-00.gh-issue-123011.warnexp.rst
@@ -0,0 +1,3 @@
+:func:`warnings.warn_explicit` no longer emits a spurious
+:exc:`DeprecationWarning` or raises :exc:`ImportError` when it is called with
+the globals of the :mod:`__main__` module.
diff --git a/Python/_warnings.c b/Python/_warnings.c
index 992c3e50f2a9f67..33385e86c049ce4 100644
--- a/Python/_warnings.c
+++ b/Python/_warnings.c
@@ -1201,12 +1201,33 @@ get_source_line(PyInterpreterState *interp, PyObject
*module_globals, int lineno
return NULL;
}
- int rc = PyDict_GetItemRef(module_globals, &_Py_ID(__name__),
- &module_name);
- if (rc < 0 || rc == 0) {
+ /* Prefer __spec__.name: __name__ is "__main__" for the module executed
+ as a script, but the loader can only handle its own module name. */
+ PyObject *spec;
+ if (PyDict_GetItemRef(module_globals, &_Py_ID(__spec__), &spec) < 0) {
Py_DECREF(loader);
return NULL;
}
+ module_name = NULL;
+ if (spec != NULL) {
+ int rc = PyObject_GetOptionalAttr(spec, &_Py_ID(name), &module_name);
+ Py_DECREF(spec);
+ if (rc < 0) {
+ Py_DECREF(loader);
+ return NULL;
+ }
+ if (module_name == Py_None) {
+ Py_CLEAR(module_name);
+ }
+ }
+ if (module_name == NULL) {
+ int rc = PyDict_GetItemRef(module_globals, &_Py_ID(__name__),
+ &module_name);
+ if (rc <= 0) { // not found or error
+ Py_DECREF(loader);
+ return NULL;
+ }
+ }
/* Make sure the loader implements the optional get_source() method. */
(void)PyObject_GetOptionalAttr(loader, &_Py_ID(get_source), &get_source);
@@ -1220,6 +1241,11 @@ get_source_line(PyInterpreterState *interp, PyObject
*module_globals, int lineno
Py_DECREF(get_source);
Py_DECREF(module_name);
if (!source) {
+ /* The source line is optional: the loader can be unable to provide
+ the source of the module, for example if it is not its loader. */
+ if (PyErr_ExceptionMatches(PyExc_ImportError)) {
+ PyErr_Clear();
+ }
return NULL;
}
if (source == Py_None) {
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]