|
3 | 3 | from uuid import UUID |
4 | 4 |
|
5 | 5 | from pydantic import model_validator |
6 | | -from sqlalchemy import BigInteger, Column, DateTime, UniqueConstraint, text |
| 6 | +from sqlalchemy import BigInteger, Column, DateTime, UniqueConstraint, event, text |
| 7 | +from sqlalchemy.engine import Connection |
| 8 | +from sqlalchemy.orm import Mapper |
7 | 9 | from sqlmodel import Field, SQLModel |
8 | 10 |
|
9 | 11 |
|
@@ -60,3 +62,29 @@ def validate_expire(self) -> Self: |
60 | 62 | if self.expires_at < self.created_at: |
61 | 63 | raise ValueError("Expiration time cannot be earlier than creation time") |
62 | 64 | return self |
| 65 | + |
| 66 | + |
| 67 | +@event.listens_for(File, "after_delete") |
| 68 | +def on_file_delete(mapper: Mapper, connection: Connection, target: File): |
| 69 | + """ |
| 70 | + Event hook to evict the file from the global app state (Redis) after it is deleted from the DB. |
| 71 | + This ensures that total_space_used is updated and the file is removed from active_uploads. |
| 72 | + """ |
| 73 | + import asyncio |
| 74 | + |
| 75 | + from app.states.app import AppState |
| 76 | + |
| 77 | + # Capture values to avoid issues with target being detached/deleted |
| 78 | + file_key = target.key |
| 79 | + file_size = target.size |
| 80 | + |
| 81 | + async def do_evict(): |
| 82 | + await AppState.evict_files(file_keys=[file_key], freed_bytes=file_size) |
| 83 | + |
| 84 | + try: |
| 85 | + loop = asyncio.get_running_loop() |
| 86 | + loop.create_task(do_evict()) |
| 87 | + except RuntimeError: |
| 88 | + # If no loop is running, we might be in a sync context. |
| 89 | + # In this app, it's expected to have a loop in FastAPI or Celery async tasks. |
| 90 | + pass |
0 commit comments