Update models.py

This commit is contained in:
thekiwismarthome
2026-02-13 13:53:36 +13:00
committed by GitHub
parent c67cc4b445
commit 4ce63938e9
@@ -1,104 +1,106 @@
"""Data models for Shopping List Manager.""" """Data models for Shopping List Manager."""
from dataclasses import dataclass, asdict from dataclasses import dataclass, field, asdict
from typing import Dict from datetime import datetime
from typing import Optional, List, Dict, Any
import uuid
def generate_id() -> str:
"""Generate a unique ID."""
return str(uuid.uuid4())
def current_timestamp() -> str:
"""Get current ISO timestamp."""
return datetime.utcnow().isoformat() + "Z"
@dataclass
class Category:
"""Category model."""
id: str
name: str
icon: str
color: str
sort_order: int
system: bool = True
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
return asdict(self)
@dataclass @dataclass
class Product: class Product:
""" """Product model."""
Product catalog entry - authoritative product definition. id: str
Products exist independently of the shopping list.
They define WHAT can be shopped, not HOW MUCH is needed.
"""
key: str
name: str name: str
category: str = "other" category_id: str
unit: str = "pcs" aliases: List[str] = field(default_factory=list)
image: str = "" default_unit: str = "units"
default_quantity: float = 1
price: Optional[float] = None
currency: Optional[str] = None
price_per_unit: Optional[float] = None
price_updated: Optional[str] = None
image_url: Optional[str] = None
image_source: Optional[str] = None
barcode: Optional[str] = None
brands: List[str] = field(default_factory=list)
nutrition: Optional[Dict[str, Any]] = None
user_frequency: int = 0
last_used: Optional[str] = None
custom: bool = False
source: str = "user"
def to_dict(self) -> dict: def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for storage/transmission.""" """Convert to dictionary."""
return { return asdict(self)
"key": self.key,
"name": self.name,
"category": self.category,
"unit": self.unit,
"image": self.image
}
@staticmethod
def from_dict(data: dict) -> 'Product':
"""Create Product from dictionary."""
return Product(
key=data["key"],
name=data["name"],
category=data.get("category", "other"),
unit=data.get("unit", "pcs"),
image=data.get("image", "")
)
def __post_init__(self):
"""Validate product data."""
if not self.key:
raise ValueError("Product key cannot be empty")
if not self.name:
raise ValueError("Product name cannot be empty")
@dataclass @dataclass
class ActiveItem: class Item:
""" """Shopping list item model."""
Shopping list state - quantity only. id: str
list_id: str
name: str
category_id: str
product_id: Optional[str] = None
quantity: float = 1
unit: str = "units"
note: Optional[str] = None
checked: bool = False
checked_at: Optional[str] = None
created_at: str = field(default_factory=current_timestamp)
updated_at: str = field(default_factory=current_timestamp)
image_url: Optional[str] = None
order_index: int = 0
price: Optional[float] = None
estimated_total: Optional[float] = None
barcode: Optional[str] = None
Contains NO product metadata, only references products by key. def to_dict(self) -> Dict[str, Any]:
qty > 0 means "on the list" """Convert to dictionary."""
qty == 0 means "not on the list" (should be removed) return asdict(self)
"""
qty: int
def to_dict(self) -> dict: def calculate_total(self) -> None:
"""Convert to dictionary for storage/transmission.""" """Calculate estimated total from quantity and price."""
return {"qty": self.qty} if self.price is not None:
self.estimated_total = self.quantity * self.price
@staticmethod
def from_dict(data: dict) -> 'ActiveItem':
"""Create ActiveItem from dictionary."""
return ActiveItem(qty=data["qty"])
def __post_init__(self):
"""Validate quantity."""
if self.qty < 0:
raise ValueError("Quantity cannot be negative")
class InvariantError(Exception): @dataclass
""" class ShoppingList:
Raised when the core data model invariant is violated. """Shopping list model."""
id: str
name: str
icon: str = "mdi:cart"
created_at: str = field(default_factory=current_timestamp)
updated_at: str = field(default_factory=current_timestamp)
item_order: List[str] = field(default_factory=list)
category_order: List[str] = field(default_factory=list)
active: bool = False
Invariant: Every key in active_list MUST exist in products. def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
If this exception is raised, the system is in an inconsistent state return asdict(self)
and must be repaired before continuing.
"""
pass
def validate_invariant(products: Dict[str, Product],
active_list: Dict[str, ActiveItem]) -> None:
"""
Validate the core data model invariant.
Args:
products: Product catalog dictionary
active_list: Active shopping list dictionary
Raises:
InvariantError: If any key in active_list doesn't exist in products
"""
for key in active_list:
if key not in products:
raise InvariantError(
f"Invariant violated: active_list contains unknown product key '{key}'. "
f"This product must be added to the catalog first."
)