""" Session service layer for business logic and database operations. This module handles all database operations for sessions, providing a clean separation between the API routes and data access layer. """ from typing import Optional from uuid import UUID from fastapi import HTTPException, status from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session from api.models.session import Session as SessionModel from api.models.project import Project from api.models.machine import Machine from api.schemas.session import SessionCreate, SessionUpdate def get_sessions(db: Session, skip: int = 0, limit: int = 100) -> tuple[list[SessionModel], int]: """ Retrieve a paginated list of sessions. Args: db: Database session skip: Number of records to skip (for pagination) limit: Maximum number of records to return Returns: tuple: (list of sessions, total count) Example: ```python sessions, total = get_sessions(db, skip=0, limit=50) print(f"Retrieved {len(sessions)} of {total} sessions") ``` """ # Get total count total = db.query(SessionModel).count() # Get paginated results, ordered by session_date descending (newest first) sessions = ( db.query(SessionModel) .order_by(SessionModel.session_date.desc()) .offset(skip) .limit(limit) .all() ) return sessions, total def get_session_by_id(db: Session, session_id: UUID) -> SessionModel: """ Retrieve a single session by its ID. Args: db: Database session session_id: UUID of the session to retrieve Returns: SessionModel: The session object Raises: HTTPException: 404 if session not found Example: ```python session = get_session_by_id(db, session_id) print(f"Found session: {session.session_title}") ``` """ session = db.query(SessionModel).filter(SessionModel.id == str(session_id)).first() if not session: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Session with ID {session_id} not found" ) return session def get_sessions_by_project(db: Session, project_id: UUID, skip: int = 0, limit: int = 100) -> tuple[list[SessionModel], int]: """ Retrieve sessions for a specific project. Args: db: Database session project_id: UUID of the project skip: Number of records to skip (for pagination) limit: Maximum number of records to return Returns: tuple: (list of sessions, total count) Example: ```python sessions, total = get_sessions_by_project(db, project_id) print(f"Found {total} sessions for project") ``` """ # Get total count total = db.query(SessionModel).filter(SessionModel.project_id == str(project_id)).count() # Get paginated results sessions = ( db.query(SessionModel) .filter(SessionModel.project_id == str(project_id)) .order_by(SessionModel.session_date.desc()) .offset(skip) .limit(limit) .all() ) return sessions, total def get_sessions_by_machine(db: Session, machine_id: UUID, skip: int = 0, limit: int = 100) -> tuple[list[SessionModel], int]: """ Retrieve sessions for a specific machine. Args: db: Database session machine_id: UUID of the machine skip: Number of records to skip (for pagination) limit: Maximum number of records to return Returns: tuple: (list of sessions, total count) Example: ```python sessions, total = get_sessions_by_machine(db, machine_id) print(f"Found {total} sessions on this machine") ``` """ # Get total count total = db.query(SessionModel).filter(SessionModel.machine_id == str(machine_id)).count() # Get paginated results sessions = ( db.query(SessionModel) .filter(SessionModel.machine_id == str(machine_id)) .order_by(SessionModel.session_date.desc()) .offset(skip) .limit(limit) .all() ) return sessions, total def create_session(db: Session, session_data: SessionCreate) -> SessionModel: """ Create a new session. Args: db: Database session session_data: Session creation data Returns: SessionModel: The created session object Raises: HTTPException: 404 if referenced project or machine not found HTTPException: 422 if validation fails HTTPException: 500 if database error occurs Example: ```python session_data = SessionCreate( session_title="Database migration work", session_date=date.today(), project_id="123e4567-e89b-12d3-a456-426614174000" ) session = create_session(db, session_data) print(f"Created session: {session.id}") ``` """ try: # Validate foreign keys if provided if session_data.project_id: project = db.query(Project).filter(Project.id == str(session_data.project_id)).first() if not project: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Project with ID {session_data.project_id} not found" ) if session_data.machine_id: machine = db.query(Machine).filter(Machine.id == str(session_data.machine_id)).first() if not machine: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Machine with ID {session_data.machine_id} not found" ) # Create new session instance db_session = SessionModel(**session_data.model_dump()) # Add to database db.add(db_session) db.commit() db.refresh(db_session) return db_session except HTTPException: db.rollback() raise except IntegrityError as e: db.rollback() # Handle foreign key constraint violations if "project_id" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"Invalid project_id: {session_data.project_id}" ) elif "machine_id" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"Invalid machine_id: {session_data.machine_id}" ) elif "client_id" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"Invalid client_id: {session_data.client_id}" ) else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Database error: {str(e)}" ) except Exception as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to create session: {str(e)}" ) def update_session(db: Session, session_id: UUID, session_data: SessionUpdate) -> SessionModel: """ Update an existing session. Args: db: Database session session_id: UUID of the session to update session_data: Session update data (only provided fields will be updated) Returns: SessionModel: The updated session object Raises: HTTPException: 404 if session, project, or machine not found HTTPException: 422 if validation fails HTTPException: 500 if database error occurs Example: ```python update_data = SessionUpdate( status="completed", duration_minutes=120 ) session = update_session(db, session_id, update_data) print(f"Updated session: {session.session_title}") ``` """ # Get existing session session = get_session_by_id(db, session_id) try: # Update only provided fields update_data = session_data.model_dump(exclude_unset=True) # Validate foreign keys if being updated if "project_id" in update_data and update_data["project_id"]: project = db.query(Project).filter(Project.id == str(update_data["project_id"])).first() if not project: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Project with ID {update_data['project_id']} not found" ) if "machine_id" in update_data and update_data["machine_id"]: machine = db.query(Machine).filter(Machine.id == str(update_data["machine_id"])).first() if not machine: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Machine with ID {update_data['machine_id']} not found" ) # Apply updates for field, value in update_data.items(): setattr(session, field, value) db.commit() db.refresh(session) return session except HTTPException: db.rollback() raise except IntegrityError as e: db.rollback() if "project_id" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid project_id" ) elif "machine_id" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid machine_id" ) elif "client_id" in str(e.orig): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Invalid client_id" ) else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Database error: {str(e)}" ) except Exception as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to update session: {str(e)}" ) def delete_session(db: Session, session_id: UUID) -> dict: """ Delete a session by its ID. Args: db: Database session session_id: UUID of the session to delete Returns: dict: Success message Raises: HTTPException: 404 if session not found HTTPException: 500 if database error occurs Example: ```python result = delete_session(db, session_id) print(result["message"]) # "Session deleted successfully" ``` """ # Get existing session (raises 404 if not found) session = get_session_by_id(db, session_id) try: db.delete(session) db.commit() return { "message": "Session deleted successfully", "session_id": str(session_id) } except Exception as e: db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to delete session: {str(e)}" )