From 5c930d77422e2b88ac6844d7e0f5e476e709f9a5 Mon Sep 17 00:00:00 2001 From: rooba Date: Mon, 28 Nov 2022 00:13:44 -0800 Subject: [PATCH] all but 4 structures done, yet to test updating, in theory it works --- .gitignore | 5 +- phlyght/api.py | 92 ++++++++- phlyght/models.py | 464 ++++++++++++++++++++++++++++++++++++++++------ test.py | 11 +- 4 files changed, 506 insertions(+), 66 deletions(-) diff --git a/.gitignore b/.gitignore index 6c65cbf..562972f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ -**/app.py -**/phlyght.* -**/phlyght.* **/poetry.lock **/__pycache__ +**/testing + !test.py diff --git a/phlyght/api.py b/phlyght/api.py index 55eef81..ec89523 100644 --- a/phlyght/api.py +++ b/phlyght/api.py @@ -10,7 +10,7 @@ from yarl import URL as UR # from rich import print -from .models import Lights +from . import models STR_FMT_RE = compile(r"""(?=(\{([^:]+)(?::([^}]+))?\}))\1""") @@ -208,286 +208,361 @@ class SubRouter(metaclass=RouterMeta): class HughApi(SubRouter): BASE_URI = "/clip/v2" - @ret_cls(Lights.Light) + @ret_cls(models.Light) @route("GET", "/resource/light") async def get_lights(self, friendly_name: Optional[str] = None): ... - @ret_cls(Lights.Light) + @ret_cls(models.Light) @route("GET", "/resource/light/{light_id}") async def get_light(self, light_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/light/{light_id}") async def set_light( self, light_id: int, on: Optional[dict[Literal["on"], bool]] = None, dimming: Optional[dict[str, Any]] = None, - **kwargs, + dimming_delta: Optional[dict] = None, + color_temperature: Optional[int] = None, + color_temperature_delta: Optional[dict] = None, + color: Optional[dict] = None, + dynamics: Optional[dict] = None, + alert: Optional[dict] = None, + gradient: Optional[dict] = None, + effects: Optional[dict] = None, + timed_effects: Optional[dict] = None, ): ... + @ret_cls(models.Scene) @route("GET", "/resource/scene") async def get_scenes(self): ... + @ret_cls(models.Identifier) @route("POST", "/resource/scene") async def create_scene(self, **kwargs): ... + @ret_cls(models.Scene) @route("GET", "/resource/scene/{scene_id}") async def get_scene(self, scene_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/scene/{scene_id}") async def set_scene(self, scene_id: str, **kwargs): ... + @ret_cls(models.Identifier) @route("DELETE", "/resource/scene/{scene_id}") async def delete_scene(self, scene_id: str): ... + @ret_cls(models.Room) @route("GET", "/resource/room") async def get_rooms(self): ... + @ret_cls(models.Identifier) @route("POST", "/resource/room") async def create_room(self, **kwargs): ... + @ret_cls(models.Room) @route("GET", "/resource/room/{room_id}") async def get_room(self, room_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/room/{room_id}") async def set_room(self, room_id: str, **kwargs): ... + @ret_cls(models.Identifier) @route("DELETE", "/resource/room/{room_id}") async def delete_room(self, room_id: str): ... + @ret_cls(models.Zone) @route("GET", "/resource/zone") async def get_zones(self): ... + @ret_cls(models.Identifier) @route("POST", "/resource/zone") async def create_zone(self, **kwargs): ... + @ret_cls(models.Zone) @route("GET", "/resource/zone/{zone_id}") async def get_zone(self, zone_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/zone/{zone_id}") async def set_zone(self, zone_id: str, **kwargs): ... + @ret_cls(models.Identifier) @route("DELETE", "/resource/zone/{zone_id}") async def delete_zone(self, zone_id: str): ... + @ret_cls(models.BridgeHome) @route("GET", "/resource/bridge_home") async def get_bridge_homes(self): ... + @ret_cls(models.BridgeHome) @route("GET", "/resource/bridge_home/{bridge_home_id}") async def get_bridge_home(self, bridge_home_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/bridge_home/{bridge_home_id}") async def set_bridge_home(self, bridge_home_id: str, **kwargs): ... + @ret_cls(models.GroupedLight) @route("GET", "/resource/grouped_light") async def get_grouped_lights(self): ... + @ret_cls(models.GroupedLight) @route("GET", "/resource/grouped_light/{grouped_light_id}") async def get_grouped_light(self, grouped_light_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/grouped_light/{grouped_light_id}") async def set_grouped_light(self, grouped_light_id: str, **kwargs): ... + @ret_cls(models.Device) @route("GET", "/resource/device") async def get_devices(self): ... + @ret_cls(models.Device) @route("GET", "/resource/device/{device_id}") async def get_device(self, device_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/device/{device_id}") async def set_device(self, device_id: str, **kwargs): ... + @ret_cls(models.Bridge) @route("GET", "/resource/bridges") async def get_bridges(self): ... + @ret_cls(models.Bridge) @route("GET", "/resource/bridges/{bridge_id}") async def get_bridge(self, bridge_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/bridges/{bridge_id}") async def set_bridge(self, bridge_id: str, **kwargs): ... + @ret_cls(models.DevicePower) @route("GET", "/resource/device_power") async def get_device_powers(self): ... + @ret_cls(models.DevicePower) @route("GET", "/resource/device_power/{device_power_id}") async def get_device_power(self, device_power_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/device_power/{device_power_id}") async def set_device_power(self, device_power_id: str, **kwargs): ... + @ret_cls(models.ZigbeeConnectivity) @route("GET", "/resource/zigbee_connectivity") async def get_zigbee_connectivities(self): ... + @ret_cls(models.ZigbeeConnectivity) @route("GET", "/resource/zigbee_connectivity/{zigbee_connectivity_id}") async def get_zigbee_connectivity(self, zigbee_connectivity_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/zigbee_connectivity/{zigbee_connectivity_id}") async def set_zigbee_connectivity(self, zigbee_connectivity_id: str, **kwargs): ... + @ret_cls(models.ZGPConnectivity) @route("GET", "/resource/zgb_connectivity") async def get_zgb_connectivities(self): ... + @ret_cls(models.ZGPConnectivity) @route("GET", "/resource/zgb_connectivity/{zgb_connectivity_id}") async def get_zgb_connectivity(self, zgb_connectivity_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/zgb_connectivity/{zgb_connectivity_id}") async def set_zgb_connectivity(self, zgb_connectivity_id: str, **kwargs): ... + @ret_cls(models.Motion) @route("GET", "/resource/motion") async def get_motions(self): ... + @ret_cls(models.Motion) @route("GET", "/resource/motion/{motion_id}") async def get_motion(self, motion_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/motion/{motion_id}") async def set_motion(self, motion_id: str, **kwargs): ... + @ret_cls(models.Temperature) @route("GET", "/resource/temperature") async def get_temperatures(self): ... + @ret_cls(models.Temperature) @route("GET", "/resource/temperature/{temperature_id}") async def get_temperature(self, temperature_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/temperature/{temperature_id}") async def set_temperature(self, temperature_id: str, **kwargs): ... + @ret_cls(models.LightLevel) @route("GET", "/resource/light_level") async def get_light_levels(self): ... + @ret_cls(models.LightLevel) @route("GET", "/resource/light_level/{light_level_id}") async def get_light_level(self, light_level_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/light_level/{light_level_id}") async def set_light_level(self, light_level_id: str, **kwargs): ... + @ret_cls(models.Button) @route("GET", "/resource/button") async def get_buttons(self): ... + @ret_cls(models.Button) @route("GET", "/resource/button/{button_id}") async def get_button(self, button_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/button/{button_id}") async def set_button(self, button_id: str, **kwargs): ... + @ret_cls(models.BehaviorScript) @route("GET", "/resource/behavior_script") async def get_behavior_scripts(self): ... + @ret_cls(models.BehaviorScript) @route("GET", "/resource/behavior_script/{behavior_script_id}") async def get_behavior_script(self, behavior_script_id: str): ... + @ret_cls(models.BehaviorInstance) @route("GET", "/resource/behavior_instance") async def get_behavior_instances(self): ... + @ret_cls(models.Identifier) @route("POST", "/resource/behavior_instance") async def create_behavior_instance(self, **kwargs): ... + @ret_cls(models.BehaviorInstance) @route("GET", "/resource/behavior_instance/{behavior_instance_id}") async def get_behavior_instance(self, behavior_instance_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/behavior_instance/{behavior_instance_id}") async def set_behavior_instance(self, behavior_instance_id: str, **kwargs): ... + @ret_cls(models.Identifier) @route("DELETE", "/resource/behavior_instance/{behavior_instance_id}") async def delete_behavior_instance(self, behavior_instance_id: str): ... + @ret_cls(models.GeofenceClient) @route("GET", "/resource/geofence_client") async def get_geofence_clients(self): ... + @ret_cls(models.Identifier) @route("POST", "/resource/geofence_client") async def create_geofence_client(self, **kwargs): ... + @ret_cls(models.GeofenceClient) @route("GET", "/resource/geofence_client/{geofence_client_id}") async def get_geofence_client(self, geofence_client_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/geofence_client/{geofence_client_id}") async def set_geofence_client(self, geofence_client_id: str, **kwargs): ... + @ret_cls(models.Identifier) @route("DELETE", "/resource/geofence_client/{geofence_client_id}") async def delete_geofence_client(self, geofence_client_id: str): ... + @ret_cls(models.Geolocation) @route("GET", "/resource/geolocation") async def get_geolocations(self): ... + @ret_cls(models.Geolocation) @route("GET", "/resource/geolocation/{geolocation_id}") async def get_geolocation(self, geolocation_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/geolocation/{geolocation_id}") async def set_geolocation(self, geolocation_id: str, **kwargs): ... + @ret_cls(models.EntertainmentConfiguration) @route("GET", "/resource/entertainment_configuration") async def get_entertainment_configurations(self): ... + @ret_cls(models.Identifier) @route("POST", "/resource/entertainment_configuration") async def create_entertainment_configuration(self, **kwargs): ... + @ret_cls(models.EntertainmentConfiguration) @route( "GET", "/resource/entertainment_configuration/{entertainment_configuration_id}" ) @@ -496,6 +571,7 @@ class HughApi(SubRouter): ): ... + @ret_cls(models.Identifier) @route( "PUT", "/resource/entertainment_configuration/{entertainment_configuration_id}" ) @@ -504,6 +580,7 @@ class HughApi(SubRouter): ): ... + @ret_cls(models.Identifier) @route( "DELETE", "/resource/entertainment_configuration/{entertainment_configuration_id}", @@ -513,30 +590,37 @@ class HughApi(SubRouter): ): ... + @ret_cls(models.Entertainment) @route("GET", "/resource/entertainment") async def get_entertainments(self): ... + @ret_cls(models.Entertainment) @route("GET", "/resource/entertainment/{entertainment_id}") async def get_entertainment(self, entertainment_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/entertainment/{entertainment_id}") async def set_entertainment(self, entertainment_id: str, **kwargs): ... + @ret_cls(models.Homekit) @route("GET", "/resource/homekit") async def get_homekits(self): ... + @ret_cls(models.Homekit) @route("GET", "/resource/homekit/{homekit_id}") async def get_homekit(self, homekit_id: str): ... + @ret_cls(models.Identifier) @route("PUT", "/resource/homekit/{homekit_id}") async def set_homekit(self, homekit_id: str, **kwargs): ... + @ret_cls(models.Resource) @route("GET", "/resource") async def get_resources(self): ... diff --git a/phlyght/models.py b/phlyght/models.py index 4454f9d..a702564 100644 --- a/phlyght/models.py +++ b/phlyght/models.py @@ -1,45 +1,125 @@ -from typing import Literal, Optional +__all__ = ("Room", "Light", "Scene") + +from typing import Any, Literal, Optional, Generic, TypeVar from uuid import UUID -from pydantic import BaseModel +from pydantic import BaseModel, Field +from dataclasses import dataclass +from enum import Enum, auto + +_T = TypeVar("_T") -class Lights: - class MetaData(BaseModel): - archetype: str - name: str +class Archetype(Enum): + @staticmethod + def _generate_next_value_(name, start, count, last_values): + return name.lower() - class Owner(BaseModel): - rid: UUID - rtype: str + BRIDGE_V2 = auto() + UNKNOWN_ARCHETYPE = auto() + CLASSIC_BULB = auto() + SULTAN_BULB = auto() + FLOOD_BULB = auto() + SPOT_BULB = auto() + CANDLE_BULB = auto() + LUSTER_BULB = auto() + PENDANT_ROUND = auto() + PENDANT_LONG = auto() + CEILING_ROUND = auto() + CEILING_SQUARE = auto() + FLOOR_SHADE = auto() + FLOOR_LANTERN = auto() + TABLE_SHADE = auto() + RECESSED_CEILING = auto() + RECESSED_FLOOR = auto() + SINGLE_SPOT = auto() + DOUBLE_SPOT = auto() + TABLE_WASH = auto() + WALL_LANTERN = auto() + WALL_SHADE = auto() + FLEXIBLE_LAMP = auto() + GROUND_SPOT = auto() + WALL_SPOT = auto() + PLUG = auto() + HUE_GO = auto() + HUE_LIGHTSTRIP = auto() + HUE_IRIS = auto() + HUE_BLOOM = auto() + BOLLARD = auto() + WALL_WASHER = auto() + HUE_PLAY = auto() + VINTAGE_BULB = auto() + CHRISTMAS_TREE = auto() + HUE_CENTRIS = auto() + HUE_LIGHTSTRIP_TV = auto() + HUE_TUBE = auto() + HUE_SIGNE = auto() - class On(BaseModel): - on: bool - class Dimming(BaseModel): - brightness: float - min_dim_level: float +class _id_v1(str): + def __get__(self, instance, owner): + return Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$)") - class MirekSchema(BaseModel): - mirek_minimum: float - mirek_maximum: float +@dataclass +class Dimming: + brightness: float + min_dim_level: Optional[float] = Field(0, repr=False) + + +@dataclass +class XY: + x: float + y: float + + +@dataclass +class On: + on: bool = Field(..., alias="on") + + +@dataclass +class ColorPoint: + xy: XY + + +@dataclass(frozen=True) +class Identifier: + rid: str + rtype: str + + +@dataclass(frozen=True) +class Metadata: + name: str + archetype: Optional[Archetype] = Archetype.UNKNOWN_ARCHETYPE + image: Optional[Identifier] = Field(None, repr=False) + + +class HueGroupedMeta(type): + def update(cls): + for v in cls.__dict__.values(): + if hasattr(v, "update_forward_refs"): + v.update_forward_refs() + + +class HueGrouped(metaclass=HueGroupedMeta): + ... + + +class _Lights(HueGrouped): class ColorTemperature(BaseModel): mirek: Optional[int] mirek_valid: bool - mirek_schema: "Lights.MirekSchema" - - class XY(BaseModel): - x: float - y: float + mirek_schema: dict[str, float] class Gamut(BaseModel): - red: "Lights.XY" - green: "Lights.XY" - blue: "Lights.XY" + red: XY + green: XY + blue: XY class Color(BaseModel): - xy: "Lights.XY" - gamut: "Lights.Gamut" + xy: XY + gamut: "_Lights.Gamut" gamut_type: Literal["A"] | Literal["B"] | Literal["C"] class Dynamics(BaseModel): @@ -48,49 +128,317 @@ class Lights: speed: float speed_valid: bool - class Alert(BaseModel): - action_values: list[str] - - class GradientColor(BaseModel): - xy: "Lights.XY" - class Gradient(BaseModel): - points: list["Lights.GradientColor"] + points: list[ColorPoint] points_capable: int class Effects(BaseModel): - effect: Optional[list[Literal["fire", "candle", "no_effect"]]] - status_values: list[Literal["fire", "candle", "no_effect"]] - status: Literal["fire", "candle", "no_effect"] - effect_values: list[Literal["fire", "candle", "no_effect"]] + effect: Optional[list[str]] = Field(repr=False) + status_values: list[str] = Field(repr=False) + status: str + effect_values: list[str] = Field(repr=False) class TimedEffects(BaseModel): - effect: Literal["sunrise", "no_effect"] + effect: str duration: int - status_values: list[Literal["sunrise", "no_effect"]] - status: Literal["sunrise", "no_effect"] - effect_values: list[Literal["sunrise", "no_effect"]] + status_values: list[str] = Field(repr=False) + status: str + effect_values: list[str] = Field(repr=False) - class Light(BaseModel): + class Light(Generic[_T], BaseModel): id: UUID - id_v1: str - owner: "Lights.Owner" - metadata: "Lights.MetaData" - on: "Lights.On" - dimming: "Lights.Dimming" + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + owner: Identifier + metadata: Metadata + on: On = Field(repr=False) + dimming: Dimming dimming_delta: dict - color_temperature: Optional["Lights.ColorTemperature"] + color_temperature: Optional["_Lights.ColorTemperature"] color_temperature_delta: Optional[dict] - color: Optional["Lights.Color"] - gradient: Optional["Lights.Gradient"] - dynamics: "Lights.Dynamics" - alert: "Lights.Alert" + color: Optional["_Lights.Color"] + gradient: Optional["_Lights.Gradient"] + dynamics: "_Lights.Dynamics" + alert: dict[str, list[str]] signaling: dict mode: str - effects: "Lights.Effects" - type: str + effects: "_Lights.Effects" + type: Literal["light"] -for k in Lights.__dict__.values(): - if hasattr(k, "update_forward_refs"): - k.update_forward_refs() +_Lights.update() + + +class _Scenes(HueGrouped): + class Action(BaseModel): + on: Optional[On] + dimming: Optional[Dimming] + color: Optional[ColorPoint] + color_temperature: Optional[dict[str, float]] + gradient: Optional[dict[str, list[ColorPoint]]] + effects: Optional[dict[str, str]] + dynamics: Optional[dict[str, float]] + + class Actions(BaseModel): + target: Identifier + action: "_Scenes.Action" = Field(repr=False) + dimming: Optional[Dimming] + color: Optional[ColorPoint] + + class PaletteColor(BaseModel): + color: ColorPoint + dimming: Dimming + + class PaletteTemperature(BaseModel): + color_temperature: dict[str, float] + dimming: Dimming + + class Palette(BaseModel): + color: list["_Scenes.PaletteColor"] + dimming: Optional[list[Dimming]] + color_temperature: list["_Scenes.PaletteTemperature"] + + class Scene(BaseModel): + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + metadata: Metadata + group: Identifier + actions: list["_Scenes.Actions"] + palette: "_Scenes.Palette" + speed: float + auto_dynamic: bool + type: Literal["scene"] + + +_Scenes.update() + + +class Room(BaseModel): + type: Literal["room"] + id: UUID + id_v1: _id_v1 + services: list[Identifier] + metadata: Metadata + children: list[Identifier] + + +class Zone(BaseModel): + type: Literal["zone"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + services: list[Identifier] + metadata: Metadata + children: list[Identifier] + + +class BridgeHome(BaseModel): + type: Literal["bridge_home"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + services: list[Identifier] + children: list[Identifier] + + +class GroupedLight(BaseModel): + type: Literal["grouped_light"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + on: On = Field(repr=False) + alert: list[str] + + +class ProductData(BaseModel): + model_id: str + manufacturer_name: str + product_name: str + product_archetype: Archetype + certified: bool + software_version: Optional[str] + hardware_platform_type: Optional[str] + + +class Device(BaseModel): + type: Literal["device"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + services: list[Identifier] + metadata: Metadata + product_data: ProductData + + +class Bridge(BaseModel): + type: Literal["bridge"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + bridge_id: str + time_zone: dict[str, str] + + +class PowerState(BaseModel): + battery_state: Literal["normal", "low", "critical"] + battery_level: float = Field(lt=100.0, gt=0.0) + + +class DevicePower(BaseModel): + type: Literal["device_power"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + owner: Identifier + power_state: PowerState + + +class ZigbeeConnectivity(BaseModel): + type: Literal["zigbee_connectivity"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + owner: Identifier + status: Literal[ + "connected", "disconnected", "connectivity_issue", "unidirectional_incoming" + ] + mac_address: str + + +class ZGPConnectivity(BaseModel): + type: Literal["zgp_connectivity"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + owner: Identifier + status: Literal[ + "connected", "disconnected", "connectivity_issue", "unidirectional_incoming" + ] + source_id: str + + +class Motion(BaseModel): + type: Literal["motion"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + owner: Identifier + enabled: bool + motion: dict[str, bool] + + +class _Temp(BaseModel): + temperature: float = Field(lt=100.0, gt=-100.0) + temperature_valid: bool + + +class Temperature(BaseModel): + type: Literal["temperature"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + owner: Identifier + enabled: bool + temperature: _Temp + + +class _Light(BaseModel): + light_level: int + light_level_valid: bool + + +class LightLevel(BaseModel): + type: Literal["light_level"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + owner: Identifier + enabled: bool + light: _Light + + +class Button(BaseModel): + type: Literal["button"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + owner: Identifier + metadata: dict[Literal["control_id"], int] + button: dict[ + Literal["last_event"], + Literal[ + "initial_press", + "repeat", + "short_release", + "long_release", + "double_short_release", + ], + ] + + +class BehaviorScript(BaseModel): + type: Literal["behavior_script"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + description: str + configuration_schema: dict[str, Any] + trigger_schema: dict[str, Any] + state_schema: dict[str, Any] + version: str + metadata: dict[str, str] + + +class Dependee(BaseModel): + type: str + target: Identifier + level: str + + +class BehaviorInstance(BaseModel): + type: Literal["behavior_instance"] + id: UUID + id_v1: str = Field("", regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + script_id: str + enabled: bool + state: Optional[dict[str, Any]] + configuration: dict[str, Any] + dependees: list[Dependee] + status: Literal["initializing", "running", "disabled", "errored"] + last_error: str + metadata: dict[Literal["name"], str] + migrated_from: Optional[str] = None + + +class GeofenceClient(BaseModel): + type: Literal["geofence_client"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + name: str + + +class Geolocation(BaseModel): + type: Literal["geolocation"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + is_configured: bool = False + + +class StreamProxy(BaseModel): + mode: Literal["auto", "manual"] + node: Identifier + + +class EntertainmentConfiguration(BaseModel): + type: Literal["entertainment_configuration"] + id: UUID + id_v1: str = Field(..., regex=r"^(\/[a-z]{4,32}\/[0-9a-zA-Z-]{1,32})?$") + metadata: dict[Literal["name"], str] + name: Optional[str] = "" + configuration_type: Literal["screen", "monitor", "music", "3dspace", "other"] + status: Literal["active", "inactive"] + active_streamer: Identifier + stream_proxy: StreamProxy + ... # TODO: finish the last 4 objects + + +class Entertainment(BaseModel): + ... + + +class Homekit(BaseModel): + ... + + +class Resource(BaseModel): + ... + + +Light = _Lights.Light +Scene = _Scenes.Scene diff --git a/test.py b/test.py index b7b07c1..fd7e73b 100644 --- a/test.py +++ b/test.py @@ -5,11 +5,20 @@ from phlyght.api import Router async def main(): - router = Router("TzPrxDf9hyWZoR5jvUaGDZn4Hlxp2XF67ue4ynSI") + router = Router("Your user key with the hue bridge") + lights = await router.get_lights() for light in lights: detailed_light = await router.get_light(light_id=str(light.id)) print(detailed_light) + scenes = await router.get_scenes() + for scene in scenes: + print(scene) + + devices = await router.get_devices() + for device in devices: + print(device) + run(main())