from __future__ import annotations import time from aare.common.logger_config import setup_logger from aare.common.error_codes import AuthErrorCode, DAQErrorCode logger = setup_logger("aareDAQ") class TransformationInvalidException(Exception): def __init__(self, message: str = "Transformation is not implemented"): super().__init__(message) self.message = message logger.error(message, extra={"exception:": Exception}) def __str__(self) -> str: return self.message class LoopCenteringFailed(Exception): def __init__(self, message: str = "Loop Centering did not detect a sample"): super().__init__(message) self.message = message logger.error(message, extra={"exception:": Exception}) def __str__(self) -> str: return self.message class MountingFailed(Exception): def __init__(self, message: str = "A sample was not mounted"): super().__init__(message) self.message = message logger.error(message, extra={"exception:": Exception}) def __str__(self) -> str: return self.message class WarningTellException(Exception): def __init__(self, message: str = "Warning error in TELL"): super().__init__(message) self.message = message logger.error(message, extra={"exception:": Exception}) def __str__(self) -> str: return self.message class CriticalTellException(Exception): def __init__(self, message: str = "Critical error in TELL"): super().__init__(message) self.message = message logger.error(message, extra={"exception:": Exception}) def __str__(self) -> str: return self.message class AXCFailed(Exception): def __init__(self, message: str = "Auto X-ray centering failed"): super().__init__(message) self.message = message logger.error(message, extra={"exception:": Exception}) def __str__(self) -> str: return self.message class BeamlineBusyException(Exception): def __init__(self, message: str = "Beamline is in busy state"): super().__init__(message) self.message = message logger.error(message, extra={"exception:": Exception}) def __str__(self) -> str: return self.message class SampleException(Exception): def __init__(self, message: str = "Sample not found"): super().__init__(message) self.message = message logger.error(message, extra={"exception:": Exception}) def __str__(self) -> str: return self.message class AuthenticationException(Exception): def __init__(self, message: str = "Authentication failed.", *, status_code: int = 401, headers: dict[str, str] | None = None, code: AuthErrorCode = AuthErrorCode.AUTHENTICATION_FAILED): super().__init__(message) self.message = message self.status_code = status_code self.headers = headers self.code = code logger.error(message, extra={"exception:": Exception}) def __str__(self) -> str: return self.message class UserRightsException(Exception): _last_log_ts_by_message: dict[str, float] = {} _throttle_window_s = 30.0 def __init__(self, message: str = "User does not have rights to perform this action.", *, status_code: int = 403, headers: dict[str, str] | None = None, code: AuthErrorCode = AuthErrorCode.FORBIDDEN): super().__init__(message) self.message = message self.status_code = status_code self.headers = headers self.code = code now = time.monotonic() last_ts = self._last_log_ts_by_message.get(message, 0.0) if (now - last_ts) >= self._throttle_window_s: self._last_log_ts_by_message[message] = now logger.warning(message, extra={"exception:": Exception}) def __str__(self) -> str: return self.message class SmargonCommunicationError(Exception): """ Raised when Smargon HTTP communication fails (connection refused, timeout, bad HTTP status, etc). Keep the original exception in `__cause__` by using `raise ... from e`. """ def __init__( self, message: str = "Smargon communication error", *, endpoint: str | None = None, base_url: str | None = None, operation: str | None = None, # e.g. "GET" / "PUT" status_code: int | None = None, ): super().__init__(message) self.message = message self.endpoint = endpoint self.base_url = base_url self.operation = operation self.status_code = status_code logger.error( message, extra={ "device": "smargon", "operation": operation, "endpoint": endpoint, "base_url": base_url, "status_code": status_code, }, ) def __str__(self) -> str: return self.message class TellCommunicationError(Exception): """ Raised when TELL HTTP/PShell communication fails (timeouts, connection refused, etc). Intended to be caught centrally by FastAPI exception handlers. """ def __init__( self, message: str = "TELL communication error", *, endpoint: str | None = None, base_url: str | None = None, operation: str | None = None, # e.g. "GET" ): super().__init__(message) self.message = message self.endpoint = endpoint self.base_url = base_url self.operation = operation logger.error( message, extra={ "device": "tell", "operation": operation, "endpoint": endpoint, "base_url": base_url, }, ) def __str__(self) -> str: return self.message class JFJochCommunicationError(Exception): """ Raised when JFJoch HTTP/API communication fails. Intended for scan-time fallbacks and GUI-visible alerts. """ def __init__( self, message: str = "JFJoch communication error", *, operation: str | None = None, endpoint: str | None = None, base_url: str | None = None, status_code: int | None = None, ): super().__init__(message) self.message = message self.operation = operation self.endpoint = endpoint self.base_url = base_url self.status_code = status_code logger.error( message, extra={ "device": "jfjjoch", "operation": operation, "endpoint": endpoint, "base_url": base_url, "status_code": status_code, }, ) def __str__(self) -> str: return self.message class AareDBCommunicationError(Exception): """Raised when AareDB HTTPS communication fails.""" def __init__( self, message: str = "AareDB communication error", *, operation: str | None = None, endpoint: str | None = None, base_url: str | None = None, status_code: int | None = None, ): super().__init__(message) self.message = message self.operation = operation self.endpoint = endpoint self.base_url = base_url self.status_code = status_code logger.error( message, extra={ "device": "aaredb", "operation": operation, "endpoint": endpoint, "base_url": base_url, "status_code": status_code, }, ) def __str__(self) -> str: return self.message class AerotechCommunicationError(Exception): """ Raised when Aerotech HTTP/API communication fails (connection refused, timeout, bad HTTP status, etc). Keep the original exception in `__cause__` by using `raise ... from e`. """ def __init__( self, message: str = "Aerotech communication error", *, endpoint: str | None = None, base_url: str | None = None, operation: str | None = None, status_code: int | None = None, ): super().__init__(message) self.message = message self.endpoint = endpoint self.base_url = base_url self.operation = operation self.status_code = status_code logger.error( message, extra={ "device": "aerotech", "operation": operation, "endpoint": endpoint, "base_url": base_url, "status_code": status_code, }, ) def __str__(self) -> str: return self.message class MagnetPositionSensorErorr(Exception): def __init__(self, message: str = "Magnet position sensor error"): super().__init__(message) self.message = message logger.error( message, extra={"device": "magnet_position_sensor"}, ) def __str__(self) -> str: return self.message