# src/common/models.py from datetime import datetime from typing import Generic, Optional, TypeVar import uuid from pydantic import BaseModel, Field, SecretStr from sqlalchemy import Column, DateTime, String, func, event from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column from src.config import TIMEZONE import pytz from src.auth.service import CurrentUser from src.enums import ResponseStatus # SQLAlchemy Mixins class TimeStampMixin(object): """Timestamping mixin""" created_at = Column( DateTime(timezone=True), default=datetime.now(pytz.timezone(TIMEZONE))) created_at._creation_order = 9998 updated_at = Column( DateTime(timezone=True), default=datetime.now(pytz.timezone(TIMEZONE))) updated_at._creation_order = 9998 @staticmethod def _updated_at(mapper, connection, target): target.updated_at = datetime.now(pytz.timezone(TIMEZONE)) @classmethod def __declare_last__(cls): event.listen(cls, "before_update", cls._updated_at) class UUIDMixin: """UUID mixin""" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False) class IdentityMixin: """Identity mixin""" created_by = Column(String(100), nullable=True) updated_by = Column(String(100), nullable=True) class DefaultMixin(TimeStampMixin, UUIDMixin): """Default mixin""" pass # Pydantic Models class DefultBase(BaseModel): class Config: from_attributes = True validate_assignment = True arbitrary_types_allowed = True str_strip_whitespace = True json_encoders = { # custom output conversion for datetime datetime: lambda v: v.strftime("%Y-%m-%dT%H:%M:%S.%fZ") if v else None, SecretStr: lambda v: v.get_secret_value() if v else None, } class Pagination(DefultBase): itemsPerPage: int page: int total: int class PrimaryKeyModel(BaseModel): id: uuid.UUID # Define data type variable for generic response T = TypeVar('T') class StandardResponse(BaseModel, Generic[T]): data: Optional[T] = None message: str = "Success" status: ResponseStatus = ResponseStatus.SUCCESS