schedule-manager / backend /app /exceptions.py
Jessedbrown's picture
build class schedule manager
e310cf5
Raw History Blame Contribute Delete
5.7 kB
from typing import Any, Optional
from datetime import date, time
class AppException(Exception):
status_code: int = 500
error_code: str = "internal_error"
def __init__(
self,
message: str,
details: Optional[dict[str, Any]] = None,
error_code: Optional[str] = None,
):
self.message = message
self.details = details or {}
if error_code:
self.error_code = error_code
super().__init__(message)
def to_dict(self) -> dict[str, Any]:
response = {
"error": self.error_code,
"message": self.message,
}
if self.details:
response["details"] = self.details
return response
class ValidationError(AppException):
status_code = 400
error_code = "validation_error"
def __init__(
self,
message: str,
field: Optional[str] = None,
invalid_values: Optional[list] = None,
**kwargs,
):
details = {}
if field:
details["field"] = field
if invalid_values:
details["invalid_values"] = invalid_values
super().__init__(message, details=details, **kwargs)
class EntityNotFoundError(ValidationError):
error_code = "entity_not_found"
def __init__(
self,
entity_type: str,
entity_id: Optional[int] = None,
entity_ids: Optional[list[int]] = None,
):
if entity_ids:
message = f"{entity_type}(s) not found: {sorted(entity_ids)}"
details = {"entity_type": entity_type, "missing_ids": sorted(entity_ids)}
else:
message = f"{entity_type} with ID {entity_id} not found"
details = {"entity_type": entity_type, "id": entity_id}
super().__init__(message, details=details)
class NotFoundError(AppException):
status_code = 404
error_code = "not_found"
def __init__(
self,
resource_type: str,
resource_id: int,
):
message = f"{resource_type} with ID {resource_id} not found"
details = {"resource_type": resource_type, "id": resource_id}
super().__init__(message, details=details)
class ConflictError(AppException):
status_code = 409
error_code = "conflict"
def __init__(
self,
message: str = "Cannot save class due to scheduling conflicts",
conflicts: Optional[list[dict]] = None,
):
details = {"conflicts": conflicts or []}
super().__init__(message, details=details)
def to_dict(self) -> dict[str, Any]:
return {
"error": self.error_code,
"message": self.message,
"conflicts": self.details.get("conflicts", []),
}
class ServerError(AppException):
status_code = 500
error_code = "internal_server_error"
def __init__(
self,
message: str = "An unexpected error occurred",
error_id: Optional[str] = None,
original_error: Optional[Exception] = None,
):
details = {}
if error_id:
details["error_id"] = error_id
self.original_error = original_error
super().__init__(message, details=details)
class RoomConflictError(ConflictError):
def __init__(
self,
room_name: str,
existing_course: str,
existing_time: str,
suggested_rooms: Optional[list[dict]] = None,
):
conflicts = [{
"type": "room",
"message": f"Room {room_name} is already booked for {existing_course}",
"suggestion": self._build_suggestion(suggested_rooms),
}]
super().__init__(conflicts=conflicts)
@staticmethod
def _build_suggestion(suggested_rooms: Optional[list[dict]]) -> str:
if not suggested_rooms:
return "Try selecting a different room or time slot"
room_names = [r["name"] for r in suggested_rooms[:3]]
return f"Available rooms: {', '.join(room_names)}"
class InstructorConflictError(ConflictError):
def __init__(
self,
instructor_name: str,
existing_course: str,
existing_time: str,
):
conflicts = [{
"type": "instructor",
"message": f"Instructor {instructor_name} is already teaching {existing_course}",
"suggestion": "Try selecting a different instructor or time slot",
}]
super().__init__(conflicts=conflicts)
class StudentConflictError(ConflictError):
pass
class CapacityConflictError(ConflictError):
def __init__(
self,
room_name: str,
capacity: int,
student_count: int,
suggested_rooms: Optional[list[dict]] = None,
):
conflicts = [{
"type": "capacity",
"message": f"Room {room_name} has capacity {capacity}, but {student_count} students enrolled",
"suggestion": self._build_suggestion(suggested_rooms, student_count),
}]
super().__init__(conflicts=conflicts)
@staticmethod
def _build_suggestion(suggested_rooms: Optional[list[dict]], student_count: int) -> str:
if not suggested_rooms:
return f"Select a room with capacity >= {student_count} or reduce enrollment"
suitable = [r for r in suggested_rooms if r.get("capacity", 0) >= student_count]
if suitable:
room_names = [f"{r['name']} (cap: {r['capacity']})" for r in suitable[:3]]
return f"Rooms with sufficient capacity: {', '.join(room_names)}"
return f"No available rooms with capacity >= {student_count}. Reduce enrollment."