diff --git a/doc/source/development/contributing_codebase.rst b/doc/source/development/contributing_codebase.rst index c74c44fb1d5f0..bc85a54e61f22 100644 --- a/doc/source/development/contributing_codebase.rst +++ b/doc/source/development/contributing_codebase.rst @@ -122,6 +122,7 @@ Otherwise, you need to do it manually: .. code-block:: python import warnings + from pandas.util._exceptions import find_stack_level def old_func(): @@ -130,7 +131,11 @@ Otherwise, you need to do it manually: .. deprecated:: 1.1.0 Use new_func instead. """ - warnings.warn('Use new_func instead.', FutureWarning, stacklevel=2) + warnings.warn( + 'Use new_func instead.', + FutureWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) new_func() diff --git a/pandas/_config/config.py b/pandas/_config/config.py index d5e77d824340d..fc35b95bba7dd 100644 --- a/pandas/_config/config.py +++ b/pandas/_config/config.py @@ -54,6 +54,7 @@ ContextDecorator, contextmanager, ) +import inspect import re from typing import ( Any, @@ -70,6 +71,7 @@ F, T, ) +from pandas.util._exceptions import find_stack_level class DeprecatedOption(NamedTuple): @@ -657,7 +659,11 @@ def _warn_if_deprecated(key: str) -> bool: d = _get_deprecated_option(key) if d: if d.msg: - warnings.warn(d.msg, FutureWarning) + warnings.warn( + d.msg, + FutureWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) else: msg = f"'{key}' is deprecated" if d.removal_ver: @@ -667,7 +673,9 @@ def _warn_if_deprecated(key: str) -> bool: else: msg += ", please refrain from using it." - warnings.warn(msg, FutureWarning) + warnings.warn( + msg, FutureWarning, stacklevel=find_stack_level(inspect.currentframe()) + ) return True return False diff --git a/pandas/_libs/interval.pyx b/pandas/_libs/interval.pyx index 7cacc8cc639f7..2982110ea35cc 100644 --- a/pandas/_libs/interval.pyx +++ b/pandas/_libs/interval.pyx @@ -230,7 +230,7 @@ def _warning_interval(inclusive: str | None = None, closed: None | lib.NoDefault warnings.warn( "Argument `closed` is deprecated in favor of `inclusive`.", FutureWarning, - stacklevel=2, + stacklevel=find_stack_level(inspect.currentframe()), ) if closed is None: inclusive = "right" diff --git a/pandas/_libs/lib.pyx b/pandas/_libs/lib.pyx index c90c9003c8d60..8e4b23f32f48c 100644 --- a/pandas/_libs/lib.pyx +++ b/pandas/_libs/lib.pyx @@ -1,6 +1,7 @@ from collections import abc from decimal import Decimal from enum import Enum +import inspect from typing import Literal import warnings @@ -30,6 +31,8 @@ from cython cimport ( floating, ) +from pandas.util._exceptions import find_stack_level + import_datetime() import numpy as np @@ -352,6 +355,7 @@ def fast_unique_multiple(list arrays, sort: bool = True): "The values in the array are unorderable. " "Pass `sort=False` to suppress this warning.", RuntimeWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) pass diff --git a/pandas/_libs/parsers.pyx b/pandas/_libs/parsers.pyx index b07fa143c98b6..e8b7160af9b2c 100644 --- a/pandas/_libs/parsers.pyx +++ b/pandas/_libs/parsers.pyx @@ -8,10 +8,13 @@ from csv import ( QUOTE_NONNUMERIC, ) from errno import ENOENT +import inspect import sys import time import warnings +from pandas.util._exceptions import find_stack_level + cimport cython from cpython.bytes cimport ( PyBytes_AsString, @@ -958,7 +961,7 @@ cdef class TextReader: "Defining usecols with out of bounds indices is deprecated " "and will raise a ParserError in a future version.", FutureWarning, - stacklevel=6, + stacklevel=find_stack_level(inspect.currentframe()), ) results = {} @@ -1009,7 +1012,7 @@ cdef class TextReader: warnings.warn((f"Both a converter and dtype were specified " f"for column {name} - only the converter will " f"be used."), ParserWarning, - stacklevel=5) + stacklevel=find_stack_level(inspect.currentframe())) results[i] = _apply_converter(conv, self.parser, i, start, end) continue diff --git a/pandas/_libs/tslib.pyx b/pandas/_libs/tslib.pyx index 55057ff628619..598e6b552e49b 100644 --- a/pandas/_libs/tslib.pyx +++ b/pandas/_libs/tslib.pyx @@ -1,3 +1,4 @@ +import inspect import warnings cimport cython @@ -9,6 +10,8 @@ from cpython.datetime cimport ( tzinfo, ) +from pandas.util._exceptions import find_stack_level + # import datetime C API import_datetime() @@ -845,7 +848,7 @@ cdef inline bint _parse_today_now(str val, int64_t* iresult, bint utc): "deprecated. In a future version, this will match Timestamp('now') " "and Timestamp.now()", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return True diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 0dfb859a3444f..b25095ead790b 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -1,9 +1,13 @@ +import inspect + cimport cython import warnings import numpy as np +from pandas.util._exceptions import find_stack_level + cimport numpy as cnp from cpython.object cimport PyObject from numpy cimport ( @@ -287,7 +291,7 @@ cdef _TSObject convert_to_tsobject(object ts, tzinfo tz, str unit, "Conversion of non-round float with unit={unit} is ambiguous " "and will raise in a future version.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) ts = cast_from_unit(ts, unit) diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx index b3dd5b7907cad..909541d24121e 100644 --- a/pandas/_libs/tslibs/nattype.pyx +++ b/pandas/_libs/tslibs/nattype.pyx @@ -1,5 +1,8 @@ +import inspect import warnings +from pandas.util._exceptions import find_stack_level + from cpython.datetime cimport ( PyDate_Check, PyDateTime_Check, @@ -135,7 +138,7 @@ cdef class _NaT(datetime): "order to match the standard library behavior. " "In a future version these will be considered non-comparable.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return False @@ -379,7 +382,7 @@ class NaTType(_NaT): warnings.warn( "NaT.freq is deprecated and will be removed in a future version.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return None diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index d553c0d77bc56..d799770a57be2 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -1,8 +1,11 @@ +import inspect import operator import re import time import warnings +from pandas.util._exceptions import find_stack_level + cimport cython from cpython.datetime cimport ( PyDate_Check, @@ -499,7 +502,7 @@ cdef class BaseOffset: "DateOffset.__call__ is deprecated and will be removed in a future " "version. Use `offset + other` instead.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return self._apply(other) @@ -509,7 +512,7 @@ cdef class BaseOffset: f"{type(self).__name__}.apply is deprecated and will be removed " "in a future version. Use `offset + other` instead", FutureWarning, - stacklevel=2, + stacklevel=find_stack_level(inspect.currentframe()), ) return self._apply(other) @@ -820,7 +823,7 @@ cdef class BaseOffset: warnings.warn( "onOffset is a deprecated, use is_on_offset instead.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return self.is_on_offset(dt) @@ -828,7 +831,7 @@ cdef class BaseOffset: warnings.warn( "isAnchored is a deprecated, use is_anchored instead.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return self.is_anchored() diff --git a/pandas/_libs/tslibs/parsing.pyx b/pandas/_libs/tslibs/parsing.pyx index 8c223020c4012..b442e32071011 100644 --- a/pandas/_libs/tslibs/parsing.pyx +++ b/pandas/_libs/tslibs/parsing.pyx @@ -1,10 +1,13 @@ """ Parsing functions for datetime and datetime-like strings. """ +import inspect import re import time import warnings +from pandas.util._exceptions import find_stack_level + cimport cython from cpython.datetime cimport ( datetime, @@ -214,7 +217,7 @@ cdef inline object _parse_delimited_date(str date_string, bint dayfirst): format='MM/DD/YYYY', dayfirst='True', ), - stacklevel=4, + stacklevel=find_stack_level(inspect.currentframe()), ) elif not dayfirst and swapped_day_and_month: warnings.warn( @@ -222,7 +225,7 @@ cdef inline object _parse_delimited_date(str date_string, bint dayfirst): format='DD/MM/YYYY', dayfirst='False (the default)', ), - stacklevel=4, + stacklevel=find_stack_level(inspect.currentframe()), ) # In Python <= 3.6.0 there is no range checking for invalid dates # in C api, thus we call faster C version for 3.6.1 or newer diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index fa264f29aa8a8..d2d4838bfafc0 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1,5 +1,8 @@ +import inspect import warnings +from pandas.util._exceptions import find_stack_level + cimport numpy as cnp from cpython.object cimport ( Py_EQ, @@ -1827,7 +1830,7 @@ cdef class _Period(PeriodMixin): "be removed in a future version. Use " "`per.to_timestamp(...).tz_localize(tz)` instead.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) how = validate_end_alias(how) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 215d1c9d6c722..f53d4ccf2d555 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -1,6 +1,9 @@ import collections +import inspect import warnings +from pandas.util._exceptions import find_stack_level + cimport cython from cpython.object cimport ( Py_EQ, @@ -683,7 +686,7 @@ cdef inline timedelta_from_spec(object number, object frac, object unit): "Units 'M', 'Y' and 'y' do not represent unambiguous " "timedelta values and will be removed in a future version.", FutureWarning, - stacklevel=3, + stacklevel=find_stack_level(inspect.currentframe()), ) if unit == 'M': @@ -1055,7 +1058,7 @@ cdef class _Timedelta(timedelta): warnings.warn( "Timedelta.freq is deprecated and will be removed in a future version", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return None @@ -1065,7 +1068,7 @@ cdef class _Timedelta(timedelta): warnings.warn( "Timedelta.is_populated is deprecated and will be removed in a future version", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return self._is_populated @@ -1269,7 +1272,7 @@ cdef class _Timedelta(timedelta): warnings.warn( "Timedelta.delta is deprecated and will be removed in a future version.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return self.value diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 66d848ba43da9..2655c25ed0893 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -6,6 +6,7 @@ construction requirements, we need to do object instantiation in python (see Timestamp class below). This will serve as a C extension type that shadows the python class, where we do any heavy lifting. """ +import inspect import warnings cimport cython @@ -47,6 +48,9 @@ import_datetime() from pandas._libs.tslibs cimport ccalendar from pandas._libs.tslibs.base cimport ABCTimestamp + +from pandas.util._exceptions import find_stack_level + from pandas._libs.tslibs.conversion cimport ( _TSObject, convert_datetime_to_tsobject, @@ -253,7 +257,7 @@ cdef class _Timestamp(ABCTimestamp): warnings.warn( "Timestamp.freq is deprecated and will be removed in a future version.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return self._freq @@ -365,7 +369,7 @@ cdef class _Timestamp(ABCTimestamp): "In a future version these will be considered non-comparable. " "Use 'ts == pd.Timestamp(date)' or 'ts.date() == date' instead.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return NotImplemented else: @@ -666,7 +670,7 @@ cdef class _Timestamp(ABCTimestamp): "version. When you have a freq, use " f"freq.{field}(timestamp) instead.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) @property @@ -1172,7 +1176,7 @@ cdef class _Timestamp(ABCTimestamp): """ if self.nanosecond != 0 and warn: warnings.warn("Discarding nonzero nanoseconds in conversion.", - UserWarning, stacklevel=2) + UserWarning, stacklevel=find_stack_level(inspect.currentframe())) return datetime(self.year, self.month, self.day, self.hour, self.minute, self.second, @@ -1251,6 +1255,7 @@ cdef class _Timestamp(ABCTimestamp): warnings.warn( "Converting to Period representation will drop timezone information.", UserWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) if freq is None: @@ -1259,7 +1264,7 @@ cdef class _Timestamp(ABCTimestamp): "In a future version, calling 'Timestamp.to_period()' without " "passing a 'freq' will raise an exception.", FutureWarning, - stacklevel=2, + stacklevel=find_stack_level(inspect.currentframe()), ) return Period(self, freq=freq) @@ -1451,7 +1456,7 @@ class Timestamp(_Timestamp): "Timestamp.utcfromtimestamp(ts).tz_localize(None). " "To get the future behavior, use Timestamp.fromtimestamp(ts, 'UTC')", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return cls(datetime.utcfromtimestamp(ts)) @@ -1687,7 +1692,7 @@ class Timestamp(_Timestamp): "as a wall time, not a UTC time. To interpret as a UTC time, " "use `Timestamp(dt64).tz_localize('UTC').tz_convert(tz)`", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) # Once this deprecation is enforced, we can do # return Timestamp(ts_input).tz_localize(tzobj) @@ -1704,7 +1709,7 @@ class Timestamp(_Timestamp): "The 'freq' argument in Timestamp is deprecated and will be " "removed in a future version.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) if not is_offset_object(freq): freq = to_offset(freq) @@ -2040,7 +2045,7 @@ timedelta}, default 'raise' warnings.warn( "Timestamp.freqstr is deprecated and will be removed in a future version.", FutureWarning, - stacklevel=1, + stacklevel=find_stack_level(inspect.currentframe()), ) return self._freqstr diff --git a/pandas/_testing/_warnings.py b/pandas/_testing/_warnings.py index e9df85eae550a..a5b0d1e199863 100644 --- a/pandas/_testing/_warnings.py +++ b/pandas/_testing/_warnings.py @@ -130,9 +130,7 @@ def _assert_caught_expected_warning( if issubclass(actual_warning.category, expected_warning): saw_warning = True - if check_stacklevel and issubclass( - actual_warning.category, (FutureWarning, DeprecationWarning) - ): + if check_stacklevel: _assert_raised_with_correct_stacklevel(actual_warning) if match is not None: diff --git a/pandas/compat/_optional.py b/pandas/compat/_optional.py index 3801a1648f1e7..c2d1927bccfff 100644 --- a/pandas/compat/_optional.py +++ b/pandas/compat/_optional.py @@ -1,10 +1,13 @@ from __future__ import annotations import importlib +import inspect import sys import types import warnings +from pandas.util._exceptions import find_stack_level + from pandas.util.version import Version # Update install.rst when updating versions! @@ -159,7 +162,11 @@ def import_optional_dependency( f"(version '{version}' currently installed)." ) if errors == "warn": - warnings.warn(msg, UserWarning) + warnings.warn( + msg, + UserWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) return None elif errors == "raise": raise ImportError(msg) diff --git a/pandas/core/algorithms.py b/pandas/core/algorithms.py index a4736c2a141a5..7222e465cb710 100644 --- a/pandas/core/algorithms.py +++ b/pandas/core/algorithms.py @@ -1036,7 +1036,10 @@ def mode( try: npresult = np.sort(npresult) except TypeError as err: - warnings.warn(f"Unable to sort modes: {err}") + warnings.warn( + f"Unable to sort modes: {err}", + stacklevel=find_stack_level(inspect.currentframe()), + ) result = _reconstruct_data(npresult, original.dtype, original) return result diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index 0d228582992d2..fff7772c2cf7d 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -1011,7 +1011,7 @@ def set_categories( "a future version. Removing unused categories will always " "return a new Categorical object.", FutureWarning, - stacklevel=2, + stacklevel=find_stack_level(inspect.currentframe()), ) else: inplace = False diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 2c070499308a7..253b582eddf2c 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1373,6 +1373,7 @@ def _addsub_object_array(self, other: np.ndarray, op): "Adding/subtracting object-dtype array to " f"{type(self).__name__} not vectorized.", PerformanceWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) # Caller is responsible for broadcasting if necessary diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 58dee30288be9..64c15df64de3b 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -702,6 +702,7 @@ def _add_offset(self, offset) -> DatetimeArray: warnings.warn( "Non-vectorized DateOffset being applied to Series or DatetimeIndex.", PerformanceWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) result = self.astype("O") + offset result = type(self)._from_sequence(result) @@ -1099,6 +1100,7 @@ def to_period(self, freq=None) -> PeriodArray: "Converting to PeriodArray/Index representation " "will drop timezone information.", UserWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) if freq is None: diff --git a/pandas/core/arrays/sparse/array.py b/pandas/core/arrays/sparse/array.py index e9302efdce2e7..13ebd2b25e949 100644 --- a/pandas/core/arrays/sparse/array.py +++ b/pandas/core/arrays/sparse/array.py @@ -778,7 +778,11 @@ def fillna( elif method is not None: msg = "fillna with 'method' requires high memory usage." - warnings.warn(msg, PerformanceWarning) + warnings.warn( + msg, + PerformanceWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) new_values = np.asarray(self) # interpolate_2d modifies new_values inplace interpolate_2d(new_values, method=method, limit=limit) diff --git a/pandas/core/computation/expressions.py b/pandas/core/computation/expressions.py index e82bec47c6ac5..a4b83fc557413 100644 --- a/pandas/core/computation/expressions.py +++ b/pandas/core/computation/expressions.py @@ -7,6 +7,7 @@ """ from __future__ import annotations +import inspect import operator import warnings @@ -15,6 +16,7 @@ from pandas._config import get_option from pandas._typing import FuncType +from pandas.util._exceptions import find_stack_level from pandas.core.computation.check import NUMEXPR_INSTALLED from pandas.core.ops import roperator @@ -214,7 +216,8 @@ def _bool_arith_fallback(op_str, a, b): warnings.warn( f"evaluating in Python space because the {repr(op_str)} " "operator is not supported by numexpr for the bool dtype, " - f"use {repr(_BOOL_OP_UNSUPPORTED[op_str])} instead." + f"use {repr(_BOOL_OP_UNSUPPORTED[op_str])} instead.", + stacklevel=find_stack_level(inspect.currentframe()), ) return True return False diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 45b839f175a88..7ed6e0d84445c 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2079,7 +2079,7 @@ def __array_wrap__( "The __array_wrap__ method of DataFrame and Series will be removed in " "a future version", DeprecationWarning, - stacklevel=2, + stacklevel=find_stack_level(inspect.currentframe()), ) res = lib.item_from_zerodim(result) if is_scalar(res): diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 1383f850ab043..816260c8a6d2d 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -7437,7 +7437,7 @@ def _maybe_cast_data_without_dtype( "In a future version, the Index constructor will not infer numeric " "dtypes when passed object-dtype sequences (matching Series behavior)", FutureWarning, - stacklevel=3, + stacklevel=find_stack_level(inspect.currentframe()), ) result = ensure_wrapped_if_datetimelike(result) return result diff --git a/pandas/core/reshape/merge.py b/pandas/core/reshape/merge.py index 50aaac211c7a5..ccaf37bd98b85 100644 --- a/pandas/core/reshape/merge.py +++ b/pandas/core/reshape/merge.py @@ -1293,6 +1293,7 @@ def _maybe_coerce_merge_keys(self) -> None: "columns where the float values " "are not equal to their int representation.", UserWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) continue @@ -1305,6 +1306,7 @@ def _maybe_coerce_merge_keys(self) -> None: "columns where the float values " "are not equal to their int representation.", UserWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) continue diff --git a/pandas/core/reshape/reshape.py b/pandas/core/reshape/reshape.py index 52b059f6b92af..0270a5dd75952 100644 --- a/pandas/core/reshape/reshape.py +++ b/pandas/core/reshape/reshape.py @@ -1,5 +1,6 @@ from __future__ import annotations +import inspect import itertools from typing import ( TYPE_CHECKING, @@ -13,6 +14,7 @@ from pandas._typing import npt from pandas.errors import PerformanceWarning from pandas.util._decorators import cache_readonly +from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.cast import maybe_promote from pandas.core.dtypes.common import ( @@ -130,6 +132,7 @@ def __init__(self, index: MultiIndex, level=-1, constructor=None) -> None: f"The following operation may generate {num_cells} cells " f"in the resulting pandas object.", PerformanceWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) self._make_selectors() diff --git a/pandas/io/clipboard/__init__.py b/pandas/io/clipboard/__init__.py index 27fb06dfb6023..03599497f8d03 100644 --- a/pandas/io/clipboard/__init__.py +++ b/pandas/io/clipboard/__init__.py @@ -40,8 +40,11 @@ Pyperclip into running them with whatever permissions the Python process has. """ +import inspect + __version__ = "1.7.0" + import contextlib import ctypes from ctypes import ( @@ -62,6 +65,7 @@ PyperclipException, PyperclipWindowsException, ) +from pandas.util._exceptions import find_stack_level # `import PyQt4` sys.exit()s if DISPLAY is not in the environment. # Thus, we need to detect the presence of $DISPLAY manually @@ -270,10 +274,14 @@ def copy_dev_clipboard(text): if text == "": warnings.warn( "Pyperclip cannot copy a blank string to the clipboard on Cygwin. " - "This is effectively a no-op." + "This is effectively a no-op.", + stacklevel=find_stack_level(inspect.currentframe()), ) if "\r" in text: - warnings.warn("Pyperclip cannot handle \\r characters on Cygwin.") + warnings.warn( + "Pyperclip cannot handle \\r characters on Cygwin.", + stacklevel=find_stack_level(inspect.currentframe()), + ) with open("/dev/clipboard", "wt") as fd: fd.write(text) @@ -517,7 +525,8 @@ def determine_clipboard(): if os.path.exists("/dev/clipboard"): warnings.warn( "Pyperclip's support for Cygwin is not perfect, " - "see https://github.com/asweigart/pyperclip/issues/55" + "see https://github.com/asweigart/pyperclip/issues/55", + stacklevel=find_stack_level(inspect.currentframe()), ) return init_dev_clipboard_clipboard() diff --git a/pandas/io/clipboards.py b/pandas/io/clipboards.py index 0968f1facf128..7cf01affd5a19 100644 --- a/pandas/io/clipboards.py +++ b/pandas/io/clipboards.py @@ -1,9 +1,12 @@ """ io on the clipboard """ from __future__ import annotations +import inspect from io import StringIO import warnings +from pandas.util._exceptions import find_stack_level + from pandas.core.dtypes.generic import ABCDataFrame from pandas import ( @@ -79,7 +82,8 @@ def read_clipboard(sep: str = r"\s+", **kwargs): # pragma: no cover kwargs["engine"] = "python" elif len(sep) > 1 and kwargs.get("engine") == "c": warnings.warn( - "read_clipboard with regex separator does not work properly with c engine." + "read_clipboard with regex separator does not work properly with c engine.", + stacklevel=find_stack_level(inspect.currentframe()), ) return read_csv(StringIO(text), sep=sep, **kwargs) @@ -135,10 +139,14 @@ def to_clipboard( return except TypeError: warnings.warn( - "to_clipboard in excel mode requires a single character separator." + "to_clipboard in excel mode requires a single character separator.", + stacklevel=find_stack_level(inspect.currentframe()), ) elif sep is not None: - warnings.warn("to_clipboard with excel=False ignores the sep argument.") + warnings.warn( + "to_clipboard with excel=False ignores the sep argument.", + stacklevel=find_stack_level(inspect.currentframe()), + ) if isinstance(obj, ABCDataFrame): # str(df) has various unhelpful defaults, like truncation diff --git a/pandas/io/common.py b/pandas/io/common.py index 7add6ec10222c..2fae19df13f8b 100644 --- a/pandas/io/common.py +++ b/pandas/io/common.py @@ -339,6 +339,7 @@ def _get_filepath_or_buffer( warnings.warn( f"{compression} will not write the byte order mark for {encoding}", UnicodeWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) # Use binary mode when converting path-like objects to file-like objects (fsspec) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 778df087d28d8..e86a1b0bcd635 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -3,6 +3,7 @@ """ from __future__ import annotations +import inspect import re from typing import ( Callable, @@ -13,6 +14,7 @@ import warnings from pandas.errors import CSSWarning +from pandas.util._exceptions import find_stack_level def _side_expander(prop_fmt: str) -> Callable: @@ -46,7 +48,11 @@ def expand(self, prop, value: str) -> Generator[tuple[str, str], None, None]: try: mapping = self.SIDE_SHORTHANDS[len(tokens)] except KeyError: - warnings.warn(f'Could not expand "{prop}: {value}"', CSSWarning) + warnings.warn( + f'Could not expand "{prop}: {value}"', + CSSWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) return for key, idx in zip(self.SIDES, mapping): yield prop_fmt.format(key), tokens[idx] @@ -88,7 +94,9 @@ def expand(self, prop, value: str) -> Generator[tuple[str, str], None, None]: tokens = value.split() if len(tokens) == 0 or len(tokens) > 3: warnings.warn( - f'Too many tokens provided to "{prop}" (expected 1-3)', CSSWarning + f'Too many tokens provided to "{prop}" (expected 1-3)', + CSSWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) # TODO: Can we use current color as initial value to comply with CSS standards? @@ -324,7 +332,11 @@ def _update_other_units(self, props: dict[str, str]) -> dict[str, str]: def size_to_pt(self, in_val, em_pt=None, conversions=UNIT_RATIOS): def _error(): - warnings.warn(f"Unhandled size: {repr(in_val)}", CSSWarning) + warnings.warn( + f"Unhandled size: {repr(in_val)}", + CSSWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) return self.size_to_pt("1!!default", conversions=conversions) match = re.match(r"^(\S*?)([a-zA-Z%!].*)", in_val) @@ -396,4 +408,5 @@ def parse(self, declarations_str: str) -> Iterator[tuple[str, str]]: warnings.warn( f"Ill-formatted attribute: expected a colon in {repr(decl)}", CSSWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 5a7d0a4690bbd..c4ddac088d901 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -7,6 +7,7 @@ lru_cache, reduce, ) +import inspect import itertools import re from typing import ( @@ -28,6 +29,7 @@ StorageOptions, ) from pandas.util._decorators import doc +from pandas.util._exceptions import find_stack_level from pandas.core.dtypes import missing from pandas.core.dtypes.common import ( @@ -427,7 +429,11 @@ def color_to_excel(self, val: str | None) -> str | None: try: return self.NAMED_COLORS[val] except KeyError: - warnings.warn(f"Unhandled color format: {repr(val)}", CSSWarning) + warnings.warn( + f"Unhandled color format: {repr(val)}", + CSSWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) return None def _is_hex_color(self, color_string: str) -> bool: diff --git a/pandas/io/json/_table_schema.py b/pandas/io/json/_table_schema.py index b7a8b5cc82f7a..7c323992d11a0 100644 --- a/pandas/io/json/_table_schema.py +++ b/pandas/io/json/_table_schema.py @@ -5,6 +5,7 @@ """ from __future__ import annotations +import inspect from typing import ( TYPE_CHECKING, Any, @@ -17,6 +18,7 @@ DtypeObj, JSONSerializable, ) +from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.base import _registry as registry from pandas.core.dtypes.common import ( @@ -100,10 +102,14 @@ def set_default_names(data): if com.all_not_none(*data.index.names): nms = data.index.names if len(nms) == 1 and data.index.name == "index": - warnings.warn("Index name of 'index' is not round-trippable.") + warnings.warn( + "Index name of 'index' is not round-trippable.", + stacklevel=find_stack_level(inspect.currentframe()), + ) elif len(nms) > 1 and any(x.startswith("level_") for x in nms): warnings.warn( - "Index names beginning with 'level_' are not round-trippable." + "Index names beginning with 'level_' are not round-trippable.", + stacklevel=find_stack_level(inspect.currentframe()), ) return data diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py index f7d5fb9270247..96ba6b2e84cf3 100644 --- a/pandas/io/pytables.py +++ b/pandas/io/pytables.py @@ -3545,7 +3545,11 @@ def validate_version(self, where=None) -> None: if where is not None: if self.is_old_version: ws = incompatibility_doc % ".".join([str(x) for x in self.version]) - warnings.warn(ws, IncompatibilityWarning) + warnings.warn( + ws, + IncompatibilityWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) def validate_min_itemsize(self, min_itemsize) -> None: """ diff --git a/pandas/io/sas/sas_xport.py b/pandas/io/sas/sas_xport.py index a2e217767d1d4..648c58dee6600 100644 --- a/pandas/io/sas/sas_xport.py +++ b/pandas/io/sas/sas_xport.py @@ -11,6 +11,7 @@ from collections import abc from datetime import datetime +import inspect import struct import warnings @@ -23,6 +24,7 @@ ReadBuffer, ) from pandas.util._decorators import Appender +from pandas.util._exceptions import find_stack_level import pandas as pd @@ -412,7 +414,10 @@ def _record_count(self) -> int: total_records_length = self.filepath_or_buffer.tell() - self.record_start if total_records_length % 80 != 0: - warnings.warn("xport file may be corrupted.") + warnings.warn( + "xport file may be corrupted.", + stacklevel=find_stack_level(inspect.currentframe()), + ) if self.record_length > 80: self.filepath_or_buffer.seek(self.record_start) diff --git a/pandas/io/sql.py b/pandas/io/sql.py index 2b835a1e7ebed..ee6564d103147 100644 --- a/pandas/io/sql.py +++ b/pandas/io/sql.py @@ -762,6 +762,7 @@ def pandasSQL_builder(con, schema: str | None = None) -> SQLDatabase | SQLiteDat "database string URI or sqlite3 DBAPI2 connection. " "Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.", UserWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) return SQLiteDatabase(con) @@ -1655,7 +1656,11 @@ def check_case_sensitive( "due to case sensitivity issues. Consider using lower " "case table names." ) - warnings.warn(msg, UserWarning) + warnings.warn( + msg, + UserWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) def to_sql( self, diff --git a/pandas/io/stata.py b/pandas/io/stata.py index e59b6c8770389..80e7f54d828b5 100644 --- a/pandas/io/stata.py +++ b/pandas/io/stata.py @@ -13,6 +13,7 @@ from collections import abc import datetime +import inspect from io import BytesIO import os import struct @@ -51,6 +52,7 @@ Appender, doc, ) +from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.common import ( ensure_object, @@ -348,7 +350,10 @@ def convert_delta_safe(base, deltas, unit) -> Series: conv_dates = convert_delta_safe(base, ms, "ms") elif fmt.startswith(("%tC", "tC")): - warnings.warn("Encountered %tC format. Leaving in Stata Internal Format.") + warnings.warn( + "Encountered %tC format. Leaving in Stata Internal Format.", + stacklevel=find_stack_level(inspect.currentframe()), + ) conv_dates = Series(dates, dtype=object) if has_bad_values: conv_dates[bad_locs] = NaT @@ -462,7 +467,10 @@ def g(x: datetime.datetime) -> int: d = parse_dates_safe(dates, delta=True) conv_dates = d.delta / 1000 elif fmt in ["%tC", "tC"]: - warnings.warn("Stata Internal Format tC not supported.") + warnings.warn( + "Stata Internal Format tC not supported.", + stacklevel=find_stack_level(inspect.currentframe()), + ) conv_dates = dates elif fmt in ["%td", "td"]: d = parse_dates_safe(dates, delta=True) @@ -642,7 +650,11 @@ def _cast_to_stata_types(data: DataFrame) -> DataFrame: sentinel = StataMissingValue.BASE_MISSING_VALUES[data[col].dtype.name] data.loc[orig_missing, col] = sentinel if ws: - warnings.warn(ws, PossiblePrecisionLoss) + warnings.warn( + ws, + PossiblePrecisionLoss, + stacklevel=find_stack_level(inspect.currentframe()), + ) return data @@ -697,6 +709,7 @@ def _prepare_value_labels(self): warnings.warn( value_label_mismatch_doc.format(self.labname), ValueLabelTypeMismatch, + stacklevel=find_stack_level(inspect.currentframe()), ) category = category.encode(self._encoding) offsets.append(self.text_len) @@ -1506,7 +1519,11 @@ def _decode(self, s: bytes) -> str: so the fallback encoding of latin-1 is being used. This can happen when a file has been incorrectly encoded by Stata or some other software. You should verify the string values returned are correct.""" - warnings.warn(msg, UnicodeWarning) + warnings.warn( + msg, + UnicodeWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) return s.decode("latin-1") def _read_value_labels(self) -> None: @@ -1902,7 +1919,9 @@ def _do_convert_categoricals( if self._using_iterator: # warn is using an iterator warnings.warn( - categorical_conversion_warning, CategoricalConversionWarning + categorical_conversion_warning, + CategoricalConversionWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) initial_categories = None cat_data = Categorical( @@ -2482,7 +2501,11 @@ def _check_column_names(self, data: DataFrame) -> DataFrame: conversion_warning.append(msg) ws = invalid_name_doc.format("\n ".join(conversion_warning)) - warnings.warn(ws, InvalidColumnName) + warnings.warn( + ws, + InvalidColumnName, + stacklevel=find_stack_level(inspect.currentframe()), + ) self._converted_names = converted_names self._update_strl_names() @@ -2649,6 +2672,7 @@ def write_file(self) -> None: f"This save was not successful but {self._fname} could not " "be deleted. This file is not valid.", ResourceWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) raise exc diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 045c27bb8fe56..d85495b70e6c3 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -1,5 +1,6 @@ from __future__ import annotations +import inspect from typing import ( TYPE_CHECKING, Literal, @@ -10,6 +11,8 @@ from matplotlib.artist import setp import numpy as np +from pandas.util._exceptions import find_stack_level + from pandas.core.dtypes.common import is_dict_like from pandas.core.dtypes.missing import remove_na_arraylike @@ -89,7 +92,8 @@ def _validate_color_args(self): if self.colormap is not None: warnings.warn( "'color' and 'colormap' cannot be used " - "simultaneously. Using 'color'" + "simultaneously. Using 'color'", + stacklevel=find_stack_level(inspect.currentframe()), ) self.color = self.kwds.pop("color") diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index ee7493813f13a..7d8c7da6dd9aa 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -4,6 +4,7 @@ ABC, abstractmethod, ) +import inspect from typing import ( TYPE_CHECKING, Hashable, @@ -22,6 +23,7 @@ ) from pandas.errors import AbstractMethodError from pandas.util._decorators import cache_readonly +from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.common import ( is_categorical_dtype, @@ -394,7 +396,8 @@ def _validate_color_args(self): "color" in self.kwds or "colors" in self.kwds ) and self.colormap is not None: warnings.warn( - "'color' and 'colormap' cannot be used simultaneously. Using 'color'" + "'color' and 'colormap' cannot be used simultaneously. Using 'color'", + stacklevel=find_stack_level(inspect.currentframe()), ) if "color" in self.kwds and self.style is not None: diff --git a/pandas/plotting/_matplotlib/style.py b/pandas/plotting/_matplotlib/style.py index 9e459b82fec97..2f29aafbdf5cf 100644 --- a/pandas/plotting/_matplotlib/style.py +++ b/pandas/plotting/_matplotlib/style.py @@ -1,5 +1,6 @@ from __future__ import annotations +import inspect import itertools from typing import ( TYPE_CHECKING, @@ -15,6 +16,8 @@ import matplotlib.colors import numpy as np +from pandas.util._exceptions import find_stack_level + from pandas.core.dtypes.common import is_list_like import pandas.core.common as com @@ -121,7 +124,8 @@ def _derive_colors( elif color is not None: if colormap is not None: warnings.warn( - "'color' and 'colormap' cannot be used simultaneously. Using 'color'" + "'color' and 'colormap' cannot be used simultaneously. Using 'color'", + stacklevel=find_stack_level(inspect.currentframe()), ) return _get_colors_from_color(color) else: diff --git a/pandas/plotting/_matplotlib/tools.py b/pandas/plotting/_matplotlib/tools.py index c5119205d1861..8f0ea70ab4124 100644 --- a/pandas/plotting/_matplotlib/tools.py +++ b/pandas/plotting/_matplotlib/tools.py @@ -234,6 +234,7 @@ def create_subplots( warnings.warn( "When passing multiple axes, layout keyword is ignored.", UserWarning, + stacklevel=find_stack_level(inspect.currentframe()), ) if sharex or sharey: warnings.warn( diff --git a/pandas/tests/apply/test_str.py b/pandas/tests/apply/test_str.py index 2af063dee8b55..38b2a5459eb1f 100644 --- a/pandas/tests/apply/test_str.py +++ b/pandas/tests/apply/test_str.py @@ -80,7 +80,9 @@ def test_apply_np_transformer(float_frame, op, how): if op in ["log", "sqrt"]: warn = RuntimeWarning - with tm.assert_produces_warning(warn): + with tm.assert_produces_warning(warn, check_stacklevel=False): + # float_frame fixture is defined in conftest.py, so we don't check the + # stacklevel as otherwise the test would fail. result = getattr(float_frame, how)(op) expected = getattr(np, op)(float_frame) tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/extension/test_sparse.py b/pandas/tests/extension/test_sparse.py index 60eef0d8097e4..0a2686a24c732 100644 --- a/pandas/tests/extension/test_sparse.py +++ b/pandas/tests/extension/test_sparse.py @@ -246,11 +246,11 @@ def test_isna(self, data_missing): self.assert_equal(sarr.isna(), expected) def test_fillna_limit_pad(self, data_missing): - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, check_stacklevel=False): super().test_fillna_limit_pad(data_missing) def test_fillna_limit_backfill(self, data_missing): - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, check_stacklevel=False): super().test_fillna_limit_backfill(data_missing) def test_fillna_no_op_returns_copy(self, data, request): @@ -258,11 +258,11 @@ def test_fillna_no_op_returns_copy(self, data, request): request.node.add_marker( pytest.mark.xfail(reason="returns array with different fill value") ) - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, check_stacklevel=False): super().test_fillna_no_op_returns_copy(data) def test_fillna_series_method(self, data_missing): - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, check_stacklevel=False): super().test_fillna_limit_backfill(data_missing) @pytest.mark.xfail(reason="Unsupported") @@ -373,7 +373,7 @@ def test_combine_first(self, data, request): super().test_combine_first(data) def test_searchsorted(self, data_for_sorting, as_series): - with tm.assert_produces_warning(PerformanceWarning): + with tm.assert_produces_warning(PerformanceWarning, check_stacklevel=False): super().test_searchsorted(data_for_sorting, as_series) def test_shift_0_periods(self, data): diff --git a/pandas/tests/indexes/test_common.py b/pandas/tests/indexes/test_common.py index e7e971f957e48..40a107658231d 100644 --- a/pandas/tests/indexes/test_common.py +++ b/pandas/tests/indexes/test_common.py @@ -406,6 +406,7 @@ def test_astype_preserves_name(self, index, dtype): with tm.assert_produces_warning( warn, raise_on_extra_warnings=is_pyarrow_str, + check_stacklevel=False, ): result = index.astype(dtype) except (ValueError, TypeError, NotImplementedError, SystemError): diff --git a/pandas/tests/io/parser/common/test_chunksize.py b/pandas/tests/io/parser/common/test_chunksize.py index 8a3f8788a45aa..c8cef56c73902 100644 --- a/pandas/tests/io/parser/common/test_chunksize.py +++ b/pandas/tests/io/parser/common/test_chunksize.py @@ -192,8 +192,12 @@ def test_warn_if_chunks_have_mismatched_type(all_parsers): buf = StringIO(data) - with tm.assert_produces_warning(warning_type): - df = parser.read_csv(buf) + df = parser.read_csv_check_warnings( + warning_type, + r"Columns \(0\) have mixed types. " + "Specify dtype option on import or set low_memory=False.", + buf, + ) assert df.a.dtype == object diff --git a/pandas/tests/io/parser/common/test_common_basic.py b/pandas/tests/io/parser/common/test_common_basic.py index a0da3a7eaadce..a7cdc3c1a84d2 100644 --- a/pandas/tests/io/parser/common/test_common_basic.py +++ b/pandas/tests/io/parser/common/test_common_basic.py @@ -732,8 +732,15 @@ def test_no_header_two_extra_columns(all_parsers): ref = DataFrame([["foo", "bar", "baz"]], columns=column_names) stream = StringIO("foo,bar,baz,bam,blah") parser = all_parsers - with tm.assert_produces_warning(ParserWarning): - df = parser.read_csv(stream, header=None, names=column_names, index_col=False) + df = parser.read_csv_check_warnings( + ParserWarning, + "Length of header or names does not match length of data. " + "This leads to a loss of data with index_col=False.", + stream, + header=None, + names=column_names, + index_col=False, + ) tm.assert_frame_equal(df, ref) diff --git a/pandas/tests/io/parser/dtypes/test_dtypes_basic.py b/pandas/tests/io/parser/dtypes/test_dtypes_basic.py index da53664a0278d..2c18d461cddf8 100644 --- a/pandas/tests/io/parser/dtypes/test_dtypes_basic.py +++ b/pandas/tests/io/parser/dtypes/test_dtypes_basic.py @@ -103,10 +103,14 @@ def test_dtype_with_converters(all_parsers): 1.2,2.3""" # Dtype spec ignored if converted specified. - with tm.assert_produces_warning(ParserWarning): - result = parser.read_csv( - StringIO(data), dtype={"a": "i8"}, converters={"a": lambda x: str(x)} - ) + result = parser.read_csv_check_warnings( + ParserWarning, + "Both a converter and dtype were specified for column a " + "- only the converter will be used.", + StringIO(data), + dtype={"a": "i8"}, + converters={"a": lambda x: str(x)}, + ) expected = DataFrame({"a": ["1.1", "1.2"], "b": [2.2, 2.3]}) tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/io/parser/test_c_parser_only.py b/pandas/tests/io/parser/test_c_parser_only.py index 9a81790ca3bb0..ecc49ea8adb9f 100644 --- a/pandas/tests/io/parser/test_c_parser_only.py +++ b/pandas/tests/io/parser/test_c_parser_only.py @@ -59,15 +59,17 @@ def test_buffer_rd_bytes(c_parser_only): ) parser = c_parser_only - with tm.assert_produces_warning(RuntimeWarning): - # compression has no effect when passing a non-binary object as input - for _ in range(100): - try: - parser.read_csv( - StringIO(data), compression="gzip", delim_whitespace=True - ) - except Exception: - pass + for _ in range(100): + try: + parser.read_csv_check_warnings( + RuntimeWarning, + "compression has no effect when passing a non-binary object as input", + StringIO(data), + compression="gzip", + delim_whitespace=True, + ) + except Exception: + pass def test_delim_whitespace_custom_terminator(c_parser_only): diff --git a/pandas/tests/io/parser/test_dialect.py b/pandas/tests/io/parser/test_dialect.py index 55b193903bce0..458d4116558e4 100644 --- a/pandas/tests/io/parser/test_dialect.py +++ b/pandas/tests/io/parser/test_dialect.py @@ -108,9 +108,14 @@ def test_dialect_conflict_except_delimiter(all_parsers, custom_dialect, arg, val kwds[arg] = "blah" with tm.with_csv_dialect(dialect_name, **dialect_kwargs): - with tm.assert_produces_warning(warning_klass): - result = parser.read_csv(StringIO(data), dialect=dialect_name, **kwds) - tm.assert_frame_equal(result, expected) + result = parser.read_csv_check_warnings( + warning_klass, + "Conflicting values for", + StringIO(data), + dialect=dialect_name, + **kwds, + ) + tm.assert_frame_equal(result, expected) @pytest.mark.parametrize( @@ -141,6 +146,11 @@ def test_dialect_conflict_delimiter(all_parsers, custom_dialect, kwargs, warning data = "a:b\n1:2" with tm.with_csv_dialect(dialect_name, **dialect_kwargs): - with tm.assert_produces_warning(warning_klass): - result = parser.read_csv(StringIO(data), dialect=dialect_name, **kwargs) - tm.assert_frame_equal(result, expected) + result = parser.read_csv_check_warnings( + warning_klass, + "Conflicting values for 'delimiter'", + StringIO(data), + dialect=dialect_name, + **kwargs, + ) + tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/io/parser/test_parse_dates.py b/pandas/tests/io/parser/test_parse_dates.py index 5d2e5bccd9762..9c8809b6099f9 100644 --- a/pandas/tests/io/parser/test_parse_dates.py +++ b/pandas/tests/io/parser/test_parse_dates.py @@ -1678,10 +1678,14 @@ def test_parse_delimited_date_swap_with_warning( parser = all_parsers expected = DataFrame({0: [expected]}, dtype="datetime64[ns]") warning_msg = "Specify a format to ensure consistent parsing" - with tm.assert_produces_warning(UserWarning, match=warning_msg): - result = parser.read_csv( - StringIO(date_string), header=None, dayfirst=dayfirst, parse_dates=[0] - ) + result = parser.read_csv_check_warnings( + UserWarning, + warning_msg, + StringIO(date_string), + header=None, + dayfirst=dayfirst, + parse_dates=[0], + ) tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/io/parser/test_python_parser_only.py b/pandas/tests/io/parser/test_python_parser_only.py index 0717078a83a46..1d3392b124895 100644 --- a/pandas/tests/io/parser/test_python_parser_only.py +++ b/pandas/tests/io/parser/test_python_parser_only.py @@ -424,8 +424,9 @@ def test_on_bad_lines_callable_not_expected_length(python_parser_only): """ bad_sio = StringIO(data) - with tm.assert_produces_warning(ParserWarning, match="Length of header or names"): - result = parser.read_csv(bad_sio, on_bad_lines=lambda x: x) + result = parser.read_csv_check_warnings( + ParserWarning, "Length of header or names", bad_sio, on_bad_lines=lambda x: x + ) expected = DataFrame({"a": [1, 2, 3], "b": [2, 3, 4]}) tm.assert_frame_equal(result, expected) @@ -466,8 +467,14 @@ def test_index_col_false_and_header_none(python_parser_only): 0.5,0.03 0.1,0.2,0.3,2 """ - with tm.assert_produces_warning(ParserWarning, match="Length of header"): - result = parser.read_csv(StringIO(data), sep=",", header=None, index_col=False) + result = parser.read_csv_check_warnings( + ParserWarning, + "Length of header", + StringIO(data), + sep=",", + header=None, + index_col=False, + ) expected = DataFrame({0: [0.5, 0.1], 1: [0.03, 0.2]}) tm.assert_frame_equal(result, expected) @@ -476,7 +483,8 @@ def test_header_int_do_not_infer_multiindex_names_on_different_line(python_parse # GH#46569 parser = python_parser_only data = StringIO("a\na,b\nc,d,e\nf,g,h") - with tm.assert_produces_warning(ParserWarning, match="Length of header"): - result = parser.read_csv(data, engine="python", index_col=False) + result = parser.read_csv_check_warnings( + ParserWarning, "Length of header", data, engine="python", index_col=False + ) expected = DataFrame({"a": ["a", "c", "f"]}) tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/io/test_clipboard.py b/pandas/tests/io/test_clipboard.py index cb34cb6678a67..db85cb416145f 100644 --- a/pandas/tests/io/test_clipboard.py +++ b/pandas/tests/io/test_clipboard.py @@ -258,7 +258,11 @@ def test_round_trip_frame_string(self, df): # Two character separator is not supported in to_clipboard # Test that multi-character separators are not silently passed def test_excel_sep_warning(self, df): - with tm.assert_produces_warning(): + with tm.assert_produces_warning( + UserWarning, + match="to_clipboard in excel mode requires a single character separator.", + check_stacklevel=False, + ): df.to_clipboard(excel=True, sep=r"\t") # Separator is ignored when excel=False and should produce a warning diff --git a/pandas/tests/plotting/frame/test_hist_box_by.py b/pandas/tests/plotting/frame/test_hist_box_by.py index fe39c3d441396..e568016c858fd 100644 --- a/pandas/tests/plotting/frame/test_hist_box_by.py +++ b/pandas/tests/plotting/frame/test_hist_box_by.py @@ -164,7 +164,7 @@ def test_hist_plot_empty_list_string_tuple_by(self, by, column, hist_df): def test_hist_plot_layout_with_by(self, by, column, layout, axes_num, hist_df): # GH 15079 # _check_plot_works adds an ax so catch warning. see GH #13188 - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works( hist_df.plot.hist, column=column, by=by, layout=layout ) diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index 9ca8a71ed1897..9112d5cb3368f 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -61,23 +61,23 @@ def test_boxplot_legacy1(self): _check_plot_works(df.boxplot, return_type="dict") _check_plot_works(df.boxplot, column=["one", "two"], return_type="dict") # _check_plot_works adds an ax so catch warning. see GH #13188 - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): _check_plot_works(df.boxplot, column=["one", "two"], by="indic") _check_plot_works(df.boxplot, column="one", by=["indic", "indic2"]) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): _check_plot_works(df.boxplot, by="indic") - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): _check_plot_works(df.boxplot, by=["indic", "indic2"]) _check_plot_works(plotting._core.boxplot, data=df["one"], return_type="dict") _check_plot_works(df.boxplot, notch=1, return_type="dict") - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): _check_plot_works(df.boxplot, by="indic", notch=1) def test_boxplot_legacy2(self): df = DataFrame(np.random.rand(10, 2), columns=["Col1", "Col2"]) df["X"] = Series(["A", "A", "A", "A", "A", "B", "B", "B", "B", "B"]) df["Y"] = Series(["A"] * 10) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): _check_plot_works(df.boxplot, by="X") # When ax is supplied and required number of axes is 1, @@ -330,7 +330,7 @@ def test_boxplot_group_xlabel_ylabel(self, vert): class TestDataFrameGroupByPlots(TestPlotBase): def test_boxplot_legacy1(self, hist_df): grouped = hist_df.groupby(by="gender") - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(grouped.boxplot, return_type="axes") self._check_axes_shape(list(axes.values), axes_num=2, layout=(1, 2)) axes = _check_plot_works(grouped.boxplot, subplots=False, return_type="axes") @@ -341,7 +341,7 @@ def test_boxplot_legacy2(self): tuples = zip(string.ascii_letters[:10], range(10)) df = DataFrame(np.random.rand(10, 3), index=MultiIndex.from_tuples(tuples)) grouped = df.groupby(level=1) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(grouped.boxplot, return_type="axes") self._check_axes_shape(list(axes.values), axes_num=10, layout=(4, 3)) @@ -352,7 +352,7 @@ def test_boxplot_legacy3(self): tuples = zip(string.ascii_letters[:10], range(10)) df = DataFrame(np.random.rand(10, 3), index=MultiIndex.from_tuples(tuples)) grouped = df.unstack(level=1).groupby(level=0, axis=1) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(grouped.boxplot, return_type="axes") self._check_axes_shape(list(axes.values), axes_num=3, layout=(2, 2)) axes = _check_plot_works(grouped.boxplot, subplots=False, return_type="axes") @@ -437,20 +437,20 @@ def test_grouped_box_layout(self, hist_df): df.boxplot(column=["weight", "height"], by=df.gender, layout=(-1, -1)) # _check_plot_works adds an ax so catch warning. see GH #13188 - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): box = _check_plot_works( df.groupby("gender").boxplot, column="height", return_type="dict" ) self._check_axes_shape(self.plt.gcf().axes, axes_num=2, layout=(1, 2)) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): box = _check_plot_works( df.groupby("category").boxplot, column="height", return_type="dict" ) self._check_axes_shape(self.plt.gcf().axes, axes_num=4, layout=(2, 2)) # GH 6769 - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): box = _check_plot_works( df.groupby("classroom").boxplot, column="height", return_type="dict" ) @@ -473,7 +473,7 @@ def test_grouped_box_layout(self, hist_df): ) self._check_axes_shape(self.plt.gcf().axes, axes_num=3, layout=(2, 2)) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): box = _check_plot_works( df.groupby("category").boxplot, column="height", @@ -481,7 +481,7 @@ def test_grouped_box_layout(self, hist_df): return_type="dict", ) self._check_axes_shape(self.plt.gcf().axes, axes_num=4, layout=(3, 2)) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): box = _check_plot_works( df.groupby("category").boxplot, column="height", diff --git a/pandas/tests/plotting/test_hist_method.py b/pandas/tests/plotting/test_hist_method.py index 0955e7808f3f6..9c11d589716fe 100644 --- a/pandas/tests/plotting/test_hist_method.py +++ b/pandas/tests/plotting/test_hist_method.py @@ -31,9 +31,9 @@ def test_hist_legacy(self, ts): _check_plot_works(ts.hist, grid=False) _check_plot_works(ts.hist, figsize=(8, 10)) # _check_plot_works adds an ax so catch warning. see GH #13188 - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): _check_plot_works(ts.hist, by=ts.index.month) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): _check_plot_works(ts.hist, by=ts.index.month, bins=5) fig, ax = self.plt.subplots(1, 1) @@ -74,31 +74,31 @@ def test_hist_layout_with_by(self, hist_df): # _check_plot_works adds an `ax` kwarg to the method call # so we get a warning about an axis being cleared, even # though we don't explicing pass one, see GH #13188 - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(df.height.hist, by=df.gender, layout=(2, 1)) self._check_axes_shape(axes, axes_num=2, layout=(2, 1)) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(df.height.hist, by=df.gender, layout=(3, -1)) self._check_axes_shape(axes, axes_num=2, layout=(3, 1)) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(df.height.hist, by=df.category, layout=(4, 1)) self._check_axes_shape(axes, axes_num=4, layout=(4, 1)) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(df.height.hist, by=df.category, layout=(2, -1)) self._check_axes_shape(axes, axes_num=4, layout=(2, 2)) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(df.height.hist, by=df.category, layout=(3, -1)) self._check_axes_shape(axes, axes_num=4, layout=(3, 2)) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(df.height.hist, by=df.category, layout=(-1, 4)) self._check_axes_shape(axes, axes_num=4, layout=(1, 4)) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(df.height.hist, by=df.classroom, layout=(2, 2)) self._check_axes_shape(axes, axes_num=3, layout=(2, 2)) @@ -235,7 +235,7 @@ class TestDataFramePlots(TestPlotBase): def test_hist_df_legacy(self, hist_df): from matplotlib.patches import Rectangle - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): _check_plot_works(hist_df.hist) # make sure layout is handled @@ -248,7 +248,7 @@ def test_hist_df_legacy(self, hist_df): dtype=np.int64, ) ) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(df.hist, grid=False) self._check_axes_shape(axes, axes_num=3, layout=(2, 2)) assert not axes[1, 1].get_visible() @@ -267,20 +267,20 @@ def test_hist_df_legacy(self, hist_df): dtype=np.int64, ) ) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(df.hist, layout=(4, 2)) self._check_axes_shape(axes, axes_num=6, layout=(4, 2)) # make sure sharex, sharey is handled - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): _check_plot_works(df.hist, sharex=True, sharey=True) # handle figsize arg - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): _check_plot_works(df.hist, figsize=(8, 10)) # check bins argument - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): _check_plot_works(df.hist, bins=5) # make sure xlabelsize and xrot are handled @@ -659,13 +659,13 @@ def test_grouped_hist_layout(self, hist_df): with pytest.raises(ValueError, match=msg): df.hist(column="height", by=df.category, layout=(-1, -1)) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works( df.hist, column="height", by=df.gender, layout=(2, 1) ) self._check_axes_shape(axes, axes_num=2, layout=(2, 1)) - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works( df.hist, column="height", by=df.gender, layout=(2, -1) ) @@ -682,14 +682,14 @@ def test_grouped_hist_layout(self, hist_df): tm.close() # GH 6769 - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works( df.hist, column="height", by="classroom", layout=(2, 2) ) self._check_axes_shape(axes, axes_num=3, layout=(2, 2)) # without column - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works(df.hist, by="classroom") self._check_axes_shape(axes, axes_num=3, layout=(2, 2)) diff --git a/pandas/tests/plotting/test_misc.py b/pandas/tests/plotting/test_misc.py index ab8e64be648d4..7dd9b78bab1cd 100644 --- a/pandas/tests/plotting/test_misc.py +++ b/pandas/tests/plotting/test_misc.py @@ -107,7 +107,7 @@ def test_scatter_matrix_axis(self, pass_axis): df = DataFrame(np.random.randn(100, 3)) # we are plotting multiples on a sub-plot - with tm.assert_produces_warning(UserWarning, raise_on_extra_warnings=True): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works( scatter_matrix, filterwarnings="always", @@ -125,7 +125,7 @@ def test_scatter_matrix_axis(self, pass_axis): df[0] = (df[0] - 2) / 3 # we are plotting multiples on a sub-plot - with tm.assert_produces_warning(UserWarning): + with tm.assert_produces_warning(UserWarning, check_stacklevel=False): axes = _check_plot_works( scatter_matrix, filterwarnings="always", diff --git a/pandas/tseries/frequencies.py b/pandas/tseries/frequencies.py index 35174d92b4125..3edabc7c089e1 100644 --- a/pandas/tseries/frequencies.py +++ b/pandas/tseries/frequencies.py @@ -235,7 +235,7 @@ def __init__(self, index, warn: bool = True) -> None: "warn is deprecated (and never implemented) and " "will be removed in a future version.", FutureWarning, - stacklevel=3, + stacklevel=find_stack_level(inspect.currentframe()), ) self.warn = warn diff --git a/scripts/list_future_warnings.sh b/scripts/list_future_warnings.sh index 121f4f5a92abb..dc3d0b59b618b 100755 --- a/scripts/list_future_warnings.sh +++ b/scripts/list_future_warnings.sh @@ -6,7 +6,7 @@ # This is useful to detect features that have been deprecated, and should be # removed from the code. For example, if a line of code contains: # -# warning.warn('Method deprecated', FutureWarning, stacklevel=2) +# warning.warn('Method deprecated', FutureWarning, stacklevel=find_stack_level(inspect.currentframe())) # # Which is released in Pandas 0.20.0, then it is expected that the method # is removed before releasing Pandas 0.24.0, including the warning. If it diff --git a/scripts/validate_min_versions_in_sync.py b/scripts/validate_min_versions_in_sync.py index 1b937673672f8..cb6a204094bf5 100755 --- a/scripts/validate_min_versions_in_sync.py +++ b/scripts/validate_min_versions_in_sync.py @@ -26,9 +26,11 @@ # in pre-commit environment sys.path.append("pandas/compat") sys.path.append("pandas/util") +import _exceptions import version sys.modules["pandas.util.version"] = version +sys.modules["pandas.util._exceptions"] = _exceptions import _optional