from __future__ import annotations from dataclasses import dataclass, asdict, fields from datetime import date, datetime from typing import Any, Dict, Tuple, Type, TypeVar, Union Row = Tuple[Any, ...] | Dict[str, Any] # what sqlite3.Row returns T = TypeVar("T", bound="BaseModel") @dataclass() class BaseModel: """A tiny helper that gives every model a common interface.""" @classmethod def from_row(cls: Type[T], row: Row) -> T: """ Build a model instance from a sqlite3.Row (or a dict‑like object). Column names are matched to the dataclass field names. """ if isinstance(row, dict): data = row else: # sqlite3.Row behaves like a mapping, but we guard for safety data = dict(row) # Convert raw strings to proper Python types where we know the annotation converted: Dict[str, Any] = {} for f in fields(cls): value = data.get(f.name) if value is None: converted[f.name] = None continue # datetime/date handling – sqlite returns str in ISO format if f.type is datetime: converted[f.name] = datetime.fromisoformat(value) elif f.type is date: converted[f.name] = date.fromisoformat(value) else: converted[f.name] = value return cls(**converted) # type: ignore[arg-type] def to_dict(self) -> Dict[str, Any]: """Return a plain dict (useful for INSERT/UPDATE statements).""" return asdict(self) def __repr__(self) -> str: # a nicer representation when printing field_vals = ", ".join(f"{f.name}={getattr(self, f.name)!r}" for f in fields(self)) return f"{self.__class__.__name__}({field_vals})"