""" Service layer for job processing business logic. This module provides high-level operations for managing processing jobs with strict status transition validation and business rules enforcement. """ from typing import Optional from app.database.repositories.job_repository import JobRepository from app.exceptions.job_exceptions import InvalidStatusTransitionError from app.models.job import ProcessingJob, ProcessingStatus from app.models.types import PyObjectId class JobService: """ Service for processing job business logic operations. Provides high-level job management with strict status transition validation and business rule enforcement. """ def __init__(self, database): """ Initialize service with job repository. Args: repository: Optional JobRepository instance (creates default if None) """ self.db = database self.repository = JobRepository(database) async def initialize(self): await self.repository.initialize() return self async def create_job(self, document_id: PyObjectId, task_id: Optional[str] = None) -> ProcessingJob: """ Create a new processing job. Args: document_id: Reference to the file document task_id: Optional Celery task UUID Returns: The created ProcessingJob Raises: JobRepositoryError: If database operation fails """ return await self.repository.create_job(document_id, task_id) async def get_job_by_id(self, job_id: PyObjectId) -> ProcessingJob: """ Retrieve a job by its ID. Args: job_id: The job ObjectId Returns: The ProcessingJob document Raises: JobNotFoundError: If job doesn't exist JobRepositoryError: If database operation fails """ return await self.repository.find_job_by_id(job_id) async def mark_job_as_started(self, job_id: PyObjectId) -> ProcessingJob: """ Mark a job as started (PENDING → PROCESSING). Args: job_id: The job ObjectId Returns: The updated ProcessingJob Raises: JobNotFoundError: If job doesn't exist InvalidStatusTransitionError: If job is not in PENDING status JobRepositoryError: If database operation fails """ # Get current job to validate transition current_job = await self.repository.find_job_by_id(job_id) # Validate status transition if current_job.status != ProcessingStatus.PENDING: raise InvalidStatusTransitionError(current_job.status, ProcessingStatus.PROCESSING) # Update status return await self.repository.update_job_status(job_id, ProcessingStatus.PROCESSING) async def mark_job_as_completed(self, job_id: PyObjectId) -> ProcessingJob: """ Mark a job as completed (PROCESSING → COMPLETED). Args: job_id: The job ObjectId Returns: The updated ProcessingJob Raises: JobNotFoundError: If job doesn't exist InvalidStatusTransitionError: If job is not in PROCESSING status JobRepositoryError: If database operation fails """ # Get current job to validate transition current_job = await self.repository.find_job_by_id(job_id) # Validate status transition if current_job.status != ProcessingStatus.PROCESSING: raise InvalidStatusTransitionError(current_job.status, ProcessingStatus.COMPLETED) # Update status return await self.repository.update_job_status(job_id, ProcessingStatus.COMPLETED) async def mark_job_as_failed( self, job_id: PyObjectId, error_message: Optional[str] = None ) -> ProcessingJob: """ Mark a job as failed (PROCESSING → FAILED). Args: job_id: The job ObjectId error_message: Optional error description Returns: The updated ProcessingJob Raises: JobNotFoundError: If job doesn't exist InvalidStatusTransitionError: If job is not in PROCESSING status JobRepositoryError: If database operation fails """ # Get current job to validate transition current_job = await self.repository.find_job_by_id(job_id) # Validate status transition if current_job.status != ProcessingStatus.PROCESSING: raise InvalidStatusTransitionError(current_job.status, ProcessingStatus.FAILED) # Update status with error message return await self.repository.update_job_status( job_id, ProcessingStatus.FAILED, error_message ) async def delete_job(self, job_id: PyObjectId) -> bool: """ Delete a job from the database. Args: job_id: The job ObjectId Returns: True if job was deleted, False if not found Raises: JobRepositoryError: If database operation fails """ return await self.repository.delete_job(job_id) async def get_jobs_by_status(self, status: ProcessingStatus) -> list[ProcessingJob]: """ Retrieve all jobs with a specific status. Args: status: The processing status to filter by Returns: List of ProcessingJob documents Raises: JobRepositoryError: If database operation fails """ return await self.repository.get_jobs_by_status(status)