Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Our backwards-compatibility policy can be found [here](https://github.com/python

- Fix an `AttributeError` in `cattrs` internals that could be triggered by using the `include_subclasses` strategy in a `structure_hook_factory`
([#721](https://github.com/python-attrs/cattrs/issues/721), [#722](https://github.com/python-attrs/cattrs/pull/722))
- Add `CattrsError` exception type: all exceptions raised by `cattrs` inherit from this
([#728](https://github.com/python-attrs/cattrs/pull/728))
- Literal and date-time validation raise this directly, instead of `Exception`

## 26.1.0 (2026-02-18)

Expand Down
5 changes: 3 additions & 2 deletions src/cattrs/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
)
from .enums import enum_structure_factory, enum_unstructure_factory
from .errors import (
CattrsError,
IterableValidationError,
IterableValidationNote,
StructureHandlerNotFoundError,
Expand Down Expand Up @@ -708,7 +709,7 @@ def _structure_call(obj: Any, cl: type[T]) -> Any:
@staticmethod
def _structure_simple_literal(val, type):
if val not in type.__args__:
raise Exception(f"{val} not in literal {type}")
raise CattrsError(f"{val} not in literal {type}")
return val

@staticmethod
Expand All @@ -717,7 +718,7 @@ def _structure_enum_literal(val, type):
try:
return vals[val]
except KeyError:
raise Exception(f"{val} not in literal {type}") from None
raise CattrsError(f"{val} not in literal {type}") from None

def _structure_newtype(self, val: UnstructuredValue, type) -> StructuredValue:
base = get_newtype_base(type)
Expand Down
10 changes: 7 additions & 3 deletions src/cattrs/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
from cattrs._compat import ExceptionGroup


class StructureHandlerNotFoundError(Exception):
class CattrsError(Exception):
"""Base ``cattrs`` exception."""


class StructureHandlerNotFoundError(CattrsError):
"""
Error raised when structuring cannot find a handler for converting inputs into
:attr:`type_`.
Expand All @@ -21,7 +25,7 @@ def __str__(self) -> str:
return self.message


class BaseValidationError(ExceptionGroup):
class BaseValidationError(ExceptionGroup, CattrsError):
cl: type

def __new__(cls, message: str, excs: Sequence[Exception], cl: type) -> Self:
Expand Down Expand Up @@ -111,7 +115,7 @@ def group_exceptions(
return excs_with_notes, other_excs


class ForbiddenExtraKeysError(Exception):
class ForbiddenExtraKeysError(CattrsError):
"""
Raised when `forbid_extra_keys` is activated and such extra keys are detected
during structuring.
Expand Down
3 changes: 2 additions & 1 deletion src/cattrs/preconf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@

from .._compat import is_subclass
from ..converters import Converter, UnstructureHook
from ..errors import CattrsError
from ..fns import identity


def validate_datetime(v, _):
if not isinstance(v, datetime):
raise Exception(f"Expected datetime, got {v}")
raise CattrsError(f"Expected datetime, got {v}")
return v


Expand Down
8 changes: 5 additions & 3 deletions tests/preconf/test_pyyaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from yaml import safe_dump, safe_load

from cattrs._compat import FrozenSetSubscriptable
from cattrs.errors import ClassValidationError
from cattrs.errors import CattrsError, ClassValidationError
from cattrs.preconf.pyyaml import make_converter

from ..test_preconf import Everything, everythings, native_unions
Expand Down Expand Up @@ -70,13 +70,15 @@ class A:
date: 1
"""

with raises(ClassValidationError if detailed_validation else Exception) as exc_info:
with raises(
ClassValidationError if detailed_validation else CattrsError
) as exc_info:
converter.loads(bad_data, A)

if detailed_validation:
assert (
repr(exc_info.value.exceptions[0])
== "Exception('Expected datetime, got 1')"
== "CattrsError('Expected datetime, got 1')"
)
assert (
repr(exc_info.value.exceptions[1]) == "ValueError('Expected date, got 1')"
Expand Down
3 changes: 2 additions & 1 deletion tests/test_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from cattrs import BaseConverter
from cattrs._compat import Literal
from cattrs.errors import CattrsError

from .untyped import enums_of_primitives

Expand All @@ -27,7 +28,7 @@ def test_enum_failure(enum):
converter = BaseConverter()
type = Literal[next(iter(enum))]

with raises(Exception) as exc_info:
with raises(CattrsError) as exc_info:
converter.structure("", type)

assert exc_info.value.args[0] == f" not in literal {type!r}"
Expand Down