from __future__ import annotations import math from datetime import datetime from typing import Annotated, Literal from pydantic import BaseModel, Field, field_validator, model_validator WidgetId = Annotated[str, Field(min_length=1, max_length=160)] ConfigurationValue = str | int | float | bool | None class DashboardWidgetPlacementPayload(BaseModel): instance_id: str = Field(min_length=1, max_length=120) widget_id: WidgetId size: Literal["small", "medium", "wide", "full"] = "medium" column_start: int | None = Field(default=None, ge=1, le=4) configuration: dict[str, ConfigurationValue] = Field( default_factory=dict, max_length=40, ) @model_validator(mode="after") def validate_column_span(self) -> "DashboardWidgetPlacementPayload": if self.column_start is None: return self span = { "small": 1, "medium": 2, "wide": 3, "full": 4, }[self.size] if self.column_start + span - 1 > 4: raise ValueError( f"A {self.size} widget cannot start in column " f"{self.column_start} of a four-column Dashboard." ) return self @field_validator("configuration") @classmethod def validate_configuration( cls, value: dict[str, ConfigurationValue], ) -> dict[str, ConfigurationValue]: approximate_size = 0 for key, item in value.items(): if not key or len(key) > 120: raise ValueError( "Widget configuration keys must contain 1 to 120 characters." ) approximate_size += len(key) if isinstance(item, str): if len(item) > 4_000: raise ValueError( "Widget configuration text values may contain at most " "4,000 characters." ) approximate_size += len(item) else: approximate_size += len(str(item)) if isinstance(item, float) and not math.isfinite(item): raise ValueError( "Widget configuration numbers must be finite." ) if approximate_size > 32_000: raise ValueError( "A widget configuration may contain at most 32,000 characters." ) return value class DashboardLayoutUpdateRequest(BaseModel): expected_revision: int | None = Field(default=None, ge=0) layout_version: Literal[1] = 1 placements: list[DashboardWidgetPlacementPayload] = Field( default_factory=list, max_length=100, ) known_widget_ids: list[WidgetId] = Field( default_factory=list, max_length=500, ) @model_validator(mode="after") def validate_unique_ids(self) -> "DashboardLayoutUpdateRequest": instance_ids = [item.instance_id for item in self.placements] if len(instance_ids) != len(set(instance_ids)): raise ValueError("Dashboard widget instance ids must be unique.") return self class DashboardLayoutResponse(BaseModel): exists: bool view_id: str | None = None layout_version: int = 1 revision: int = 0 placements: list[DashboardWidgetPlacementPayload] = Field( default_factory=list ) known_widget_ids: list[str] = Field(default_factory=list) updated_at: datetime | None = None __all__ = [ "DashboardLayoutResponse", "DashboardLayoutUpdateRequest", "DashboardWidgetPlacementPayload", ]