diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst index b6392450a1722f..5b17bd009b36e7 100644 --- a/Doc/library/warnings.rst +++ b/Doc/library/warnings.rst @@ -159,8 +159,10 @@ the disposition of the match. Each entry is a tuple of the form (*action*, * *message* is a string containing a regular expression that the start of the warning message must match, case-insensitively. In :option:`-W` and - :envvar:`PYTHONWARNINGS`, *message* is a literal string that the start of the - warning message must contain (case-insensitively), ignoring any whitespace at + :envvar:`PYTHONWARNINGS`, if *message* starts and ends with a forward slash + (``/``), it specifies a regular expression as above; + otherwise it is a literal string that the start of the + warning message must match (case-insensitively), ignoring any whitespace at the start or end of *message*. * *category* is a class (a subclass of :exc:`Warning`) of which the warning @@ -168,7 +170,9 @@ the disposition of the match. Each entry is a tuple of the form (*action*, * *module* is a string containing a regular expression that the start of the fully qualified module name must match, case-sensitively. In :option:`-W` and - :envvar:`PYTHONWARNINGS`, *module* is a literal string that the + :envvar:`PYTHONWARNINGS`, if *module* starts and ends with a forward slash + (``/``), it specifies a regular expression as above; + otherwise it is a literal string that the fully qualified module name must be equal to (case-sensitively), ignoring any whitespace at the start or end of *module*. diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index 3f8b52e2345b25..74c18c2a6ede9c 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -479,8 +479,10 @@ Miscellaneous options The *action* field is as explained above but only applies to warnings that match the remaining fields. - The *message* field must match the whole warning message; this match is - case-insensitive. + The *message* field must match the start of the warning message; + this match is case-insensitive. + If it starts and ends with a forward slash (``/``), it specifies + a regular expression, otherwise it specifies a literal string. The *category* field matches the warning category (ex: ``DeprecationWarning``). This must be a class name; the match test @@ -489,6 +491,10 @@ Miscellaneous options The *module* field matches the (fully qualified) module name; this match is case-sensitive. + If it starts and ends with a forward slash (``/``), it specifies + a regular expression that the start of the fully qualified module name + must match, otherwise it specifies a literal string that the fully + qualified module name must be equal to. The *lineno* field matches the line number, where zero matches all line numbers and is thus equivalent to an omitted line number. @@ -506,6 +512,9 @@ Miscellaneous options See :ref:`warning-filter` and :ref:`describing-warning-filters` for more details. + .. versionchanged:: next + Added regular expression support for *message* and *module*. + .. option:: -x @@ -980,6 +989,9 @@ conflict. See :ref:`warning-filter` and :ref:`describing-warning-filters` for more details. + .. versionchanged:: next + Added regular expression support for *message* and *module*. + .. envvar:: PYTHONFAULTHANDLER diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index ac09c57441e540..dc16c944886bd0 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -273,6 +273,12 @@ Other language changes This speeds up class creation, and helps avoid reference cycles. (Contributed by Petr Viktorin in :gh:`135228`.) +* The :option:`-W` option and the :envvar:`PYTHONWARNINGS` environment variable + can now specify regular expressions instead of literal strings to match + the warning message and the module name, if the corresponding field starts + and ends with a forward slash (``/``). + (Contributed by Serhiy Storchaka in :gh:`134716`.) + New modules =========== diff --git a/Lib/_py_warnings.py b/Lib/_py_warnings.py index 2e7113a637a2ac..576a17ea7b8501 100644 --- a/Lib/_py_warnings.py +++ b/Lib/_py_warnings.py @@ -369,9 +369,15 @@ def _setoption(arg): if message or module: import re if message: - message = re.escape(message) + if len(message) >= 2 and message[0] == message[-1] == '/': + message = message[1:-1] + else: + message = re.escape(message) if module: - module = re.escape(module) + r'\z' + if len(module) >= 2 and module[0] == module[-1] == '/': + module = module[1:-1] + else: + module = re.escape(module) + r'\z' if lineno: try: lineno = int(lineno) @@ -381,7 +387,23 @@ def _setoption(arg): raise _wm._OptionError("invalid lineno %r" % (lineno,)) from None else: lineno = 0 - _wm.filterwarnings(action, message, category, module, lineno) + try: + _wm.filterwarnings(action, message, category, module, lineno) + except re.PatternError if message or module else (): + if message: + try: + re.compile(message) + except re.PatternError: + raise _wm._OptionError(f"invalid regular expression for " + f"message: {message!r}") from None + if module: + try: + re.compile(module) + except re.PatternError: + raise _wm._OptionError(f"invalid regular expression for " + f"module: {module!r}") from None + # Should never happen. + raise # Helper for _setoption() diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index 256713365286c2..260fae8fe243b6 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -755,6 +755,10 @@ def test_improper_input(self): self.module._setoption('ignore::===') with self.assertRaisesRegex(self.module._OptionError, 'Wärning'): self.module._setoption('ignore::Wärning') + with self.assertRaisesRegex(self.module._OptionError, 'message'): + self.module._setoption('ignore:/?/:Warning') + with self.assertRaisesRegex(self.module._OptionError, 'module'): + self.module._setoption('ignore::Warning:/?/') self.module._setoption('error::Warning::0') self.assertRaises(UserWarning, self.module.warn, 'convert to error') @@ -769,6 +773,31 @@ def test_import_from_module(self): with self.assertRaises(TestWarning): self.module.warn('test warning', TestWarning) + def test_message(self): + # Match prefix, case-insensitive. + with self.module.catch_warnings(): + self.module._setoption('error:TEST WARN:UserWarning') + with self.assertRaises(UserWarning): + self.module.warn('Test Warning') + with self.module.catch_warnings(): + self.module._setoption(r'error:/TE.*WARN/:UserWarning') + with self.assertRaises(UserWarning): + self.module.warn('Test Warning') + + def test_module(self): + with self.module.catch_warnings(): + self.module._setoption(f'error::UserWarning:{__name__}') + with self.assertRaises(UserWarning): + self.module.warn('test warning') + # Only full match. + self.module._setoption(f'ignore::UserWarning:{__name__[:-2]}') + with self.assertRaises(UserWarning): + self.module.warn('test warning') + with self.module.catch_warnings(): + self.module._setoption(f'error::UserWarning:/{re.escape(__name__[:-2])}./') + with self.assertRaises(UserWarning): + self.module.warn('test warning') + class CWCmdLineTests(WCmdLineTests, unittest.TestCase): module = c_warnings diff --git a/Misc/NEWS.d/next/Library/2025-08-25-22-38-03.gh-issue-134716.kyYKeX.rst b/Misc/NEWS.d/next/Library/2025-08-25-22-38-03.gh-issue-134716.kyYKeX.rst new file mode 100644 index 00000000000000..8a65080973a19e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-25-22-38-03.gh-issue-134716.kyYKeX.rst @@ -0,0 +1,2 @@ +Add support of regular expressions in the :option:`-W` option and the +:envvar:`PYTHONWARNINGS` environment variable.