Skip to content
Open
Show file tree
Hide file tree
Changes from all 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