Skip to content

ENH: move an exception and add a prehook to check for exception place… #48088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,14 @@ repos:
entry: python scripts/validate_min_versions_in_sync.py
language: python
files: ^(ci/deps/actions-.*-minimum_versions\.yaml|pandas/compat/_optional\.py)$
- id: validate-errors-locations
name: Validate errors locations
description: Validate errors are in approriate locations.
entry: python scripts/validate_exception_location.py
language: python
files: ^pandas/
exclude: ^(pandas/_libs/|pandas/tests/|pandas/errors/__init__.py$)
types: [python]
- id: flake8-pyi
name: flake8-pyi
entry: flake8 --extend-ignore=E301,E302,E305,E701,E704
Expand Down
5 changes: 5 additions & 0 deletions doc/source/reference/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,14 @@ Exceptions and warnings
errors.IncompatibilityWarning
errors.IndexingError
errors.InvalidColumnName
errors.InvalidComparison
errors.InvalidIndexError
errors.InvalidVersion
errors.IntCastingNaNError
errors.LossySetitemError
errors.MergeError
errors.NoBufferPresent
errors.NotThisMethod
errors.NullFrequencyError
errors.NumbaUtilError
errors.NumExprClobberingError
Expand Down
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.6.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Other enhancements
- :meth:`.GroupBy.quantile` now preserving nullable dtypes instead of casting to numpy dtypes (:issue:`37493`)
- :meth:`Series.add_suffix`, :meth:`DataFrame.add_suffix`, :meth:`Series.add_prefix` and :meth:`DataFrame.add_prefix` support an ``axis`` argument. If ``axis`` is set, the default behaviour of which axis to consider can be overwritten (:issue:`47819`)
- :func:`assert_frame_equal` now shows the first element where the DataFrames differ, analogously to ``pytest``'s output (:issue:`47910`)
- :class:`.CategoricalConversionWarning`, :class:`.InvalidComparison`, :class:`.InvalidVersion`, :class:`.LossySetitemError`, :class:`.NoBufferPresent`, and :class:`.NotThisMethod` are now exposed in ``pandas.errors`` (:issue:`27656`)
-

.. ---------------------------------------------------------------------------
Expand Down
10 changes: 1 addition & 9 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
from pandas.compat.numpy import function as nv
from pandas.errors import (
AbstractMethodError,
InvalidComparison,
NullFrequencyError,
PerformanceWarning,
)
Expand Down Expand Up @@ -153,15 +154,6 @@
DatetimeLikeArrayT = TypeVar("DatetimeLikeArrayT", bound="DatetimeLikeArrayMixin")


class InvalidComparison(Exception):
"""
Raised by _validate_comparison_value to indicate to caller it should
return invalid_comparison.
"""

pass


