You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
be-optimumoh/src/soft_delete.py

78 lines
2.1 KiB
Python

"""
Soft delete utilities.
Provides reusable functions to convert hard-delete operations into soft-deletes.
The `soft_delete_record` function works with any model that inherits `SoftDeleteMixin`
(which is included in `DefaultMixin`).
Usage in service files:
from src.soft_delete import soft_delete_record, apply_not_deleted_filter
async def delete(*, db_session, record_id: str):
await soft_delete_record(db_session=db_session, model=MyModel, record_id=record_id)
async def get_all(*, db_session, ...):
query = Select(MyModel)
query = apply_not_deleted_filter(query, MyModel)
...
"""
import logging
from datetime import datetime
from typing import TypeVar, Type
from sqlalchemy import Select, Update
from sqlalchemy.ext.asyncio import AsyncSession
import pytz
from src.config import TIMEZONE
log = logging.getLogger(__name__)
T = TypeVar("T")
def apply_not_deleted_filter(query: Select, model) -> Select:
"""
Appends a WHERE clause to exclude soft-deleted records.
Use this on all "get" queries to hide deleted data.
Example:
query = Select(OverhaulScope)
query = apply_not_deleted_filter(query, OverhaulScope)
"""
if hasattr(model, "deleted_at"):
return query.filter(model.deleted_at.is_(None))
return query
async def soft_delete_record(
*,
db_session: AsyncSession,
model: Type[T],
record_id: str,
id_column: str = "id",
) -> None:
"""
Soft-delete a record by setting its `deleted_at` timestamp.
Instead of DELETE, this performs an UPDATE.
Args:
db_session: The async database session.
model: The SQLAlchemy model class.
record_id: The value of the ID to match.
id_column: The name of the ID column (default: "id").
"""
now = datetime.now(pytz.timezone(TIMEZONE))
id_col = getattr(model, id_column)
stmt = (
Update(model)
.where(id_col == record_id)
.values(deleted_at=now)
)
await db_session.execute(stmt)
await db_session.commit()
log.info(f"Soft-deleted {model.__tablename__} record: {record_id}")