from __future__ import annotations import datetime from typing import ClassVar, List, Optional from pydantic import Field, model_validator from common import ApiModel from ingredients import Ingredient from api.dtos import MemberRef from recipes import Recipe class MealRecipe(ApiModel): meal_id: int recipe_id: int servings: float recipe: Optional[Recipe] = None class Meal(ApiModel): KEYS: ClassVar[List[str]] = ["id", "suggested_date", "consumed_date", "purchase_date"] id: int = -1 suggested_date: datetime.datetime consumed_date: Optional[datetime.datetime] = None chefs: List[MemberRef] = Field(default_factory=list) cleanup: List[MemberRef] = Field(default_factory=list) consumers: List[MemberRef] = Field(default_factory=list) recipes: List[MealRecipe] = Field(default_factory=list) extra_ingredients: List[Ingredient] = Field(default_factory=list) # Set from shopping list purchase_date: Optional[datetime.datetime] = None @model_validator(mode="before") @classmethod def _coerce_members(cls, data: dict) -> dict: # Accept legacy Person objects in tests by coercing to MemberRef def to_member_ref(x): # Already a mapping suitable for MemberRef or an instance with id/display_name if isinstance(x, dict): if "id" in x and ("display_name" in x or "displayName" in x or "name" in x): # normalize display_name key if "display_name" not in x: dn = x.get("displayName") or x.get("name") x = {**x, "display_name": dn} return x return x # Object with attributes pid = getattr(x, "id", None) dname = getattr(x, "display_name", None) or getattr(x, "name", None) if isinstance(pid, int) and dname: return {"id": pid, "display_name": dname} return x if isinstance(data, dict): for key in ("chefs", "cleanup", "consumers"): val = data.get(key) if isinstance(val, list): data[key] = [to_member_ref(v) for v in val] return data