Skip to content

Soft Deletes

Use Case 5: Soft Deletes Pattern

Scenario: Implement soft delete functionality for data recovery.

Python
from mongo_ops import BaseDocument, BaseRepository
from datetime import datetime
from typing import Optional

class SoftDeleteDocument(BaseDocument):
    """Base document with soft delete support"""
    is_deleted: bool = False
    deleted_at: Optional[datetime] = None
    deleted_by: Optional[str] = None

class Task(SoftDeleteDocument):
    title: str
    description: str
    assignee_id: str
    status: str = "pending"
    priority: str = "medium"

class SoftDeleteRepository(BaseRepository[T]):
    """Repository with soft delete operations"""

    async def soft_delete(self, id: str, deleted_by: str = None) -> Optional[T]:
        """Soft delete a document"""
        return await self.update(id, {
            "is_deleted": True,
            "deleted_at": datetime.utcnow(),
            "deleted_by": deleted_by
        })

    async def restore(self, id: str) -> Optional[T]:
        """Restore a soft-deleted document"""
        return await self.update(id, {
            "is_deleted": False,
            "deleted_at": None,
            "deleted_by": None
        })

    async def get_active(self, skip: int = 0, limit: int = 100):
        """Get only non-deleted documents"""
        return await self.get_many(
            filter={"is_deleted": False},
            skip=skip,
            limit=limit
        )

    async def get_deleted(self, skip: int = 0, limit: int = 100):
        """Get deleted documents"""
        return await self.get_many(
            filter={"is_deleted": True},
            skip=skip,
            limit=limit
        )

    async def permanent_delete(self, id: str) -> bool:
        """Permanently delete a document"""
        return await self.delete(id)

class TaskRepository(SoftDeleteRepository[Task]):
    def __init__(self):
        super().__init__("tasks", Task)

# Usage in FastAPI
@app.delete("/tasks/{task_id}")
async def soft_delete_task(task_id: str, user_id: str):
    task = await task_repo.soft_delete(task_id, deleted_by=user_id)
    if not task:
        raise HTTPException(status_code=404, detail="Task not found")
    return {"message": "Task deleted", "task": task}

@app.post("/tasks/{task_id}/restore")
async def restore_task(task_id: str):
    task = await task_repo.restore(task_id)
    if not task:
        raise HTTPException(status_code=404, detail="Task not found")
    return {"message": "Task restored", "task": task}

@app.get("/tasks/", response_model=List[Task])
async def list_active_tasks(skip: int = 0, limit: int = 10):
    return await task_repo.get_active(skip, limit)

@app.get("/tasks/deleted", response_model=List[Task])
async def list_deleted_tasks(skip: int = 0, limit: int = 10):
    return await task_repo.get_deleted(skip, limit)