| """ |
| Custom exception classes for the Booking Allocation Engine. |
| |
| This module defines the exception hierarchy used throughout the allocation system |
| to distinguish between transient errors (retryable) and permanent errors (non-retryable). |
| """ |
|
|
|
|
| class AllocationError(Exception): |
| """Base exception for all allocation-related errors.""" |
| |
| def __init__(self, message: str, booking_id: str = None, partner_id: str = None): |
| self.message = message |
| self.booking_id = booking_id |
| self.partner_id = partner_id |
| super().__init__(self.message) |
|
|
|
|
| class TransientError(AllocationError): |
| """ |
| Transient errors that may succeed on retry. |
| |
| These errors indicate temporary failures such as network issues, |
| database connection problems, or service unavailability. |
| Operations that raise TransientError should be retried with backoff. |
| """ |
| pass |
|
|
|
|
| class PermanentError(AllocationError): |
| """ |
| Permanent errors that will not succeed on retry. |
| |
| These errors indicate business logic violations, validation failures, |
| or data integrity issues that cannot be resolved by retrying. |
| Operations that raise PermanentError should not be retried. |
| """ |
| pass |
|
|
|
|
| class DatabaseConnectionError(TransientError): |
| """ |
| Database connection or query execution failure. |
| |
| Raised when PostgreSQL connection fails or times out. |
| Should be retried with exponential backoff. |
| """ |
| |
| def __init__(self, message: str, operation: str = None, booking_id: str = None): |
| self.operation = operation |
| super().__init__(message, booking_id=booking_id) |
|
|
|
|
| class RedisError(TransientError): |
| """ |
| Redis connection or operation failure. |
| |
| Raised when Redis connection fails or times out. |
| Should trigger fallback to PostgreSQL for data retrieval. |
| """ |
| |
| def __init__(self, message: str, key: str = None, operation: str = None): |
| self.key = key |
| self.operation = operation |
| super().__init__(message) |
|
|
|
|
| class NoEligiblePartnersError(PermanentError): |
| """ |
| No eligible partners found for booking. |
| |
| Raised when partner filtering returns empty result set. |
| Should set allocation_status to 'failed'. |
| """ |
| pass |
|
|
|
|
| class BookingAlreadyAssignedError(PermanentError): |
| """ |
| Booking already assigned to a partner. |
| |
| Raised when attempting to create offer for already-assigned booking. |
| Should abort allocation attempt. |
| """ |
| pass |
|
|
|
|
| class InvalidEventStructureError(PermanentError): |
| """ |
| Event structure validation failed. |
| |
| Raised when required fields are missing or have invalid types. |
| Event should be discarded and logged. |
| """ |
| |
| def __init__(self, message: str, event_data: dict = None): |
| self.event_data = event_data |
| super().__init__(message) |
|
|
|
|
| class OfferExpiredError(PermanentError): |
| """ |
| Offer has already expired. |
| |
| Raised when partner responds after offer expiry timeout. |
| Response should be rejected. |
| """ |
| |
| def __init__(self, message: str, offer_id: str = None, expiry_time: str = None): |
| self.offer_id = offer_id |
| self.expiry_time = expiry_time |
| super().__init__(message) |
|
|
|
|
| class DuplicateResponseError(PermanentError): |
| """ |
| Partner already responded to offer. |
| |
| Raised when duplicate accept/decline response received. |
| Duplicate response should be ignored. |
| """ |
| |
| def __init__(self, message: str, offer_id: str = None, previous_response: str = None): |
| self.offer_id = offer_id |
| self.previous_response = previous_response |
| super().__init__(message) |
|
|