"""What the package raises: the engine's refusals and the service's.""" from __future__ import annotations import re from typing import Any, Optional __all__ = ["EngineError", "ServiceError"] _LINE = re.compile(r"^line (\d+):") _CODE = re.compile(r"\[([QJ]\d{3})\]$") class EngineError(Exception): """The engine refused a request, a program or a value, and said why. ``message`` is the engine's message as it gave it, the same text the command line, the service and the Studio show. ``line`` is the program's line the message names, when it names one. ``code`` is the stable code the refusal ends with: the reader's (``"Q103"``), or the job layer's (``"J011"``: the request, the method, an intent, a repair); code that handles errors should test it, not the message. """ def __init__(self, message: str, code: Optional[str] = None, line: Optional[int] = None) -> None: super().__init__(message) self.message = message if code is None: m = _CODE.search(message) code = m.group(1) if m else None self.code = code if line is None: m = _LINE.match(message) line = int(m.group(1)) if m else None self.line = line def __reduce__(self): return (type(self), (self.message, self.code, self.line)) class ServiceError(Exception): """The job service refused a request, a job failed or was cancelled, or the service could not be reached. ``status`` is the HTTP status (``None`` when no answer came), ``message`` the service's own words, and ``body`` its whole answer when it sent one. """ def __init__(self, message: str, status: Optional[int] = None, body: Any = None) -> None: super().__init__(message if status is None else f"{message} (HTTP {status})") self.message = message self.status = status self.body = body def __reduce__(self): return (type(self), (self.message, self.status, self.body))