"""Domain job handlers: safety scoring, content analysis, uploads (US02-06, US05-02). Importing this module registers the ``safety_score``, ``analysis``, and ``upload_batch`` job types so the generic worker can run them per item. Each handler delegates to its service, which owns the real work and the privacy gate. Handlers are idempotent: re-scoring or re-analyzing one asset is safe after an interrupted attempt, and an upload batch refuses to re-run an attempt whose outcome is unknown. Providers/models are the service defaults here (real NsfwModel / vision provider); tests exercise the services directly with injected fakes rather than the worker. """ from __future__ import annotations from photo_pipeline.jobs.handlers import Cancelled, JobContext, register SAFETY_SCORE = "safety_score" ANALYSIS = "analysis" UPLOAD_BATCH = "upload_batch" # Both mutate the library's metadata/derived state; one at a time (concept §one job). LIBRARY_WRITE_LOCK = "library_write" # The uploader lane: one album batch at a time (concept §16). UPLOAD_LOCK = "upload" def _safety_score_item(asset_id: str, ctx: JobContext) -> None: from photo_pipeline.services.safety import SafetyService SafetyService(ctx.session_factory).score_assets([asset_id]) def _analysis_item(asset_id: str, ctx: JobContext) -> None: from photo_pipeline.services.analysis import AnalysisService AnalysisService(ctx.session_factory).run([asset_id]) def _upload_batch_item(batch_id: str, ctx: JobContext) -> None: """One item = one album batch. The upload itself is long and external, so the handler hands the job's cancellation check to the service, which stops the uploader and leaves a resumable batch.""" from photo_pipeline.config import Config from photo_pipeline.services.upload_batches import BatchState, UploadBatchService config = ctx.config if ctx.config is not None else Config.from_env() service = UploadBatchService(ctx.session_factory, config=config) batch = service.run(batch_id, worker_id=ctx.worker_id, cancelled=ctx.cancelled) if batch["state"] == BatchState.CANCELLED: raise Cancelled(f"upload batch {batch_id} was cancelled") if batch["state"] in (BatchState.FAILED, BatchState.UNKNOWN): raise RuntimeError(f"upload batch {batch_id} is {batch['state']}: {batch['error_code']}") register(SAFETY_SCORE, _safety_score_item) register(ANALYSIS, _analysis_item) register(UPLOAD_BATCH, _upload_batch_item)