from datetime import datetime, timezone class PIPException(Exception): def __init__(self, message: str, code: str = "PIP_ERROR", status_code: int = 500, details: dict | None = None): super().__init__(message) self.message = message self.code = code self.status_code = status_code self.details = details or {} self.timestamp = datetime.now(timezone.utc).isoformat() def to_dict(self) -> dict: return { "error": { "code": self.code, "message": self.message, "details": self.details, "timestamp": self.timestamp, } } class NotFoundException(PIPException): def __init__(self, resource: str, resource_id: str | None = None): msg = f"{resource} not found" if resource_id: msg = f"{resource} '{resource_id}' not found" super().__init__(message=msg, code="NOT_FOUND", status_code=404, details={"resource": resource, "resource_id": resource_id}) class ConflictException(PIPException): def __init__(self, message: str, details: dict | None = None): super().__init__(message=message, code="CONFLICT", status_code=409, details=details) class ValidationException(PIPException): def __init__(self, message: str, details: dict | None = None): super().__init__(message=message, code="VALIDATION_ERROR", status_code=422, details=details) class UnauthorizedException(PIPException): def __init__(self, message: str = "Authentication required"): super().__init__(message=message, code="UNAUTHORIZED", status_code=401) class ForbiddenException(PIPException): def __init__(self, message: str = "Insufficient permissions", required_role: str | None = None): details = {} if required_role: details["required_role"] = required_role super().__init__(message=message, code="FORBIDDEN", status_code=403, details=details) class RateLimitException(PIPException): def __init__(self, message: str = "Rate limit exceeded"): super().__init__(message=message, code="RATE_LIMIT_EXCEEDED", status_code=429) class ServiceUnavailableException(PIPException): def __init__(self, service: str, message: str | None = None): msg = message or f"Service '{service}' is unavailable" super().__init__(message=msg, code="SERVICE_UNAVAILABLE", status_code=503, details={"service": service}) class ConnectorException(PIPException): def __init__(self, connector_type: str, message: str, details: dict | None = None): d = details or {} d["connector_type"] = connector_type super().__init__(message=message, code="CONNECTOR_ERROR", status_code=502, details=d) class SyncException(PIPException): def __init__(self, pharmacy_id: str, message: str, details: dict | None = None): d = details or {} d["pharmacy_id"] = pharmacy_id super().__init__(message=message, code="SYNC_ERROR", status_code=502, details=d)