Initial commit
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""Abstract repository contracts.
|
||||
|
||||
`Repository[T]` is the plain CRUD base. Use it for simple entities like
|
||||
VacationDays that have no GDPR deletion requirements.
|
||||
|
||||
`SoftDeleteRepository[T]` extends it for entities that need an audit trail
|
||||
(soft-delete) plus a GDPR hard delete. Use it for PersonalData and any other
|
||||
entity that directly holds PII. The `list()` contract on soft-deletable
|
||||
entities should always filter `WHERE deleted_at IS NULL` by default.
|
||||
|
||||
Dependency direction: routers/services depend on these interfaces only —
|
||||
never on a concrete DB class.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Generic, TypeVar
|
||||
from uuid import UUID
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Repository(ABC, Generic[T]):
|
||||
@abstractmethod
|
||||
async def create(self, item: T) -> T:
|
||||
"""Persist a new item and return it."""
|
||||
|
||||
@abstractmethod
|
||||
async def get_by_id(self, id: UUID) -> T | None:
|
||||
"""Return the item or None if it does not exist."""
|
||||
|
||||
@abstractmethod
|
||||
async def update(self, id: UUID, item: T) -> T:
|
||||
"""Update an existing item. Raise NotFoundError if missing."""
|
||||
|
||||
@abstractmethod
|
||||
async def delete(self, id: UUID) -> None:
|
||||
"""Remove an item. Raise NotFoundError if missing."""
|
||||
|
||||
@abstractmethod
|
||||
async def list(self, **filters: object) -> list[T]:
|
||||
"""Return items matching the given filters."""
|
||||
|
||||
|
||||
class SoftDeleteRepository(Repository[T], Generic[T]):
|
||||
"""Extended contract for GDPR-sensitive entities.
|
||||
|
||||
`delete()` on these entities sets `deleted_at` (soft delete for audit trail);
|
||||
`hard_delete()` removes the row entirely (GDPR erasure). Implementations of
|
||||
`list()` must filter `WHERE deleted_at IS NULL` unless `include_deleted=True`
|
||||
is passed.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def soft_delete(self, id: UUID) -> None:
|
||||
"""Set deleted_at to now. Raise NotFoundError if missing."""
|
||||
|
||||
@abstractmethod
|
||||
async def hard_delete(self, id: UUID) -> None:
|
||||
"""Permanently remove the row (GDPR erasure). Raise NotFoundError if missing."""
|
||||
|
||||
async def delete(self, id: UUID) -> None:
|
||||
"""Default: calls soft_delete. Override only if hard delete is the right default."""
|
||||
await self.soft_delete(id)
|
||||
Reference in New Issue
Block a user