73 lines
1.9 KiB
Python
73 lines
1.9 KiB
Python
"""
|
|
©AngelaMos | 2026
|
|
Base.py
|
|
"""
|
|
|
|
from typing import Any, Self
|
|
from datetime import datetime, UTC
|
|
|
|
from mongoengine import (
|
|
Document,
|
|
DateTimeField,
|
|
QuerySet,
|
|
)
|
|
|
|
from app.config import settings
|
|
from app.core.errors import NotFoundError
|
|
|
|
|
|
class BaseDocument(Document): # type: ignore[misc]
|
|
"""
|
|
Abstract base with timestamps and common query helpers
|
|
"""
|
|
meta = {"abstract": True} # noqa: RUF012
|
|
|
|
created_at = DateTimeField(default = lambda: datetime.now(UTC))
|
|
updated_at = DateTimeField(default = lambda: datetime.now(UTC))
|
|
|
|
def save(self, *args: Any, **kwargs: Any) -> Any:
|
|
"""
|
|
Auto-update the updated_at timestamp on every save
|
|
"""
|
|
self.updated_at = datetime.now(UTC)
|
|
return super().save(*args, **kwargs)
|
|
|
|
@classmethod
|
|
def get_by_id(cls, doc_id: str) -> Self:
|
|
"""
|
|
Fetch by ID or raise NotFoundError
|
|
"""
|
|
doc = cls.objects(id = doc_id).first()
|
|
if doc is None:
|
|
raise NotFoundError(f"{cls.__name__} not found")
|
|
return doc # type: ignore[no-any-return]
|
|
|
|
@classmethod
|
|
def get_or_none(cls, doc_id: str) -> Self | None:
|
|
"""
|
|
Fetch by ID or return None
|
|
"""
|
|
return cls.objects(id = doc_id).first() # type: ignore[no-any-return]
|
|
|
|
@classmethod
|
|
def paginate(
|
|
cls,
|
|
queryset: QuerySet | None = None,
|
|
page: int = 1,
|
|
per_page: int = settings.DEFAULT_PAGE_SIZE,
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Return a paginated result dict from any queryset
|
|
"""
|
|
qs = queryset if queryset is not None else cls.objects
|
|
total = qs.count()
|
|
offset = (page - 1) * per_page
|
|
items = list(qs.skip(offset).limit(per_page))
|
|
return {
|
|
"items": items,
|
|
"total": total,
|
|
"page": page,
|
|
"per_page": per_page,
|
|
"pages": (total + per_page - 1) // per_page,
|
|
}
|