fooder-api/fooder/domain/base.py
2026-04-07 16:49:30 +02:00

94 lines
2.7 KiB
Python

from datetime import datetime
from sqlalchemy import DateTime
from sqlalchemy.orm import DeclarativeBase, Mapped, declared_attr, mapped_column
from fooder.utils.datetime import utc_now
from fooder.utils.password_helper import password_helper
class Base(DeclarativeBase):
pass
class CommonMixin:
@declared_attr.directive
def __tablename__(cls) -> str:
return cls.__name__.lower() # type: ignore
id: Mapped[int] = mapped_column(primary_key=True)
version: Mapped[int] = mapped_column(default=0)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utc_now)
last_changed: Mapped[datetime] = mapped_column(
DateTime, default=utc_now, onupdate=utc_now
)
__mapper_args__ = {"version_id_col": version}
class SoftDeleteMixin:
deleted_at: Mapped[datetime | None] = mapped_column(
DateTime, nullable=True, default=None
)
class PasswordMixin:
hashed_password: Mapped[str]
def set_password(self, password: str) -> None:
self.hashed_password = password_helper.hash(password)
def verify_password(self, password: str) -> bool:
return password_helper.verify(password, self.hashed_password)
class EntryMacrosMixin:
"""Computed macros for entry-like models that scale product macros by grams."""
@property
def amount(self) -> float:
return self.grams / 100 # type: ignore[attr-defined]
@property
def calories(self) -> float:
return self.amount * self.product.calories # type: ignore[attr-defined]
@property
def protein(self) -> float:
return self.amount * self.product.protein # type: ignore[attr-defined]
@property
def carb(self) -> float:
return self.amount * self.product.carb # type: ignore[attr-defined]
@property
def fat(self) -> float:
return self.amount * self.product.fat # type: ignore[attr-defined]
@property
def fiber(self) -> float:
return self.amount * self.product.fiber # type: ignore[attr-defined]
class AggregateMacrosMixin:
"""Computed macros for models that sum macros across child entries."""
@property
def calories(self) -> float:
return sum(e.calories for e in self.entries) # type: ignore[attr-defined]
@property
def protein(self) -> float:
return sum(e.protein for e in self.entries) # type: ignore[attr-defined]
@property
def carb(self) -> float:
return sum(e.carb for e in self.entries) # type: ignore[attr-defined]
@property
def fat(self) -> float:
return sum(e.fat for e in self.entries) # type: ignore[attr-defined]
@property
def fiber(self) -> float:
return sum(e.fiber for e in self.entries) # type: ignore[attr-defined]