class DatetimeLikeArrayMixin(OpsMixin, NDArrayBackedExtensionArray):
"""
Shared Base/Mixin class for DatetimeArray, TimedeltaArray, PeriodArray
Expand Down
13 changes: 4 additions & 9 deletions pandas/core/dtypes/cast.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@
DtypeObj,
Scalar,
)
from pandas.errors import IntCastingNaNError
from pandas.errors import (
IntCastingNaNError,
LossySetitemError,
)
from pandas.util._exceptions import find_stack_level
from pandas.util._validators import validate_bool_kwarg

Expand Down Expand Up @@ -2098,11 +2101,3 @@ def _dtype_can_hold_range(rng: range, dtype: np.dtype) -> bool:
if not len(rng):
return True
return np.can_cast(rng[0], dtype) and np.can_cast(rng[-1], dtype)


class LossySetitemError(Exception):
"""
Raised when trying to do a __setitem__ on an np.ndarray that is not lossless.
"""

pass
2 changes: 1 addition & 1 deletion pandas/core/interchange/column.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from pandas._libs.lib import infer_dtype
from pandas._libs.tslibs import iNaT
from pandas.errors import NoBufferPresent
from pandas.util._decorators import cache_readonly

import pandas as pd
Expand All @@ -23,7 +24,6 @@
from pandas.core.interchange.utils import (
ArrowCTypes,
Endianness,
NoBufferPresent,
dtype_to_arrow_c_fmt,
)

Expand Down
4 changes: 0 additions & 4 deletions pandas/core/interchange/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,3 @@ def dtype_to_arrow_c_fmt(dtype: DtypeObj) -> str:
raise NotImplementedError(
f"Conversion of {dtype} to Arrow C format string is not implemented."
)


class NoBufferPresent(Exception):
"""Exception to signal that there is no requested buffer."""
26 changes: 26 additions & 0 deletions pandas/errors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
OutOfBoundsTimedelta,
)

from pandas._version import NotThisMethod
from pandas.util.version import InvalidVersion


class IntCastingNaNError(ValueError):
"""
Expand Down Expand Up @@ -535,6 +538,24 @@ class CategoricalConversionWarning(Warning):
"""


class LossySetitemError(Exception):
"""
Raised when trying to do a __setitem__ on an np.ndarray that is not lossless.
"""


class NoBufferPresent(Exception):
"""
Exception is raised in _get_data_buffer to signal that there is no requested buffer.
"""


class InvalidComparison(Exception):
"""
Exception is raised by _validate_comparison_value to indicate an invalid comparison.
"""


__all__ = [
"AbstractMethodError",
"AccessorRegistrationWarning",
Expand All @@ -550,9 +571,14 @@ class CategoricalConversionWarning(Warning):
"IncompatibilityWarning",
"IntCastingNaNError",
"InvalidColumnName",
"InvalidComparison",
"InvalidIndexError",
"InvalidVersion",
"IndexingError",
"LossySetitemError",
"MergeError",
"NoBufferPresent",
"NotThisMethod",
"NullFrequencyError",
"NumbaUtilError",
"NumExprClobberingError",
Expand Down
47 changes: 26 additions & 21 deletions pandas/tests/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,38 @@
@pytest.mark.parametrize(
"exc",
[
"UnsupportedFunctionCall",
"UnsortedIndexError",
"OutOfBoundsDatetime",
"ParserError",
"PerformanceWarning",
"AttributeConflictWarning",
"CSSWarning",
"CategoricalConversionWarning",
"ClosedFileError",
"DataError",
"DatabaseError",
"DtypeWarning",
"EmptyDataError",
"ParserWarning",
"IncompatibilityWarning",
"IndexingError",
"InvalidColumnName",
"InvalidComparison",
"InvalidVersion",
"LossySetitemError",
"MergeError",
"OptionError",
"NumbaUtilError",
"DataError",
"SpecificationError",
"SettingWithCopyError",
"SettingWithCopyWarning",
"NoBufferPresent",
"NotThisMethod",
"NumExprClobberingError",
"IndexingError",
"PyperclipException",
"CSSWarning",
"ClosedFileError",
"NumbaUtilError",
"OptionError",
"OutOfBoundsDatetime",
"ParserError",
"ParserWarning",
"PerformanceWarning",
"PossibleDataLossError",
"IncompatibilityWarning",
"AttributeConflictWarning",
"DatabaseError",
"PossiblePrecisionLoss",
"CategoricalConversionWarning",
"InvalidColumnName",
"PyperclipException",
"SettingWithCopyError",
"SettingWithCopyWarning",
"SpecificationError",
"UnsortedIndexError",
"UnsupportedFunctionCall",
"ValueLabelTypeMismatch",
],
)
Expand Down
2 changes: 1 addition & 1 deletion scripts/pandas_errors_documented.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Check that doc/source/reference/general_utility_functions.rst documents
Check that doc/source/reference/testing.rst documents
all exceptions and warnings in pandas/errors/__init__.py.

This is meant to be run as a pre-commit hook - to run it manually, you can do:
Expand Down
59 changes: 59 additions & 0 deletions scripts/tests/test_validate_exception_location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import pytest

from scripts.validate_exception_location import (
ERROR_MESSAGE,
validate_exception_and_warning_placement,
)

PATH = "t.py"

# ERRORS_IN_TESTING_RST is the set returned when parsing testing.rst for all the
# exceptions and warnings.
CUSTOM_EXCEPTION_NOT_IN_TESTING_RST = "MyException"
CUSTOM_EXCEPTION__IN_TESTING_RST = "MyOldException"
ERRORS_IN_TESTING_RST = {CUSTOM_EXCEPTION__IN_TESTING_RST}

TEST_CODE = """
import numpy as np
import sys

def my_func():
pass

class {custom_name}({error_type}):
pass

"""


# Test with various python-defined exceptions to ensure they are all flagged.
@pytest.fixture(params=["Exception", "ValueError", "Warning", "UserWarning"])
def error_type(request):
return request.param


def test_class_that_inherits_an_exception_and_is_not_in_the_testing_rst_is_flagged(
capsys, error_type
):
content = TEST_CODE.format(
custom_name=CUSTOM_EXCEPTION_NOT_IN_TESTING_RST, error_type=error_type
)
expected_msg = ERROR_MESSAGE.format(errors=CUSTOM_EXCEPTION_NOT_IN_TESTING_RST)
with pytest.raises(SystemExit, match=None):
validate_exception_and_warning_placement(PATH, content, ERRORS_IN_TESTING_RST)
result_msg, _ = capsys.readouterr()
assert result_msg == expected_msg


def test_class_that_inherits_an_exception_but_is_in_the_testing_rst_is_not_flagged(
capsys, error_type
):
content = TEST_CODE.format(
custom_name=CUSTOM_EXCEPTION__IN_TESTING_RST, error_type=error_type
)
validate_exception_and_warning_placement(PATH, content, ERRORS_IN_TESTING_RST)


def test_class_that_does_not_inherit_an_exception_is_not_flagged(capsys):
content = "class MyClass(NonExceptionClass): pass"
validate_exception_and_warning_placement(PATH, content, ERRORS_IN_TESTING_RST)
Loading