From 1a79d1e9bcec93957b6dbe8f0508dd2b8b6a6e3d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 1 May 2026 11:39:40 +0300 Subject: [PATCH 1/2] gh-143231: Add the module attribute to warnings.WarningMessage --- Lib/_py_warnings.py | 15 +++++---- Lib/test/test_warnings/__init__.py | 33 +++++++++++++++++-- ...-05-01-11-39-37.gh-issue-143231.0cOHET.rst | 4 +++ ...-05-02-18-23-50.gh-issue-143231.oBbQb5.rst | 1 + Python/_warnings.c | 7 ++-- 5 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-05-01-11-39-37.gh-issue-143231.0cOHET.rst create mode 100644 Misc/NEWS.d/next/Library/2026-05-02-18-23-50.gh-issue-143231.oBbQb5.rst diff --git a/Lib/_py_warnings.py b/Lib/_py_warnings.py index 81a386c4487d95..ab09913de6812d 100644 --- a/Lib/_py_warnings.py +++ b/Lib/_py_warnings.py @@ -620,17 +620,18 @@ def warn_explicit(message, category, filename, lineno, linecache.getlines(filename, module_globals) # Print message and context - msg = _wm.WarningMessage(message, category, filename, lineno, source=source) + msg = _wm.WarningMessage(message, category, filename, lineno, + module=module, source=source) _wm._showwarnmsg(msg) class WarningMessage(object): _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", - "line", "source") + "line", "source", "module") def __init__(self, message, category, filename, lineno, file=None, - line=None, source=None): + line=None, source=None, module=None): self.message = message self.category = category self.filename = filename @@ -638,12 +639,14 @@ def __init__(self, message, category, filename, lineno, file=None, self.file = file self.line = line self.source = source + self.module = module self._category_name = category.__name__ if category else None def __str__(self): - return ("{message : %r, category : %r, filename : %r, lineno : %s, " - "line : %r}" % (self.message, self._category_name, - self.filename, self.lineno, self.line)) + return ("{message : %r, category : %r, module : %r, " + "filename : %r, lineno : %s, line : %r}" % ( + self.message, self._category_name, self.module, + self.filename, self.lineno, self.line)) def __repr__(self): return f'<{type(self).__qualname__} {self}>' diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index d86844c1a29a9a..bf1bcf8e6ed5d9 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -582,15 +582,19 @@ def test_warn_nonstandard_types(self): # ``Warning() != Warning()``. self.assertEqual(str(w[-1].message), str(UserWarning(ob))) - def test_filename(self): + def test_filename_module(self): with warnings_state(self.module): with self.module.catch_warnings(record=True) as w: warning_tests.inner("spam1") self.assertEqual(os.path.basename(w[-1].filename), "stacklevel.py") + self.assertEqual(w[-1].module, + "test.test_warnings.data.stacklevel") warning_tests.outer("spam2") self.assertEqual(os.path.basename(w[-1].filename), "stacklevel.py") + self.assertEqual(w[-1].module, + "test.test_warnings.data.stacklevel") def test_stacklevel(self): # Test stacklevel argument @@ -600,23 +604,32 @@ def test_stacklevel(self): warning_tests.inner("spam3", stacklevel=1) self.assertEqual(os.path.basename(w[-1].filename), "stacklevel.py") + self.assertEqual(w[-1].module, + "test.test_warnings.data.stacklevel") warning_tests.outer("spam4", stacklevel=1) self.assertEqual(os.path.basename(w[-1].filename), "stacklevel.py") + self.assertEqual(w[-1].module, + "test.test_warnings.data.stacklevel") warning_tests.inner("spam5", stacklevel=2) self.assertEqual(os.path.basename(w[-1].filename), "__init__.py") + self.assertEqual(w[-1].module, __name__) warning_tests.outer("spam6", stacklevel=2) self.assertEqual(os.path.basename(w[-1].filename), "stacklevel.py") + self.assertEqual(w[-1].module, + "test.test_warnings.data.stacklevel") warning_tests.outer("spam6.5", stacklevel=3) self.assertEqual(os.path.basename(w[-1].filename), "__init__.py") + self.assertEqual(w[-1].module, __name__) warning_tests.inner("spam7", stacklevel=9999) self.assertEqual(os.path.basename(w[-1].filename), "") + self.assertEqual(w[-1].module, "sys") def test_stacklevel_import(self): # Issue #24305: With stacklevel=2, module-level warnings should work. @@ -627,6 +640,7 @@ def test_stacklevel_import(self): import test.test_warnings.data.import_warning # noqa: F401 self.assertEqual(len(w), 1) self.assertEqual(w[0].filename, __file__) + self.assertEqual(w[0].module, __name__) def test_skip_file_prefixes(self): with warnings_state(self.module): @@ -638,20 +652,27 @@ def test_skip_file_prefixes(self): "inner_api", stacklevel=2, warnings_module=warning_tests.warnings) self.assertEqual(w[-1].filename, __file__) + self.assertEqual(w[-1].module, __name__) warning_tests.package("package api", stacklevel=2) self.assertEqual(w[-1].filename, __file__) + self.assertEqual(w[-1].module, __name__) self.assertEqual(w[-2].filename, w[-1].filename) + self.assertEqual(w[-2].module, w[-1].module) # Low stacklevels are overridden to 2 behavior. warning_tests.package("package api 1", stacklevel=1) self.assertEqual(w[-1].filename, __file__) + self.assertEqual(w[-1].module, __name__) warning_tests.package("package api 0", stacklevel=0) self.assertEqual(w[-1].filename, __file__) + self.assertEqual(w[-1].module, __name__) warning_tests.package("package api -99", stacklevel=-99) self.assertEqual(w[-1].filename, __file__) + self.assertEqual(w[-1].module, __name__) # The stacklevel still goes up out of the package. warning_tests.package("prefix02", stacklevel=3) self.assertIn("unittest", w[-1].filename) + self.assertStartsWith(w[-1].module, "unittest") def test_skip_file_prefixes_file_path(self): # see: gh-126209 @@ -662,6 +683,8 @@ def test_skip_file_prefixes_file_path(self): self.assertEqual(len(w), 1) self.assertNotEqual(w[-1].filename, skipped) + self.assertEqual(w[-1].filename, __file__) + self.assertEqual(w[-1].module, __name__) def test_skip_file_prefixes_type_errors(self): with warnings_state(self.module): @@ -673,7 +696,7 @@ def test_skip_file_prefixes_type_errors(self): with self.assertRaises(TypeError): warn("msg", skip_file_prefixes="a sequence of strs") - def test_exec_filename(self): + def test_exec_filename_module(self): filename = "" codeobj = compile(("import warnings\n" "warnings.warn('hello', UserWarning)"), @@ -682,6 +705,12 @@ def test_exec_filename(self): self.module.simplefilter("always", category=UserWarning) exec(codeobj) self.assertEqual(w[0].filename, filename) + self.assertEqual(w[0].module, __name__) + with self.module.catch_warnings(record=True) as w: + self.module.simplefilter("always", category=UserWarning) + exec(codeobj, {}) + self.assertEqual(w[0].filename, filename) + self.assertEqual(w[0].module, '') def test_warn_explicit_non_ascii_filename(self): with self.module.catch_warnings(record=True) as w: diff --git a/Misc/NEWS.d/next/Library/2026-05-01-11-39-37.gh-issue-143231.0cOHET.rst b/Misc/NEWS.d/next/Library/2026-05-01-11-39-37.gh-issue-143231.0cOHET.rst new file mode 100644 index 00000000000000..05c9fa79904154 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-01-11-39-37.gh-issue-143231.0cOHET.rst @@ -0,0 +1,4 @@ +:func:`unittest.TestCase.assertWarns` and +:func:`unittest.TestCase.assertWarnsRegex` no longer swallow warnings that +do not match the specified category or regex. +Nested context managers are now supported. diff --git a/Misc/NEWS.d/next/Library/2026-05-02-18-23-50.gh-issue-143231.oBbQb5.rst b/Misc/NEWS.d/next/Library/2026-05-02-18-23-50.gh-issue-143231.oBbQb5.rst new file mode 100644 index 00000000000000..e4769866c2045d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-02-18-23-50.gh-issue-143231.oBbQb5.rst @@ -0,0 +1 @@ +A *module* attribute has been added to :class:`!warnings.WarningMessage`. diff --git a/Python/_warnings.c b/Python/_warnings.c index 6b8fa19ff3f606..4f6de50efa14a8 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -716,7 +716,7 @@ static int call_show_warning(PyThreadState *tstate, PyObject *category, PyObject *text, PyObject *message, PyObject *filename, int lineno, PyObject *lineno_obj, - PyObject *sourceline, PyObject *source) + PyObject *sourceline, PyObject *source, PyObject *module) { PyObject *show_fn, *msg, *res, *warnmsg_cls = NULL; PyInterpreterState *interp = tstate->interp; @@ -747,7 +747,8 @@ call_show_warning(PyThreadState *tstate, PyObject *category, } msg = PyObject_CallFunctionObjArgs(warnmsg_cls, message, category, - filename, lineno_obj, Py_None, Py_None, source, + filename, lineno_obj, Py_None, Py_None, + source ? source : Py_None, module, NULL); Py_DECREF(warnmsg_cls); if (msg == NULL) @@ -878,7 +879,7 @@ warn_explicit(PyThreadState *tstate, PyObject *category, PyObject *message, goto return_none; if (rc == 0) { if (call_show_warning(tstate, category, text, message, filename, - lineno, lineno_obj, sourceline, source) < 0) + lineno, lineno_obj, sourceline, source, module) < 0) goto cleanup; } else /* if (rc == -1) */ From 5af3068f1f3ec37f76957225e54d90fb25762438 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 3 May 2026 12:08:07 +0300 Subject: [PATCH 2/2] Delete Misc/NEWS.d/next/Library/2026-05-01-11-39-37.gh-issue-143231.0cOHET.rst --- .../Library/2026-05-01-11-39-37.gh-issue-143231.0cOHET.rst | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2026-05-01-11-39-37.gh-issue-143231.0cOHET.rst diff --git a/Misc/NEWS.d/next/Library/2026-05-01-11-39-37.gh-issue-143231.0cOHET.rst b/Misc/NEWS.d/next/Library/2026-05-01-11-39-37.gh-issue-143231.0cOHET.rst deleted file mode 100644 index 05c9fa79904154..00000000000000 --- a/Misc/NEWS.d/next/Library/2026-05-01-11-39-37.gh-issue-143231.0cOHET.rst +++ /dev/null @@ -1,4 +0,0 @@ -:func:`unittest.TestCase.assertWarns` and -:func:`unittest.TestCase.assertWarnsRegex` no longer swallow warnings that -do not match the specified category or regex. -Nested context managers are now supported.