From dcb9cfdc004277d64661945a757ca5925e2fa363 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 25 Aug 2025 22:38:06 +0300 Subject: [PATCH] gh-134716: Support regular expressions in -W and PYTHONWARNINGS --- Doc/library/warnings.rst | 10 +++++-- Doc/using/cmdline.rst | 16 ++++++++-- Doc/whatsnew/3.15.rst | 16 ++++++++++ Lib/_py_warnings.py | 28 ++++++++++++++++-- Lib/test/test_warnings/__init__.py | 29 +++++++++++++++++++ ...-08-25-22-38-03.gh-issue-134716.kyYKeX.rst | 2 ++ 6 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-08-25-22-38-03.gh-issue-134716.kyYKeX.rst diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst index 05e061cc697778..6570856853cf73 100644 --- a/Doc/library/warnings.rst +++ b/Doc/library/warnings.rst @@ -157,8 +157,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 @@ -166,7 +168,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 cad49e2deeb46f..c078bf31b7a99d 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -470,8 +470,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 @@ -480,6 +482,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. @@ -497,6 +503,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 @@ -971,6 +980,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 54a7d0f3c57dad..3a67fa957de4cd 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -273,6 +273,22 @@ Other language changes This speeds up class creation, and helps avoid reference cycles. (Contributed by Petr Viktorin in :gh:`135228`.) +* Warning filters specified by the :option:`-W` option and the + :envvar:`PYTHONWARNINGS` environment variable can now use regular + expressions for message and module. + The corresponding field must be surrounded by slashes (``/``). + + The :option:`-W` option and the :envvar:`PYTHONWARNINGS` environment + variable can now specify use regular expressions for matching warning + message and module if the corresponding field starts and ends by slashes + (``/``). + + 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 5070caea6bb054..a07f3ed05d1beb 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 694cfc97064c30..38f9a71a9c8aeb 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.