Files
aitbc/apps/coordinator-api/src/app/exceptions.py
aitbc 9db720add8
Some checks failed
Documentation Validation / validate-docs (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
API Endpoint Tests / test-api-endpoints (push) Has been cancelled
CLI Tests / test-cli (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Package Tests / test-python-packages (map[name:aitbc-agent-sdk path:packages/py/aitbc-agent-sdk]) (push) Has been cancelled
Package Tests / test-python-packages (map[name:aitbc-core path:packages/py/aitbc-core]) (push) Has been cancelled
Package Tests / test-python-packages (map[name:aitbc-crypto path:packages/py/aitbc-crypto]) (push) Has been cancelled
Package Tests / test-python-packages (map[name:aitbc-sdk path:packages/py/aitbc-sdk]) (push) Has been cancelled
Package Tests / test-javascript-packages (map[name:aitbc-sdk-js path:packages/js/aitbc-sdk]) (push) Has been cancelled
Package Tests / test-javascript-packages (map[name:aitbc-token path:packages/solidity/aitbc-token]) (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
Systemd Sync / sync-systemd (push) Has been cancelled
docs: add code quality and type checking workflows to master index
- Add Code Quality Module section with pre-commit hooks and quality checks
- Add Type Checking CI/CD Module section with MyPy workflow and coverage
- Update README with code quality achievements and project structure
- Migrate FastAPI apps from deprecated on_event to lifespan context manager
- Update pyproject.toml files to reference consolidated dependencies
- Remove unused app.py import in coordinator-api
- Add type hints to agent
2026-03-31 21:45:43 +02:00

259 lines
7.3 KiB
Python
Executable File

"""
Exception classes and error response schemas for AITBC coordinator
Provides structured error responses for consistent API error handling.
"""
from datetime import datetime
from typing import Any
from pydantic import BaseModel, Field
class ErrorDetail(BaseModel):
"""Detailed error information."""
field: str | None = Field(None, description="Field that caused the error")
message: str = Field(..., description="Error message")
code: str | None = Field(None, description="Error code for programmatic handling")
class ErrorResponse(BaseModel):
"""Standardized error response for all API errors."""
error: dict[str, Any] = Field(..., description="Error information")
timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat() + "Z")
request_id: str | None = Field(None, description="Request ID for tracing")
class Config:
json_schema_extra = {
"example": {
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"status": 422,
"details": [{"field": "email", "message": "Invalid email format", "code": "invalid_format"}],
},
"timestamp": "2026-02-13T21:00:00Z",
"request_id": "req_abc123",
}
}
class AITBCError(Exception):
"""Base exception for all AITBC errors"""
error_code: str = "INTERNAL_ERROR"
status_code: int = 500
def to_response(self, request_id: str | None = None) -> ErrorResponse:
"""Convert exception to standardized error response."""
return ErrorResponse(
error={"code": self.error_code, "message": str(self), "status": self.status_code, "details": []},
request_id=request_id,
)
class AuthenticationError(AITBCError):
"""Raised when authentication fails"""
error_code: str = "AUTHENTICATION_ERROR"
status_code: int = 401
def __init__(self, message: str = "Authentication failed"):
super().__init__(message)
class AuthorizationError(AITBCError):
"""Raised when authorization fails"""
error_code: str = "AUTHORIZATION_ERROR"
status_code: int = 403
def __init__(self, message: str = "Not authorized to perform this action"):
super().__init__(message)
class RateLimitError(AITBCError):
"""Raised when rate limit is exceeded"""
error_code: str = "RATE_LIMIT_EXCEEDED"
status_code: int = 429
def __init__(self, message: str = "Rate limit exceeded", retry_after: int = 60):
super().__init__(message)
self.retry_after = retry_after
def to_response(self, request_id: str | None = None) -> ErrorResponse:
return ErrorResponse(
error={
"code": self.error_code,
"message": str(self),
"status": self.status_code,
"details": [{"retry_after": self.retry_after}],
},
request_id=request_id,
)
class APIError(AITBCError):
"""Raised when API request fails"""
error_code: str = "API_ERROR"
status_code: int = 500
def __init__(self, message: str, status_code: int = None, response: dict = None):
super().__init__(message)
self.status_code = status_code or self.status_code
self.response = response
class ConfigurationError(AITBCError):
"""Raised when configuration is invalid"""
error_code: str = "CONFIGURATION_ERROR"
status_code: int = 500
def __init__(self, message: str = "Invalid configuration"):
super().__init__(message)
class ConnectorError(AITBCError):
"""Raised when connector operation fails"""
error_code: str = "CONNECTOR_ERROR"
status_code: int = 502
def __init__(self, message: str = "Connector operation failed"):
super().__init__(message)
class PaymentError(ConnectorError):
"""Raised when payment operation fails"""
error_code: str = "PAYMENT_ERROR"
status_code: int = 402
def __init__(self, message: str = "Payment operation failed"):
super().__init__(message)
class ValidationError(AITBCError):
"""Raised when data validation fails"""
error_code: str = "VALIDATION_ERROR"
status_code: int = 422
def __init__(self, message: str = "Validation failed", details: list[ErrorDetail] = None):
super().__init__(message)
self.details = details or []
def to_response(self, request_id: str | None = None) -> ErrorResponse:
return ErrorResponse(
error={
"code": self.error_code,
"message": str(self),
"status": self.status_code,
"details": [{"field": d.field, "message": d.message, "code": d.code} for d in self.details],
},
request_id=request_id,
)
class WebhookError(AITBCError):
"""Raised when webhook processing fails"""
error_code: str = "WEBHOOK_ERROR"
status_code: int = 500
def __init__(self, message: str = "Webhook processing failed"):
super().__init__(message)
class ERPError(ConnectorError):
"""Raised when ERP operation fails"""
error_code: str = "ERP_ERROR"
status_code: int = 502
def __init__(self, message: str = "ERP operation failed"):
super().__init__(message)
class SyncError(ConnectorError):
"""Raised when synchronization fails"""
error_code: str = "SYNC_ERROR"
status_code: int = 500
def __init__(self, message: str = "Synchronization failed"):
super().__init__(message)
class TimeoutError(AITBCError):
"""Raised when operation times out"""
error_code: str = "TIMEOUT_ERROR"
status_code: int = 504
def __init__(self, message: str = "Operation timed out"):
super().__init__(message)
class TenantError(ConnectorError):
"""Raised when tenant operation fails"""
error_code: str = "TENANT_ERROR"
status_code: int = 400
def __init__(self, message: str = "Tenant operation failed"):
super().__init__(message)
class QuotaExceededError(ConnectorError):
"""Raised when resource quota is exceeded"""
error_code: str = "QUOTA_EXCEEDED"
status_code: int = 429
def __init__(self, message: str = "Quota exceeded", limit: int = None):
super().__init__(message)
self.limit = limit
def to_response(self, request_id: str | None = None) -> ErrorResponse:
details = [{"limit": self.limit}] if self.limit else []
return ErrorResponse(
error={"code": self.error_code, "message": str(self), "status": self.status_code, "details": details},
request_id=request_id,
)
class BillingError(ConnectorError):
"""Raised when billing operation fails"""
error_code: str = "BILLING_ERROR"
status_code: int = 402
def __init__(self, message: str = "Billing operation failed"):
super().__init__(message)
class NotFoundError(AITBCError):
"""Raised when a resource is not found"""
error_code: str = "NOT_FOUND"
status_code: int = 404
def __init__(self, message: str = "Resource not found"):
super().__init__(message)
class ConflictError(AITBCError):
"""Raised when there's a conflict (e.g., duplicate resource)"""
error_code: str = "CONFLICT"
status_code: int = 409
def __init__(self, message: str = "Resource conflict"):
super().__init__(message)