Source code for kaiju_tools.exceptions

"""API exceptions."""

from aiohttp.client import ClientResponseError
from fastjsonschema import JsonSchemaValueException

from kaiju_tools.encoding import Serializable
from kaiju_tools.registry import ClassRegistry

__all__ = [
    "APIException",
    "InternalError",
    "ClientError",
    "ValidationError",
    "ModelValidationError",
    "NotFound",
    "MethodNotFound",
    "InvalidParams",
    "InvalidRequest",
    "RequestTimeout",
    "Aborted",
    "MethodNotAllowed",
    "Conflict",
    "JSONParseError",
    "PermissionDenied",
    "NotAuthorized",
    "InvalidLicense",
    "HTTPRequestError",
    "JSONRPCError",
    "ErrorRegistry",
    "ERROR_CLASSES",
]


[docs]class APIException(Serializable, Exception): """Base exception. You should use this class as a base when creating an exception which is meant to propagated across services, since this class is serializable. """ __slots__ = ("message", "status", "data", "id", "debug", "base_exc") status_code = 500 """RPC error status code."""
[docs] def __init__( self, message: str = "", *, id: int | None = None, base_exc: Exception = None, debug: bool = False, data: dict = None, **extras, ): """Initialize. :param message: error messages :param id: request id :param base_exc: base exception object (when used as a wrapper) :param debug: debug mode error :param data: additional data :param extras: will be merged with `data` """ self.id = id self.message = message self.base_exc = base_exc self.data = data if data else {} self.data.update(extras) self.debug = debug
def __str__(self): """Get error message.""" return self.message def repr(self) -> dict: """Serialize.""" data = { "code": self.status_code, "message": self.message, "data": dict(self.data), } data["data"].update({"type": self.__class__.__name__, "base_type": self.__class__.__base__.__name__}) if self.debug and self.base_exc: data["data"].update( { "base_exc_type": self.base_exc.__class__.__name__, "base_exc_data": getattr(self.base_exc, "extras", {}), "base_exc_message": str(self.base_exc), } ) data["data"].update(self.data) return data
[docs]class ClientError(APIException): """Any kind of error happened because of a client's actions.""" status_code = 400
[docs]class JSONParseError(ClientError, ValueError): """Wrongly formatted JSON data.""" status_code = -32700
class JSONRPCError(ClientError): """Common JSONRPC error."""
[docs]class InvalidRequest(JSONRPCError): """Invalid RPC request format.""" status_code = -32600
MethodNotAllowed = InvalidRequest # compatibility
[docs]class MethodNotFound(JSONRPCError): """RPC method does not exist or not available for this type of user.""" status_code = -32601
[docs]class InvalidParams(JSONRPCError): """JSONSchema validation error.""" status_code = -32602 def repr(self) -> dict: """Serialize.""" data = super().repr() if self.base_exc and isinstance(self.base_exc, JsonSchemaValueException): data["data"].update( { "name": ".".join(("params", *self.base_exc.path[1:])), "definition": self.base_exc.definition, "rule": self.base_exc.rule, "rule_definition": self.base_exc.rule_definition, "value": self.base_exc.value, } ) return data
ValidationError = InvalidParams # compatibility class ModelValidationError(ValidationError): """Validation error during a data model validation.""" status_code = -32702
[docs]class RequestTimeout(ClientError): """Request reached its timeout.""" status_code = -32002
[docs]class Aborted(ClientError): """Aborted by the server due to timeout or other conditions.""" status_code = -32001
[docs]class InternalError(APIException): """Any internal error happened on the server and not caused by a client.""" status_code = -32603
[docs]class HTTPRequestError(InternalError): """HTTP response error.""" status_code = -32603 def repr(self) -> dict: """Serialize.""" data = super().repr() if self.debug and self.base_exc and isinstance(self.base_exc, ClientResponseError): data["data"].update( { "status": self.base_exc.status, "method": str(self.base_exc.request_info.method), "url": str(self.base_exc.request_info.url), "params": getattr(self.base_exc, "params", None), "took_ms": getattr(self.base_exc, "took_ms", None), "request": getattr(self.base_exc, "request", None), "response": getattr(self.base_exc, "response", None), } ) return data
[docs]class NotAuthorized(ClientError): """Authorization required.""" status_code = -32000
PermissionDenied = MethodNotFound # compatibility
[docs]class NotFound(ClientError): """Requested object or resource doesn't exist.""" status_code = 404
[docs]class Conflict(ClientError): """New object/data is in conflict with existing.""" status_code = 409
[docs]class FailedDependency(APIException): """Something.""" status_code = 424
[docs]class InvalidLicense(ClientError): """Invalid license exception.""" status_code = 451
class ErrorRegistry(ClassRegistry): """Error classes.""" @classmethod def get_base_classes(cls) -> tuple[type, ...]: return (APIException,) ERROR_CLASSES = ErrorRegistry() """Global registry of error classes, see :py:class:`~kaiju_tools.registry.ClassRegistry`.""" ERROR_CLASSES.register_from_namespace(locals())