From c54d3c1f1e6e32db6b17d57f0da1f52996e6465e Mon Sep 17 00:00:00 2001 From: appleb_m Date: Fri, 27 Feb 2026 16:24:21 +0100 Subject: [PATCH] Exceptions: added handling for tellcommunication and smargon communication errors --- src/aare/common/exception_handler.py | 75 +++++++++++++++++++++++- src/aare/daq/server_exception_handler.py | 33 ++++++++++- 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/src/aare/common/exception_handler.py b/src/aare/common/exception_handler.py index 340b14eb..7098c362 100644 --- a/src/aare/common/exception_handler.py +++ b/src/aare/common/exception_handler.py @@ -1,7 +1,7 @@ from __future__ import annotations from aare.common.logger_config import setup_logger -from aare.common.error_codes import AuthErrorCode +from aare.common.error_codes import AuthErrorCode, DAQErrorCode logger = setup_logger("aareDAQ") @@ -64,6 +64,7 @@ class AXCFailed(Exception): def __str__(self) -> str: return self.message + class BeamlineBusyException(Exception): def __init__(self, message: str = "Beamline is in busy state"): super().__init__(message) @@ -73,6 +74,7 @@ class BeamlineBusyException(Exception): def __str__(self) -> str: return self.message + class SampleException(Exception): def __init__(self, message: str = "Sample not found"): super().__init__(message) @@ -82,6 +84,7 @@ class SampleException(Exception): def __str__(self) -> str: return self.message + class AuthenticationException(Exception): def __init__(self, message: str = "Authentication failed.", @@ -99,6 +102,7 @@ class AuthenticationException(Exception): def __str__(self) -> str: return self.message + class UserRightsException(Exception): def __init__(self, message: str = "User does not have rights to perform this action.", @@ -113,5 +117,74 @@ class UserRightsException(Exception): self.code = code logger.error(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 \ No newline at end of file diff --git a/src/aare/daq/server_exception_handler.py b/src/aare/daq/server_exception_handler.py index edff28cd..530864db 100644 --- a/src/aare/daq/server_exception_handler.py +++ b/src/aare/daq/server_exception_handler.py @@ -16,7 +16,8 @@ from aare.common.exception_handler import ( BeamlineBusyException, AuthenticationException, SampleException, - UserRightsException + UserRightsException, + SmargonCommunicationError, TellCommunicationError ) logger = setup_logger("aareDAQ") @@ -116,10 +117,38 @@ def register_exception_handlers(app) -> None: content=_error_payload(code="SAMPLE_NOT_FOUND", message=str(exc) or "Sample not found"), ) + @app.exception_handler(SmargonCommunicationError) + async def smargon_comm_handler(request: Request, exc: SmargonCommunicationError) -> JSONResponse: + return JSONResponse( + status_code=api_status.HTTP_503_SERVICE_UNAVAILABLE, + content=_error_payload( + code="SMARGON_UNAVAILABLE", + message=str(exc) or "Smargon is unavailable", + extra={ + "operation": getattr(exc, "operation", None), + "endpoint": getattr(exc, "endpoint", None), + }, + ), + ) + + @app.exception_handler(TellCommunicationError) + async def tell_comm_handler(request: Request, exc: TellCommunicationError) -> JSONResponse: + return JSONResponse( + status_code=api_status.HTTP_503_SERVICE_UNAVAILABLE, + content=_error_payload( + code="TELL_UNAVAILABLE", + message=str(exc) or "TELL is unavailable", + extra={ + "operation": getattr(exc, "operation", None), + "endpoint": getattr(exc, "endpoint", None), + }, + ), + ) + @app.exception_handler(Exception) async def unhandled_exception_handler(request: Request, exc: Exception) -> JSONResponse: logger.exception("Unhandled server exception") return JSONResponse( status_code=api_status.HTTP_500_INTERNAL_SERVER_ERROR, - content=_error_payload(code="INTERNAL_SERVER_ERROR", message="Internal server error"), + content=_error_payload(code="INTERNAL_SERVER_ERROR", message=str(exc) or "Internal server error"), )