from pydantic import BaseModel, field_validator from typing import Optional, List, Any, Dict from datetime import datetime class DeviceBase(BaseModel): device_type: str label: str = "" reference: str = "" x: float = 100 y: float = 100 width: float = 120 height: float = 60 properties: Dict[str, Any] = {} pins: List[Dict[str, Any]] = [] class DeviceCreate(DeviceBase): diagram_id: int class DeviceUpdate(BaseModel): label: Optional[str] = None reference: Optional[str] = None x: Optional[float] = None y: Optional[float] = None width: Optional[float] = None height: Optional[float] = None properties: Optional[Dict[str, Any]] = None pins: Optional[List[Dict[str, Any]]] = None class Device(DeviceBase): id: int diagram_id: int model_config = {"from_attributes": True} class WireBase(BaseModel): label: str = "" from_device_id: Optional[int] = None from_pin: str = "" to_device_id: Optional[int] = None to_pin: str = "" color_primary: str = "#FF0000" color_stripe: Optional[str] = None gauge: str = "18 AWG" length: Optional[float] = None length_unit: str = "in" notes: str = "" waypoints: List[Dict[str, Any]] = [] twisted_pair: bool = False twist_pitch: float = 16.0 shielded: bool = False bundle_id: Optional[int] = None show_size_label: bool = False @field_validator("waypoints", mode="before") @classmethod def _coerce_waypoints(cls, v): return v if v is not None else [] class WireCreate(WireBase): diagram_id: int class WireUpdate(BaseModel): label: Optional[str] = None from_device_id: Optional[int] = None from_pin: Optional[str] = None to_device_id: Optional[int] = None to_pin: Optional[str] = None color_primary: Optional[str] = None color_stripe: Optional[str] = None gauge: Optional[str] = None length: Optional[float] = None length_unit: Optional[str] = None notes: Optional[str] = None waypoints: Optional[List[Dict[str, Any]]] = None twisted_pair: Optional[bool] = None twist_pitch: Optional[float] = None shielded: Optional[bool] = None bundle_id: Optional[int] = None show_size_label: Optional[bool] = None class Wire(WireBase): id: int diagram_id: int model_config = {"from_attributes": True} # ── WireBundle ──────────────────────────────────────────────────────────────── class WireBundleBase(BaseModel): label: str = "" jacket_color: str = "#2a2a2a" class WireBundleCreate(WireBundleBase): diagram_id: int class WireBundleUpdate(BaseModel): label: Optional[str] = None jacket_color: Optional[str] = None class WireBundleOut(WireBundleBase): id: int diagram_id: int model_config = {"from_attributes": True} # ── Diagram ─────────────────────────────────────────────────────────────────── class DiagramBase(BaseModel): name: str = "Untitled Diagram" description: str = "" class DiagramCreate(DiagramBase): pass class DiagramUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None class DiagramSummary(DiagramBase): id: int created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class DiagramFull(DiagramSummary): devices: List[Device] = [] wires: List[Wire] = [] wire_bundles: List[WireBundleOut] = [] # ── DiagramView ─────────────────────────────────────────────────────────────── class DiagramViewBase(BaseModel): name: str route_mode: str = "direct" class DiagramViewCreate(DiagramViewBase): diagram_id: int class DiagramViewUpdate(BaseModel): name: Optional[str] = None route_mode: Optional[str] = None class DiagramViewOut(DiagramViewBase): id: int diagram_id: int created_at: datetime model_config = {"from_attributes": True} class ViewDevicePositionUpdate(BaseModel): x: float y: float class ViewWireWaypointsUpdate(BaseModel): waypoints: List[Dict[str, Any]] = [] class ViewLayout(BaseModel): device_positions: Dict[int, Dict[str, float]] = {} wire_waypoints: Dict[int, List[Dict[str, Any]]] = {} # ── CustomConnector ─────────────────────────────────────────────────────────── class CustomConnectorBase(BaseModel): name: str manufacturer: str = "" part_number: str = "" category: str = "Custom" description: str = "" pin_count: int pin_labels: List[str] = [] class CustomConnectorCreate(CustomConnectorBase): pass class CustomConnectorUpdate(BaseModel): name: Optional[str] = None manufacturer: Optional[str] = None part_number: Optional[str] = None category: Optional[str] = None description: Optional[str] = None pin_count: Optional[int] = None pin_labels: Optional[List[str]] = None class CustomConnector(CustomConnectorBase): id: int created_at: datetime updated_at: datetime model_config = {"from_attributes": True}