From 83a045b35c183fb91a423c097c763a82e1ddabbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Sat, 3 Feb 2024 06:25:09 +0100 Subject: [PATCH 01/13] ENH: Add all warnings check to the `assert_produces_warnings`, and separate messages for each warning. --- doc/source/whatsnew/v3.0.0.rst | 2 +- pandas/_testing/_warnings.py | 45 ++++++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 25163a0f678b0..b26e45a927ce4 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -29,8 +29,8 @@ enhancement2 Other enhancements ^^^^^^^^^^^^^^^^^^ - :func:`DataFrame.to_excel` now raises an ``UserWarning`` when the character count in a cell exceeds Excel's limitation of 32767 characters (:issue:`56954`) +- :func:`assert_produces_warning` now accepts warning message match for every expected warning. A must_find_all_warnings keyword argument was added, to make the function check all expected warnings. (:issue:`56555`) - :func:`read_stata` now returns ``datetime64`` resolutions better matching those natively stored in the stata format (:issue:`55642`) -- .. --------------------------------------------------------------------------- .. _whatsnew_300.notable_bug_fixes: diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py index cff28f6a20472..bbd6bc0efdb3f 100644 --- a/pandas/_testing/_warnings.py +++ b/pandas/_testing/_warnings.py @@ -32,7 +32,8 @@ def assert_produces_warning( ] = "always", check_stacklevel: bool = True, raise_on_extra_warnings: bool = True, - match: str | None = None, + match: str | tuple[str | None, ...] | None = None, + must_find_all_warnings: bool = False, ) -> Generator[list[warnings.WarningMessage], None, None]: """ Context manager for running code expected to either raise a specific warning, @@ -68,8 +69,17 @@ class for all warnings. To raise multiple types of exceptions, raise_on_extra_warnings : bool, default True Whether extra warnings not of the type `expected_warning` should cause the test to fail. - match : str, optional - Match warning message. + match : {str, tuple[str, ...]}, optional + Match warning message. If it's a tuple, it has to be the size of + `expected_warning`. If additionally `must_find_all_warnings` is + True, each expected warning's message gets matched with a respective + match. Otherwise, multiple values get treated as an alternative. + must_find_all_warnings : bool, default False + If True and `expected_warning` is a tuple, each expected warning + type must get encountered. Otherwise, even one expected warning + results in success. + + .. versionadded:: 3.0.0 Examples -------- @@ -99,13 +109,28 @@ class for all warnings. To raise multiple types of exceptions, yield w finally: if expected_warning: - expected_warning = cast(type[Warning], expected_warning) - _assert_caught_expected_warning( - caught_warnings=w, - expected_warning=expected_warning, - match=match, - check_stacklevel=check_stacklevel, - ) + if type(expected_warning) == tuple and must_find_all_warnings: + match = ( + match + if type(match) == tuple + else tuple(match for i in range(len(expected_warning))) + ) + for warning_type, warning_match in zip(expected_warning, match): + _assert_caught_expected_warning( + caught_warnings=w, + expected_warning=warning_type, + match=warning_match, + check_stacklevel=check_stacklevel, + ) + else: + expected_warning = cast(type[Warning], expected_warning) + match = "|".join(match) if type(match) == tuple else match + _assert_caught_expected_warning( + caught_warnings=w, + expected_warning=expected_warning, + match=match, + check_stacklevel=check_stacklevel, + ) if raise_on_extra_warnings: _assert_caught_no_extra_warnings( caught_warnings=w, From 05bb67d376a545f42e3eff90291ec0f91233679a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Sat, 3 Feb 2024 07:52:00 +0100 Subject: [PATCH 02/13] Fix typing errors --- pandas/_testing/_warnings.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py index bbd6bc0efdb3f..b6d2a2d12cd20 100644 --- a/pandas/_testing/_warnings.py +++ b/pandas/_testing/_warnings.py @@ -113,7 +113,10 @@ class for all warnings. To raise multiple types of exceptions, match = ( match if type(match) == tuple - else tuple(match for i in range(len(expected_warning))) + else tuple( + cast(str | None, match) + for i in range(len(expected_warning)) + ) ) for warning_type, warning_match in zip(expected_warning, match): _assert_caught_expected_warning( @@ -124,7 +127,12 @@ class for all warnings. To raise multiple types of exceptions, ) else: expected_warning = cast(type[Warning], expected_warning) - match = "|".join(match) if type(match) == tuple else match + match = cast( + str, + "|".join(m for m in match if m) + if type(match) == tuple + else match, + ) _assert_caught_expected_warning( caught_warnings=w, expected_warning=expected_warning, From f404767a13fc17f813cc28588e483e90aa04e36f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Sat, 3 Feb 2024 08:31:20 +0100 Subject: [PATCH 03/13] Fix typing errors --- pandas/_testing/_warnings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py index b6d2a2d12cd20..bac34a7d68ffd 100644 --- a/pandas/_testing/_warnings.py +++ b/pandas/_testing/_warnings.py @@ -110,6 +110,7 @@ class for all warnings. To raise multiple types of exceptions, finally: if expected_warning: if type(expected_warning) == tuple and must_find_all_warnings: + expected_warning = cast(tuple[type[Warning]], expected_warning) match = ( match if type(match) == tuple From da262caaa56d5d1039daf4b2fbefa4ffbdcbd863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Wed, 14 Feb 2024 20:41:18 +0100 Subject: [PATCH 04/13] Remove unnecessary documentation --- doc/source/whatsnew/v3.0.0.rst | 2 +- pandas/_testing/_warnings.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index b26e45a927ce4..25163a0f678b0 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -29,8 +29,8 @@ enhancement2 Other enhancements ^^^^^^^^^^^^^^^^^^ - :func:`DataFrame.to_excel` now raises an ``UserWarning`` when the character count in a cell exceeds Excel's limitation of 32767 characters (:issue:`56954`) -- :func:`assert_produces_warning` now accepts warning message match for every expected warning. A must_find_all_warnings keyword argument was added, to make the function check all expected warnings. (:issue:`56555`) - :func:`read_stata` now returns ``datetime64`` resolutions better matching those natively stored in the stata format (:issue:`55642`) +- .. --------------------------------------------------------------------------- .. _whatsnew_300.notable_bug_fixes: diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py index bac34a7d68ffd..6942750bec766 100644 --- a/pandas/_testing/_warnings.py +++ b/pandas/_testing/_warnings.py @@ -79,8 +79,6 @@ class for all warnings. To raise multiple types of exceptions, type must get encountered. Otherwise, even one expected warning results in success. - .. versionadded:: 3.0.0 - Examples -------- >>> import warnings From d47bf11172a6ac206dc20353b33b957c63cf3b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Wed, 14 Feb 2024 20:45:06 +0100 Subject: [PATCH 05/13] Change `assert_produces_warning behavior` to check for all warnings by default --- pandas/_testing/_warnings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py index 6942750bec766..af8a67db6c742 100644 --- a/pandas/_testing/_warnings.py +++ b/pandas/_testing/_warnings.py @@ -33,7 +33,7 @@ def assert_produces_warning( check_stacklevel: bool = True, raise_on_extra_warnings: bool = True, match: str | tuple[str | None, ...] | None = None, - must_find_all_warnings: bool = False, + must_find_all_warnings: bool = True, ) -> Generator[list[warnings.WarningMessage], None, None]: """ Context manager for running code expected to either raise a specific warning, @@ -74,7 +74,7 @@ class for all warnings. To raise multiple types of exceptions, `expected_warning`. If additionally `must_find_all_warnings` is True, each expected warning's message gets matched with a respective match. Otherwise, multiple values get treated as an alternative. - must_find_all_warnings : bool, default False + must_find_all_warnings : bool, default True If True and `expected_warning` is a tuple, each expected warning type must get encountered. Otherwise, even one expected warning results in success. From 9093962b37914640ac11b2dc58fe188ea7a03604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Wed, 14 Feb 2024 20:48:54 +0100 Subject: [PATCH 06/13] Refactor typing --- pandas/_testing/_warnings.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py index af8a67db6c742..332a825e34003 100644 --- a/pandas/_testing/_warnings.py +++ b/pandas/_testing/_warnings.py @@ -107,15 +107,11 @@ class for all warnings. To raise multiple types of exceptions, yield w finally: if expected_warning: - if type(expected_warning) == tuple and must_find_all_warnings: - expected_warning = cast(tuple[type[Warning]], expected_warning) + if isinstance(expected_warning, tuple) and must_find_all_warnings: match = ( match - if type(match) == tuple - else tuple( - cast(str | None, match) - for i in range(len(expected_warning)) - ) + if isinstance(match, tuple) + else tuple(match for i in range(len(expected_warning))) ) for warning_type, warning_match in zip(expected_warning, match): _assert_caught_expected_warning( @@ -126,11 +122,10 @@ class for all warnings. To raise multiple types of exceptions, ) else: expected_warning = cast(type[Warning], expected_warning) - match = cast( - str, + match = ( "|".join(m for m in match if m) - if type(match) == tuple - else match, + if isinstance(match, tuple) + else match ) _assert_caught_expected_warning( caught_warnings=w, From 74b93de1809397b5965c120a531f8343eb899576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Mon, 19 Feb 2024 18:06:49 +0100 Subject: [PATCH 07/13] Fix tests expecting a Warning that is not raised --- pandas/tests/extension/test_sparse.py | 2 +- pandas/tests/io/parser/common/test_inf.py | 2 -- pandas/tests/io/parser/common/test_read_errors.py | 2 -- pandas/tests/io/parser/test_parse_dates.py | 14 +++++++------- pandas/tests/io/parser/usecols/test_parse_dates.py | 4 ++-- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index d8f14383ef114..5f8ecef45c960 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -237,7 +237,7 @@ def test_isna(self, data_missing): tm.assert_equal(sarr.isna(), expected) def test_fillna_limit_backfill(self, data_missing): - warns = (PerformanceWarning, FutureWarning) + warns = FutureWarning with tm.assert_produces_warning(warns, check_stacklevel=False): super().test_fillna_limit_backfill(data_missing) diff --git a/pandas/tests/io/parser/common/test_inf.py b/pandas/tests/io/parser/common/test_inf.py index 74596b178d35d..eb19c9da9f6d0 100644 --- a/pandas/tests/io/parser/common/test_inf.py +++ b/pandas/tests/io/parser/common/test_inf.py @@ -68,8 +68,6 @@ def test_read_csv_with_use_inf_as_na(all_parsers): data = "1.0\nNaN\n3.0" msg = "use_inf_as_na option is deprecated" warn = FutureWarning - if parser.engine == "pyarrow": - warn = (FutureWarning, DeprecationWarning) with tm.assert_produces_warning(warn, match=msg, check_stacklevel=False): with option_context("use_inf_as_na", True): diff --git a/pandas/tests/io/parser/common/test_read_errors.py b/pandas/tests/io/parser/common/test_read_errors.py index f5a724bad4fa2..1bfbe39628fe1 100644 --- a/pandas/tests/io/parser/common/test_read_errors.py +++ b/pandas/tests/io/parser/common/test_read_errors.py @@ -192,7 +192,6 @@ def test_warn_bad_lines(all_parsers): expected_warning = ParserWarning if parser.engine == "pyarrow": match_msg = "Expected 1 columns, but found 3: 1,2,3" - expected_warning = (ParserWarning, DeprecationWarning) with tm.assert_produces_warning( expected_warning, match=match_msg, check_stacklevel=False @@ -311,7 +310,6 @@ def test_on_bad_lines_warn_correct_formatting(all_parsers): expected_warning = ParserWarning if parser.engine == "pyarrow": match_msg = "Expected 2 columns, but found 3: a,b,c" - expected_warning = (ParserWarning, DeprecationWarning) with tm.assert_produces_warning( expected_warning, match=match_msg, check_stacklevel=False diff --git a/pandas/tests/io/parser/test_parse_dates.py b/pandas/tests/io/parser/test_parse_dates.py index f02dd997e62d0..f2ac7ce7abe7d 100644 --- a/pandas/tests/io/parser/test_parse_dates.py +++ b/pandas/tests/io/parser/test_parse_dates.py @@ -343,7 +343,7 @@ def test_multiple_date_col(all_parsers, keep_date_col, request): "names": ["X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7", "X8"], } with tm.assert_produces_warning( - (DeprecationWarning, FutureWarning), match=depr_msg, check_stacklevel=False + FutureWarning, match=depr_msg, check_stacklevel=False ): result = parser.read_csv(StringIO(data), **kwds) @@ -724,7 +724,7 @@ def test_multiple_date_col_name_collision(all_parsers, data, parse_dates, msg): ) with pytest.raises(ValueError, match=msg): with tm.assert_produces_warning( - (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False + FutureWarning, match=depr_msg, check_stacklevel=False ): parser.read_csv(StringIO(data), parse_dates=parse_dates) @@ -1248,14 +1248,14 @@ def test_multiple_date_col_named_index_compat(all_parsers): "Support for nested sequences for 'parse_dates' in pd.read_csv is deprecated" ) with tm.assert_produces_warning( - (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False + FutureWarning, match=depr_msg, check_stacklevel=False ): with_indices = parser.read_csv( StringIO(data), parse_dates={"nominal": [1, 2]}, index_col="nominal" ) with tm.assert_produces_warning( - (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False + FutureWarning, match=depr_msg, check_stacklevel=False ): with_names = parser.read_csv( StringIO(data), @@ -1280,13 +1280,13 @@ def test_multiple_date_col_multiple_index_compat(all_parsers): "Support for nested sequences for 'parse_dates' in pd.read_csv is deprecated" ) with tm.assert_produces_warning( - (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False + FutureWarning, match=depr_msg, check_stacklevel=False ): result = parser.read_csv( StringIO(data), index_col=["nominal", "ID"], parse_dates={"nominal": [1, 2]} ) with tm.assert_produces_warning( - (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False + FutureWarning, match=depr_msg, check_stacklevel=False ): expected = parser.read_csv(StringIO(data), parse_dates={"nominal": [1, 2]}) @@ -2267,7 +2267,7 @@ def test_parse_dates_dict_format_two_columns(all_parsers, key, parse_dates): "Support for nested sequences for 'parse_dates' in pd.read_csv is deprecated" ) with tm.assert_produces_warning( - (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False + FutureWarning, match=depr_msg, check_stacklevel=False ): result = parser.read_csv( StringIO(data), date_format={key: "%d- %m-%Y"}, parse_dates=parse_dates diff --git a/pandas/tests/io/parser/usecols/test_parse_dates.py b/pandas/tests/io/parser/usecols/test_parse_dates.py index bc66189ca064e..eb61fc2267beb 100644 --- a/pandas/tests/io/parser/usecols/test_parse_dates.py +++ b/pandas/tests/io/parser/usecols/test_parse_dates.py @@ -145,7 +145,7 @@ def test_usecols_with_parse_dates4(all_parsers): "Support for nested sequences for 'parse_dates' in pd.read_csv is deprecated" ) with tm.assert_produces_warning( - (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False + FutureWarning, match=depr_msg, check_stacklevel=False ): result = parser.read_csv( StringIO(data), @@ -186,7 +186,7 @@ def test_usecols_with_parse_dates_and_names(all_parsers, usecols, names, request "Support for nested sequences for 'parse_dates' in pd.read_csv is deprecated" ) with tm.assert_produces_warning( - (FutureWarning, DeprecationWarning), match=depr_msg, check_stacklevel=False + FutureWarning, match=depr_msg, check_stacklevel=False ): result = parser.read_csv( StringIO(s), names=names, parse_dates=parse_dates, usecols=usecols From 22427961d3c0e28775162b082ce635a0d21f74cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Mon, 19 Feb 2024 19:45:09 +0100 Subject: [PATCH 08/13] Adjust `raises_chained_assignment_error` and its dependencies to the new API of `assert_produces_warning` --- pandas/_testing/contexts.py | 4 ++-- .../tests/copy_view/test_chained_assignment_deprecation.py | 5 ++++- pandas/tests/frame/methods/test_interpolate.py | 5 ++--- pandas/tests/indexing/test_chaining_and_caching.py | 4 +++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/pandas/_testing/contexts.py b/pandas/_testing/contexts.py index eb6e4a917889a..833dd1630591f 100644 --- a/pandas/_testing/contexts.py +++ b/pandas/_testing/contexts.py @@ -210,7 +210,7 @@ def raises_chained_assignment_error(warn=True, extra_warnings=(), extra_match=() elif PYPY and extra_warnings: return assert_produces_warning( extra_warnings, - match="|".join(extra_match), + match=extra_match, ) else: if using_copy_on_write(): @@ -227,7 +227,7 @@ def raises_chained_assignment_error(warn=True, extra_warnings=(), extra_match=() warning = (warning, *extra_warnings) # type: ignore[assignment] return assert_produces_warning( warning, - match="|".join((match, *extra_match)), + match=(match, *extra_match), ) diff --git a/pandas/tests/copy_view/test_chained_assignment_deprecation.py b/pandas/tests/copy_view/test_chained_assignment_deprecation.py index 0a37f6b813e55..1d509ca03f463 100644 --- a/pandas/tests/copy_view/test_chained_assignment_deprecation.py +++ b/pandas/tests/copy_view/test_chained_assignment_deprecation.py @@ -170,5 +170,8 @@ def test_frame_setitem(indexer, using_copy_on_write): extra_warnings = () if using_copy_on_write else (SettingWithCopyWarning,) with option_context("chained_assignment", "warn"): - with tm.raises_chained_assignment_error(extra_warnings=extra_warnings): + with tm.raises_chained_assignment_error( + extra_warnings=extra_warnings, + extra_match=(None for _ in range(len(extra_warnings))), + ): df[0:3][indexer] = 10 diff --git a/pandas/tests/frame/methods/test_interpolate.py b/pandas/tests/frame/methods/test_interpolate.py index 5eb9aee2ffb15..7860ae9b78d82 100644 --- a/pandas/tests/frame/methods/test_interpolate.py +++ b/pandas/tests/frame/methods/test_interpolate.py @@ -3,7 +3,6 @@ from pandas._config import using_pyarrow_string_dtype -from pandas.errors import ChainedAssignmentError import pandas.util._test_decorators as td from pandas import ( @@ -392,8 +391,8 @@ def test_interp_inplace(self, using_copy_on_write): msg = "The 'downcast' keyword in Series.interpolate is deprecated" if using_copy_on_write: - with tm.assert_produces_warning( - (FutureWarning, ChainedAssignmentError), match=msg + with tm.raises_chained_assignment_error( + extra_warnings=(FutureWarning,), extra_match=(msg,) ): return_value = result["a"].interpolate(inplace=True, downcast="infer") assert return_value is None diff --git a/pandas/tests/indexing/test_chaining_and_caching.py b/pandas/tests/indexing/test_chaining_and_caching.py index 5acfb72c4a666..c9b763bdf3860 100644 --- a/pandas/tests/indexing/test_chaining_and_caching.py +++ b/pandas/tests/indexing/test_chaining_and_caching.py @@ -457,7 +457,9 @@ def test_detect_chained_assignment_changing_dtype( with tm.raises_chained_assignment_error(): df.loc[2]["C"] = "foo" tm.assert_frame_equal(df, df_original) - with tm.raises_chained_assignment_error(extra_warnings=(FutureWarning,)): + with tm.raises_chained_assignment_error( + extra_warnings=(FutureWarning,), extra_match=(None,) + ): df["C"][2] = "foo" if using_copy_on_write: tm.assert_frame_equal(df, df_original) From a3b18abdb73807a8383231d624b48dee9102320f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Sat, 24 Feb 2024 01:52:06 +0100 Subject: [PATCH 09/13] Fix `_assert_caught_expected_warning` typing not including tuple of warnings --- pandas/_testing/_warnings.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py index a275b1ada0495..a1195f548ae87 100644 --- a/pandas/_testing/_warnings.py +++ b/pandas/_testing/_warnings.py @@ -112,20 +112,20 @@ class for all warnings. To raise multiple types of exceptions, else tuple(match for i in range(len(expected_warning))) ) for warning_type, warning_match in zip(expected_warning, match): - _assert_caught_expected_warning( + _assert_caught_expected_warnings( caught_warnings=w, expected_warning=warning_type, match=warning_match, check_stacklevel=check_stacklevel, ) else: - expected_warning = cast(type[Warning], expected_warning) + expected_warning = cast(type[Warning] | tuple[type[Warning], ...], expected_warning) match = ( "|".join(m for m in match if m) if isinstance(match, tuple) else match ) - _assert_caught_expected_warning( + _assert_caught_expected_warnings( caught_warnings=w, expected_warning=expected_warning, match=match, @@ -150,10 +150,10 @@ def maybe_produces_warning( return nullcontext() -def _assert_caught_expected_warning( +def _assert_caught_expected_warnings( *, caught_warnings: Sequence[warnings.WarningMessage], - expected_warning: type[Warning], + expected_warning: type[Warning] | tuple[type[Warning], ...], match: str | None, check_stacklevel: bool, ) -> None: @@ -161,6 +161,11 @@ def _assert_caught_expected_warning( saw_warning = False matched_message = False unmatched_messages = [] + warning_name = ( + tuple(x.__name__ for x in expected_warning) + if isinstance(expected_warning, tuple) + else expected_warning.__name__ + ) for actual_warning in caught_warnings: if issubclass(actual_warning.category, expected_warning): @@ -177,12 +182,12 @@ def _assert_caught_expected_warning( if not saw_warning: raise AssertionError( - f"Did not see expected warning of class {expected_warning.__name__!r}" + f"Did not see expected warning of class {warning_name!r}" ) if match and not matched_message: raise AssertionError( - f"Did not see warning {expected_warning.__name__!r} " + f"Did not see warning {warning_name!r} " f"matching '{match}'. The emitted warning messages are " f"{unmatched_messages}" ) From e5020ca30a492cb5c1253f436f8f373b1bfcb7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Sat, 24 Feb 2024 02:21:53 +0100 Subject: [PATCH 10/13] fixup! Refactor typing --- pandas/_testing/_warnings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py index a1195f548ae87..b72958e86a111 100644 --- a/pandas/_testing/_warnings.py +++ b/pandas/_testing/_warnings.py @@ -109,7 +109,7 @@ class for all warnings. To raise multiple types of exceptions, match = ( match if isinstance(match, tuple) - else tuple(match for i in range(len(expected_warning))) + else (match,) * len(expected_warning) ) for warning_type, warning_match in zip(expected_warning, match): _assert_caught_expected_warnings( From a21cfc909ae4d819bd4c1863d362cb29fd6678c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Sat, 24 Feb 2024 02:30:21 +0100 Subject: [PATCH 11/13] fixup! Fix `_assert_caught_expected_warning` typing not including tuple of warnings --- pandas/_testing/_warnings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py index b72958e86a111..566c2b051779f 100644 --- a/pandas/_testing/_warnings.py +++ b/pandas/_testing/_warnings.py @@ -119,7 +119,9 @@ class for all warnings. To raise multiple types of exceptions, check_stacklevel=check_stacklevel, ) else: - expected_warning = cast(type[Warning] | tuple[type[Warning], ...], expected_warning) + expected_warning = cast( + type[Warning] | tuple[type[Warning], ...], expected_warning + ) match = ( "|".join(m for m in match if m) if isinstance(match, tuple) @@ -181,9 +183,7 @@ def _assert_caught_expected_warnings( unmatched_messages.append(actual_warning.message) if not saw_warning: - raise AssertionError( - f"Did not see expected warning of class {warning_name!r}" - ) + raise AssertionError(f"Did not see expected warning of class {warning_name!r}") if match and not matched_message: raise AssertionError( From 2fadce208993e2ad894d66006934f1152a501cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20B=C5=82a=C5=BCejowski?= <56088851+Jorewin@users.noreply.github.com> Date: Tue, 27 Feb 2024 20:23:32 +0100 Subject: [PATCH 12/13] fixup! Fix `_assert_caught_expected_warning` typing not including tuple of warnings --- pandas/_testing/_warnings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py index 566c2b051779f..cd2e2b4141ffd 100644 --- a/pandas/_testing/_warnings.py +++ b/pandas/_testing/_warnings.py @@ -11,6 +11,7 @@ from typing import ( TYPE_CHECKING, Literal, + Union, cast, ) import warnings @@ -120,7 +121,8 @@ class for all warnings. To raise multiple types of exceptions, ) else: expected_warning = cast( - type[Warning] | tuple[type[Warning], ...], expected_warning + Union[type[Warning], tuple[type[Warning], ...]], + expected_warning, ) match = ( "|".join(m for m in match if m) From d13e876b6dc33c70b452e76e5a6f7db9e5113bc6 Mon Sep 17 00:00:00 2001 From: Richard Shadrach Date: Sat, 13 Apr 2024 08:02:23 -0400 Subject: [PATCH 13/13] Add tests --- .../util/test_assert_produces_warning.py | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/pandas/tests/util/test_assert_produces_warning.py b/pandas/tests/util/test_assert_produces_warning.py index 88e9f0d8fccee..15afea0ceaf81 100644 --- a/pandas/tests/util/test_assert_produces_warning.py +++ b/pandas/tests/util/test_assert_produces_warning.py @@ -41,7 +41,6 @@ def f(): warnings.warn("f2", RuntimeWarning) -@pytest.mark.filterwarnings("ignore:f1:FutureWarning") def test_assert_produces_warning_honors_filter(): # Raise by default. msg = r"Caused unexpected warning\(s\)" @@ -179,6 +178,44 @@ def test_match_multiple_warnings(): warnings.warn("Match this too", UserWarning) +def test_must_match_multiple_warnings(): + # https://github.com/pandas-dev/pandas/issues/56555 + category = (FutureWarning, UserWarning) + msg = "Did not see expected warning of class 'UserWarning'" + with pytest.raises(AssertionError, match=msg): + with tm.assert_produces_warning(category, match=r"^Match this"): + warnings.warn("Match this", FutureWarning) + + +def test_must_match_multiple_warnings_messages(): + # https://github.com/pandas-dev/pandas/issues/56555 + category = (FutureWarning, UserWarning) + msg = r"The emitted warning messages are \[UserWarning\('Not this'\)\]" + with pytest.raises(AssertionError, match=msg): + with tm.assert_produces_warning(category, match=r"^Match this"): + warnings.warn("Match this", FutureWarning) + warnings.warn("Not this", UserWarning) + + +def test_allow_partial_match_for_multiple_warnings(): + # https://github.com/pandas-dev/pandas/issues/56555 + category = (FutureWarning, UserWarning) + with tm.assert_produces_warning( + category, match=r"^Match this", must_find_all_warnings=False + ): + warnings.warn("Match this", FutureWarning) + + +def test_allow_partial_match_for_multiple_warnings_messages(): + # https://github.com/pandas-dev/pandas/issues/56555 + category = (FutureWarning, UserWarning) + with tm.assert_produces_warning( + category, match=r"^Match this", must_find_all_warnings=False + ): + warnings.warn("Match this", FutureWarning) + warnings.warn("Not this", UserWarning) + + def test_right_category_wrong_match_raises(pair_different_warnings): target_category, other_category = pair_different_warnings with pytest.raises(AssertionError, match="Did not see warning.*matching"):