from typing import Annotated from typing import Literal from typing import Union from pydantic import BaseModel from pydantic import Field from pydantic import ValidationError from pydantic import root_validator from pydantic import validator from pydantic.color import Color # from pydantic_yaml import YamlModel from typing_extensions import Self class HomeAssistantConfig(BaseModel): token: str hostname: str port: int | None secure: bool = True @validator("port") def check_port(cls, v): if v <= 0 or v >= 65536: raise ValidationError("port must be greater than 0 and less than 65536") return v class StreamDeckConfig(BaseModel): index: int | None serial_number: str | None brightness: int | float screensaver: int screen: str @root_validator def check_index_serial_number(cls: type[Self], values: dict): index = values.get("index") serial_number = values.get("serial_number") if index is None and serial_number is None: raise ValidationError("must set either index or serial_number") if index is not None and serial_number is not None: raise ValidationError("cannot set both index and serial_number") return values @validator("brightness") def check_brightness(cls: type[Self], v: int | float) -> int | float: if isinstance(v, int): if v < 0 or v > 100: raise ValidationError("brightness") return v if isinstance(v, float): if v < 0.0 or v > 1.0: raise ValidationError("brightness") return v raise ValidationError("brightness must be an int or a float") class ColorConfig(BaseModel): foreground: Color background: Color # class HomeAssistantIconImageStateConfig(BaseModel): # state: str | None # icon: str # up: ColorConfig # down: ColorConfig class ImageConfig(BaseModel): icon: str up: ColorConfig down: ColorConfig class HomeAssistantIconImageConfig(BaseModel): type: Literal["homeassistant-icon"] entity_id: str states: dict[str | None, ImageConfig] @root_validator def check_states(cls: type[Self], values: dict) -> dict: states = values.get("states") if None not in states: raise ValidationError("must define default state by using '~' in yaml") return values class StaticIconImageConfig(ImageConfig): type: Literal["static-icon"] # icon: str # up: ColorConfig # down: ColorConfig class ImageTextConfig(ImageConfig): icon: str text: str class StaticTextIconImageConfig(ImageTextConfig): type: Literal["static-icon-text"] class HomeAssistantServiceActionConfig(BaseModel): type: Literal["homeassistant-service"] domain: str service: str data: dict class PulseAudioActionConfig(BaseModel): type: Literal["pulseaudio-action"] application_name: str action: Literal["volume-up", "volume-down", "toggle-mute"] class ActionConfig(BaseModel): __root__: Annotated[ Union[HomeAssistantServiceActionConfig, PulseAudioActionConfig], Field(discriminator="type"), ] ImageTypes = Annotated[ Union[ StaticIconImageConfig, StaticTextIconImageConfig, HomeAssistantIconImageConfig ], Field(discriminator="type"), ] class TileConfig(BaseModel): position: tuple[int, int] image: ImageTypes actions: list[ActionConfig] class Config(BaseModel): homeassistant: HomeAssistantConfig streamdecks: list[StreamDeckConfig] screens: dict[str, list[TileConfig]]