111 lines
3.5 KiB
Python
111 lines
3.5 KiB
Python
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",
|
|
]
|