Skip to content

Commit

Permalink
feat(python): Add base PolarsError and PolarsWarning class (#13615)
Browse files Browse the repository at this point in the history
  • Loading branch information
stinodego authored and r-brink committed Jan 24, 2024
1 parent 099add8 commit 318a042
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 59 deletions.
1 change: 1 addition & 0 deletions py-polars/docs/source/reference/exceptions.rst
Expand Up @@ -14,6 +14,7 @@ Exceptions
InvalidOperationError
NoDataError
NoRowsReturnedError
PolarsError
PolarsPanicError
RowsError
SchemaError
Expand Down
4 changes: 4 additions & 0 deletions py-polars/polars/__init__.py
Expand Up @@ -78,7 +78,9 @@
InvalidOperationError,
NoDataError,
OutOfBoundsError,
PolarsError,
PolarsPanicError,
PolarsWarning,
SchemaError,
SchemaFieldNotFoundError,
ShapeError,
Expand Down Expand Up @@ -223,12 +225,14 @@
"InvalidOperationError",
"NoDataError",
"OutOfBoundsError",
"PolarsError",
"PolarsPanicError",
"SchemaError",
"SchemaFieldNotFoundError",
"ShapeError",
"StructFieldNotFoundError",
# warnings
"PolarsWarning",
"CategoricalRemappingWarning",
# core classes
"DataFrame",
Expand Down
88 changes: 49 additions & 39 deletions py-polars/polars/exceptions.py
Expand Up @@ -7,7 +7,9 @@
InvalidOperationError,
NoDataError,
OutOfBoundsError,
PolarsError,
PolarsPanicError,
PolarsWarning,
SchemaError,
SchemaFieldNotFoundError,
ShapeError,
Expand All @@ -17,92 +19,98 @@
except ImportError:
# redefined for documentation purposes when there is no binary

class ColumnNotFoundError(Exception): # type: ignore[no-redef]
class PolarsError(Exception): # type: ignore[no-redef]
"""Base class for all Polars errors."""

class ColumnNotFoundError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when a specified column is not found."""

class ComputeError(Exception): # type: ignore[no-redef]
class ComputeError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when polars could not finish the computation."""

class DuplicateError(Exception): # type: ignore[no-redef]
class DuplicateError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when a column name is duplicated."""

class InvalidOperationError(Exception): # type: ignore[no-redef]
class InvalidOperationError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when an operation is not allowed on a certain data type."""

class NoDataError(Exception): # type: ignore[no-redef]
class NoDataError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when an operation can not be performed on an empty data structure.""" # noqa: W505

class OutOfBoundsError(Exception): # type: ignore[no-redef]
class OutOfBoundsError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when the given index is out of bounds."""

class PolarsPanicError(Exception): # type: ignore[no-redef]
class PolarsPanicError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when an unexpected state causes a panic in the underlying Rust library.""" # noqa: W505

class SchemaError(Exception): # type: ignore[no-redef]
class SchemaError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when trying to combine data structures with mismatched schemas.""" # noqa: W505

class SchemaFieldNotFoundError(Exception): # type: ignore[no-redef]
class SchemaFieldNotFoundError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when a specified schema field is not found."""

class ShapeError(Exception): # type: ignore[no-redef]
class ShapeError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when trying to combine data structures with incompatible shapes.""" # noqa: W505

class StringCacheMismatchError(Exception): # type: ignore[no-redef]
class StringCacheMismatchError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when string caches come from different sources."""

class StructFieldNotFoundError(Exception): # type: ignore[no-redef]
class StructFieldNotFoundError(PolarsError): # type: ignore[no-redef, misc]
"""Exception raised when a specified schema field is not found."""

class CategoricalRemappingWarning(Warning): # type: ignore[no-redef]
class PolarsWarning(Exception): # type: ignore[no-redef]
"""Base class for all Polars warnings."""

class CategoricalRemappingWarning(PolarsWarning): # type: ignore[no-redef, misc]
"""Warning raised when a categorical needs to be remapped to be compatible with another categorical.""" # noqa: W505


class ChronoFormatWarning(Warning):
"""
Warning raised when a chrono format string contains dubious patterns.
class InvalidAssert(PolarsError): # type: ignore[misc]
"""Exception raised when an unsupported testing assert is made."""

Polars uses Rust's chrono crate to convert between string data and temporal data.
The patterns used by chrono differ slightly from Python's built-in datetime module.
Refer to the `chrono strftime documentation
<https://docs.rs/chrono/latest/chrono/format/strftime/index.html>`_ for the full
specification.
"""

class RowsError(PolarsError): # type: ignore[misc]
"""Exception raised when the number of returned rows does not match expectation."""

class InvalidAssert(Exception):
"""Exception raised when an unsupported testing assert is made."""

class NoRowsReturnedError(RowsError):
"""Exception raised when no rows are returned, but at least one row is expected."""


class RowsError(Exception):
"""Exception raised when the number of returned rows does not match expectation."""
class TooManyRowsReturnedError(RowsError):
"""Exception raised when more rows than expected are returned."""


class ModuleUpgradeRequired(ModuleNotFoundError):
"""Exception raised when the module is installed but needs to be upgraded."""


class NoRowsReturnedError(RowsError):
"""Exception raised when no rows are returned, but at least one row is expected."""


class ParameterCollisionError(RuntimeError):
class ParameterCollisionError(PolarsError): # type: ignore[misc]
"""Exception raised when the same parameter occurs multiple times."""


class PolarsInefficientMapWarning(Warning):
"""Warning raised when a potentially slow `apply` operation is performed."""
class UnsuitableSQLError(PolarsError): # type: ignore[misc]
"""Exception raised when unsuitable SQL is given to a database method."""


class TooManyRowsReturnedError(RowsError):
"""Exception raised when more rows than expected are returned."""
class ChronoFormatWarning(PolarsWarning): # type: ignore[misc]
"""
Warning raised when a chrono format string contains dubious patterns.
Polars uses Rust's chrono crate to convert between string data and temporal data.
The patterns used by chrono differ slightly from Python's built-in datetime module.
Refer to the `chrono strftime documentation
<https://docs.rs/chrono/latest/chrono/format/strftime/index.html>`_ for the full
specification.
"""


class TimeZoneAwareConstructorWarning(Warning):
"""Warning raised when constructing Series from non-UTC time-zone-aware inputs."""
class PolarsInefficientMapWarning(PolarsWarning): # type: ignore[misc]
"""Warning raised when a potentially slow `apply` operation is performed."""


class UnsuitableSQLError(ValueError):
"""Exception raised when unsuitable SQL is given to a database method."""
class TimeZoneAwareConstructorWarning(PolarsWarning): # type: ignore[misc]
"""Warning raised when constructing Series from non-UTC time-zone-aware inputs."""


class ArrowError(Exception):
Expand All @@ -122,7 +130,9 @@ class ArrowError(Exception):
"OutOfBoundsError",
"PolarsInefficientMapWarning",
"CategoricalRemappingWarning",
"PolarsError",
"PolarsPanicError",
"PolarsWarning",
"RowsError",
"SchemaError",
"SchemaFieldNotFoundError",
Expand Down
32 changes: 20 additions & 12 deletions py-polars/src/error.rs
Expand Up @@ -12,6 +12,7 @@ use pyo3::{create_exception, PyTypeInfo};
use thiserror::Error;

use crate::Wrap;

#[derive(Error)]
pub enum PyPolarsErr {
#[error(transparent)]
Expand Down Expand Up @@ -74,18 +75,25 @@ impl Debug for PyPolarsErr {
}
}

create_exception!(polars.exceptions, ColumnNotFoundError, PyException);
create_exception!(polars.exceptions, ComputeError, PyException);
create_exception!(polars.exceptions, DuplicateError, PyException);
create_exception!(polars.exceptions, InvalidOperationError, PyException);
create_exception!(polars.exceptions, NoDataError, PyException);
create_exception!(polars.exceptions, OutOfBoundsError, PyException);
create_exception!(polars.exceptions, SchemaError, PyException);
create_exception!(polars.exceptions, SchemaFieldNotFoundError, PyException);
create_exception!(polars.exceptions, ShapeError, PyException);
create_exception!(polars.exceptions, StringCacheMismatchError, PyException);
create_exception!(polars.exceptions, StructFieldNotFoundError, PyException);
create_exception!(polars.exceptions, CategoricalRemappingWarning, PyWarning);
create_exception!(polars.exceptions, PolarsBaseError, PyException);
create_exception!(polars.exceptions, ColumnNotFoundError, PolarsBaseError);
create_exception!(polars.exceptions, ComputeError, PolarsBaseError);
create_exception!(polars.exceptions, DuplicateError, PolarsBaseError);
create_exception!(polars.exceptions, InvalidOperationError, PolarsBaseError);
create_exception!(polars.exceptions, NoDataError, PolarsBaseError);
create_exception!(polars.exceptions, OutOfBoundsError, PolarsBaseError);
create_exception!(polars.exceptions, SchemaError, PolarsBaseError);
create_exception!(polars.exceptions, SchemaFieldNotFoundError, PolarsBaseError);
create_exception!(polars.exceptions, ShapeError, PolarsBaseError);
create_exception!(polars.exceptions, StringCacheMismatchError, PolarsBaseError);
create_exception!(polars.exceptions, StructFieldNotFoundError, PolarsBaseError);

create_exception!(polars.exceptions, PolarsBaseWarning, PyWarning);
create_exception!(
polars.exceptions,
CategoricalRemappingWarning,
PolarsBaseWarning
);

#[macro_export]
macro_rules! raise_err(
Expand Down
22 changes: 14 additions & 8 deletions py-polars/src/lib.rs
Expand Up @@ -53,8 +53,8 @@ use crate::conversion::Wrap;
use crate::dataframe::PyDataFrame;
use crate::error::{
CategoricalRemappingWarning, ColumnNotFoundError, ComputeError, DuplicateError,
InvalidOperationError, NoDataError, OutOfBoundsError, PyPolarsErr, SchemaError,
SchemaFieldNotFoundError, StructFieldNotFoundError,
InvalidOperationError, NoDataError, OutOfBoundsError, PolarsBaseError, PolarsBaseWarning,
PyPolarsErr, SchemaError, SchemaFieldNotFoundError, StructFieldNotFoundError,
};
use crate::expr::PyExpr;
use crate::functions::PyStringCacheHolder;
Expand Down Expand Up @@ -249,7 +249,9 @@ fn polars(py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(functions::set_random_seed))
.unwrap();

// Exceptions
// Exceptions - Errors
m.add("PolarsError", py.get_type::<PolarsBaseError>())
.unwrap();
m.add("ColumnNotFoundError", py.get_type::<ColumnNotFoundError>())
.unwrap();
m.add("ComputeError", py.get_type::<ComputeError>())
Expand All @@ -262,11 +264,6 @@ fn polars(py: Python, m: &PyModule) -> PyResult<()> {
)
.unwrap();
m.add("NoDataError", py.get_type::<NoDataError>()).unwrap();
m.add(
"CategoricalRemappingWarning",
py.get_type::<CategoricalRemappingWarning>(),
)
.unwrap();
m.add("OutOfBoundsError", py.get_type::<OutOfBoundsError>())
.unwrap();
m.add("PolarsPanicError", py.get_type::<PanicException>())
Expand All @@ -290,6 +287,15 @@ fn polars(py: Python, m: &PyModule) -> PyResult<()> {
)
.unwrap();

// Exceptions - Warnings
m.add("PolarsWarning", py.get_type::<PolarsBaseWarning>())
.unwrap();
m.add(
"CategoricalRemappingWarning",
py.get_type::<CategoricalRemappingWarning>(),
)
.unwrap();

// Build info
#[cfg(feature = "build_info")]
m.add(
Expand Down
10 changes: 10 additions & 0 deletions py-polars/tests/unit/test_exceptions.py
@@ -0,0 +1,10 @@
import pytest

import polars as pl


def test_base_class() -> None:
assert isinstance(pl.ComputeError("msg"), pl.PolarsError)
msg = "msg"
with pytest.raises(pl.PolarsError, match=msg):
raise pl.OutOfBoundsError(msg)

0 comments on commit 318a042

Please sign in to comment.