feat(backend): refactor mono repository
This commit is contained in:
179
backend/models/dataclasses.py
Normal file
179
backend/models/dataclasses.py
Normal file
@@ -0,0 +1,179 @@
|
||||
# ------------------------------------------------------------
|
||||
# Central place for all data‑model definitions.
|
||||
# ------------------------------------------------------------
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, asdict, fields
|
||||
from datetime import date, datetime
|
||||
from typing import Any, Dict, Tuple, Type, TypeVar, Union
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Helper types – what sqlite3.Row returns (either a tuple‑like or a dict‑like)
|
||||
# ----------------------------------------------------------------------
|
||||
Row = Tuple[Any, ...] | Dict[str, Any]
|
||||
|
||||
T = TypeVar("T", bound="BaseModel")
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# BaseModel – common conversion helpers for every model
|
||||
# ----------------------------------------------------------------------
|
||||
class BaseModel:
|
||||
"""
|
||||
Minimal base class that knows how to:
|
||||
|
||||
* Build an instance from a SQLite row (or any mapping with column names).
|
||||
* Export itself as a plain ``dict`` suitable for INSERT/UPDATE statements.
|
||||
* Render a readable ``repr`` for debugging.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_row(cls: Type[T], row: Row) -> T:
|
||||
"""
|
||||
Convert a ``sqlite3.Row`` (or a dict‑like mapping) into a dataclass
|
||||
instance. Field names are matched to column names; ``None`` values are
|
||||
preserved verbatim. ``datetime`` and ``date`` columns are parsed from
|
||||
ISO‑8601 strings when necessary.
|
||||
"""
|
||||
# ``row`` may already be a dict – otherwise turn the Row into one.
|
||||
data = dict(row) if not isinstance(row, dict) else row
|
||||
|
||||
converted: Dict[str, Any] = {}
|
||||
for f in fields(cls):
|
||||
raw = data.get(f.name)
|
||||
|
||||
# Preserve ``None`` exactly as‑is.
|
||||
if raw is None:
|
||||
converted[f.name] = None
|
||||
continue
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 1️⃣ datetime handling
|
||||
# ------------------------------------------------------------------
|
||||
if f.type is datetime:
|
||||
# SQLite stores datetimes as ISO strings.
|
||||
if isinstance(raw, str):
|
||||
converted[f.name] = datetime.fromisoformat(raw)
|
||||
else:
|
||||
converted[f.name] = raw
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 2️⃣ date handling
|
||||
# ------------------------------------------------------------------
|
||||
elif f.type is date:
|
||||
if isinstance(raw, str):
|
||||
converted[f.name] = date.fromisoformat(raw)
|
||||
else:
|
||||
converted[f.name] = raw
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 3️⃣ fallback – keep whatever we received
|
||||
# ------------------------------------------------------------------
|
||||
else:
|
||||
converted[f.name] = raw
|
||||
|
||||
# Instantiate the concrete dataclass.
|
||||
return cls(**converted) # type: ignore[arg-type]
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Convenience helpers
|
||||
# ------------------------------------------------------------------
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Return a plain ``dict`` of the dataclass fields (good for INSERTs)."""
|
||||
return asdict(self)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Readable representation useful during debugging."""
|
||||
parts = ", ".join(f"{f.name}={getattr(self, f.name)!r}" for f in fields(self))
|
||||
return f"{self.__class__.__name__}({parts})"
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Concrete models – each one is a thin dataclass inheriting from BaseModel.
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
|
||||
# ---------- Logging tables ----------
|
||||
@dataclass
|
||||
class AcceptedLog(BaseModel):
|
||||
LogId: int
|
||||
MemberId: int
|
||||
ServiceId: int
|
||||
AcceptedAt: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeclineLog(BaseModel):
|
||||
DeclineId: int
|
||||
MemberId: int
|
||||
ServiceId: int
|
||||
DeclinedAt: datetime
|
||||
DeclineDate: date # the service day that was declined
|
||||
Reason: Union[str, None] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScheduledLog(BaseModel):
|
||||
LogId: int
|
||||
MemberId: int
|
||||
ServiceId: int
|
||||
ScheduledAt: datetime
|
||||
ExpiresAt: datetime
|
||||
|
||||
|
||||
# ---------- Core reference data ----------
|
||||
@dataclass
|
||||
class Classification(BaseModel):
|
||||
ClassificationId: int
|
||||
ClassificationName: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceType(BaseModel):
|
||||
ServiceTypeId: int
|
||||
TypeName: str
|
||||
|
||||
|
||||
# ---------- Primary domain entities ----------
|
||||
@dataclass
|
||||
class Member(BaseModel):
|
||||
MemberId: int
|
||||
FirstName: str
|
||||
LastName: str
|
||||
Email: Union[str, None] = None
|
||||
PhoneNumber: Union[str, None] = None
|
||||
ClassificationId: Union[int, None] = None
|
||||
Notes: Union[str, None] = None
|
||||
IsActive: int = 1
|
||||
LastScheduledAt: Union[datetime, None] = None
|
||||
LastAcceptedAt: Union[datetime, None] = None
|
||||
LastDeclinedAt: Union[datetime, None] = None
|
||||
DeclineStreak: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class Service(BaseModel):
|
||||
ServiceId: int
|
||||
ServiceTypeId: int
|
||||
ServiceDate: date
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceAvailability(BaseModel):
|
||||
ServiceAvailabilityId: int
|
||||
MemberId: int
|
||||
ServiceTypeId: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class Schedule(BaseModel):
|
||||
ScheduleId: int
|
||||
ServiceId: int
|
||||
MemberId: int
|
||||
Status: str # 'pending' | 'accepted' | 'declined'
|
||||
ScheduledAt: datetime
|
||||
AcceptedAt: Union[datetime, None] = None
|
||||
DeclinedAt: Union[datetime, None] = None
|
||||
ExpiresAt: Union[datetime, None] = None
|
||||
DeclineReason: Union[str, None] = None
|
||||
Reference in New Issue
Block a user