from typing import Any, List, Optional, Set, Tuple, Type, Union from cattrs._compat import ExceptionGroup class StructureHandlerNotFoundError(Exception): """ Error raised when structuring cannot find a handler for converting inputs into :attr:`type_`. """ def __init__(self, message: str, type_: Type) -> None: super().__init__(message) self.type_ = type_ class BaseValidationError(ExceptionGroup): cl: Type def __new__(cls, message, excs, cl: Type): obj = super().__new__(cls, message, excs) obj.cl = cl return obj def derive(self, excs): return ClassValidationError(self.message, excs, self.cl) class IterableValidationNote(str): """Attached as a note to an exception when an iterable element fails structuring.""" index: Union[int, str] # Ints for list indices, strs for dict keys type: Any def __new__( cls, string: str, index: Union[int, str], type: Any ) -> "IterableValidationNote": instance = str.__new__(cls, string) instance.index = index instance.type = type return instance def __getnewargs__(self) -> Tuple[str, Union[int, str], Any]: return (str(self), self.index, self.type) class IterableValidationError(BaseValidationError): """Raised when structuring an iterable.""" def group_exceptions( self, ) -> Tuple[List[Tuple[Exception, IterableValidationNote]], List[Exception]]: """Split the exceptions into two groups: with and without validation notes.""" excs_with_notes = [] other_excs = [] for subexc in self.exceptions: if hasattr(subexc, "__notes__"): for note in subexc.__notes__: if note.__class__ is IterableValidationNote: excs_with_notes.append((subexc, note)) break else: other_excs.append(subexc) else: other_excs.append(subexc) return excs_with_notes, other_excs class AttributeValidationNote(str): """Attached as a note to an exception when an attribute fails structuring.""" name: str type: Any def __new__(cls, string: str, name: str, type: Any) -> "AttributeValidationNote": instance = str.__new__(cls, string) instance.name = name instance.type = type return instance def __getnewargs__(self) -> Tuple[str, str, Any]: return (str(self), self.name, self.type) class ClassValidationError(BaseValidationError): """Raised when validating a class if any attributes are invalid.""" def group_exceptions( self, ) -> Tuple[List[Tuple[Exception, AttributeValidationNote]], List[Exception]]: """Split the exceptions into two groups: with and without validation notes.""" excs_with_notes = [] other_excs = [] for subexc in self.exceptions: if hasattr(subexc, "__notes__"): for note in subexc.__notes__: if note.__class__ is AttributeValidationNote: excs_with_notes.append((subexc, note)) break else: other_excs.append(subexc) else: other_excs.append(subexc) return excs_with_notes, other_excs class ForbiddenExtraKeysError(Exception): """ Raised when `forbid_extra_keys` is activated and such extra keys are detected during structuring. The attribute `extra_fields` is a sequence of those extra keys, which were the cause of this error, and `cl` is the class which was structured with those extra keys. """ def __init__( self, message: Optional[str], cl: Type, extra_fields: Set[str] ) -> None: self.cl = cl self.extra_fields = extra_fields cln = cl.__name__ super().__init__( message or f"Extra fields in constructor for {cln}: {', '.join(extra_fields)}" )