📦 EqualifyEverything / equalify-reflow

📄 cleanup_service.py · 78 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78"""Cleanup service for denied job file removal.

Handles S3 temp file cleanup when jobs are denied during approval workflow.
Implements idempotent cleanup with graceful error handling.
"""

import logging
from typing import Any

from botocore.exceptions import ClientError

from ..config import settings

logger = logging.getLogger(__name__)


class CleanupService:
    """Service for cleaning up files from denied jobs."""

    def __init__(self, s3_client: Any):
        """Initialize cleanup service with S3 client.

        Args:
            s3_client: Boto3 S3 async client instance
        """
        self.s3 = s3_client
        self.temp_bucket = settings.s3_temp_bucket

    async def cleanup_job_files(self, s3_key: str) -> bool:
        """Delete temporary PDF file from S3.

        Idempotent operation - succeeds even if file doesn't exist.
        Logs errors but doesn't raise exceptions to avoid blocking
        the approval workflow.

        Args:
            s3_key: S3 object key to delete

        Returns:
            bool: True if cleanup successful, False if error occurred

        Example:
            >>> cleanup = CleanupService(s3_client)
            >>> success = await cleanup.cleanup_job_files("temp/abc-123.pdf")
            >>> # Returns True even if file already deleted
        """
        try:
            await self.s3.delete_object(
                Bucket=self.temp_bucket,
                Key=s3_key
            )
            logger.info(f"Successfully cleaned up S3 file: {s3_key}")
            return True

        except ClientError as e:
            error_code = e.response['Error']['Code']

            # File doesn't exist - idempotent success
            if error_code in ('NoSuchKey', '404'):
                logger.info(f"S3 file already deleted (idempotent): {s3_key}")
                return True

            # Other S3 errors - log and return False
            logger.error(
                f"S3 ClientError cleaning up {s3_key}: {error_code} - {str(e)}",
                exc_info=True
            )
            return False

        except Exception as e:
            # Log error but don't fail the approval workflow
            # Orphaned files can be cleaned up by timeout worker
            logger.error(
                f"Failed to cleanup S3 file {s3_key}: {str(e)}",
                exc_info=True
            )
            return False