""" MemoirImage 模型与 API 用 dict 的互转(与 schema.normalize_image_asset 字段一致)。 """ from datetime import datetime from typing import Any from app.features.memoir.models import MemoirImage def _parse_optional_datetime(s: str | None): if not s: return None try: return datetime.fromisoformat(s.replace("Z", "+00:00")) except (ValueError, AttributeError): return None def memoir_image_to_dict(m: MemoirImage | None) -> dict[str, Any] | None: """将 MemoirImage 转为前端/API 使用的单条图片 dict。""" if not m: return None d: dict[str, Any] = { "placeholder": m.placeholder or "", "description": m.description or "", "index": m.order_index, "status": m.status or "pending", "prompt": m.prompt, "url": m.url, "storage_key": m.storage_key, "provider": m.provider, "style": m.style, "size": m.size, "error": m.error, "retryable": m.retryable, "created_at": m.created_at.isoformat() if m.created_at else None, "updated_at": m.updated_at.isoformat() if m.updated_at else None, } return d def image_dict_to_row_kwargs(d: dict[str, Any] | None) -> dict[str, Any]: """从单条图片 dict 提取可写入 MemoirImage 的字段(不含 id/chapter_id/section_id/order_index)。""" if not d or not isinstance(d, dict): return {} created = d.get("created_at") updated = d.get("updated_at") if isinstance(created, str): created = _parse_optional_datetime(created) elif not isinstance(created, datetime): created = None if isinstance(updated, str): updated = _parse_optional_datetime(updated) elif not isinstance(updated, datetime): updated = None return { "placeholder": d.get("placeholder") or None, "description": d.get("description") or None, "status": (d.get("status") or "pending").strip() or "pending", "prompt": d.get("prompt") or None, "url": d.get("url") or None, "storage_key": d.get("storage_key") or None, "provider": d.get("provider") or None, "style": d.get("style") or None, "size": d.get("size") or None, "error": d.get("error") or None, "retryable": d.get("retryable") if d.get("retryable") is not None else None, "created_at": created, "updated_at": updated, }