From 724eec830912a848de31e5dcfd741a0b30266b71 Mon Sep 17 00:00:00 2001 From: Artem Sorokin Date: Tue, 3 Oct 2023 14:30:21 +0300 Subject: [PATCH] Use base class for API response model --- custom_components/yandex_smart_home/device.py | 6 ++-- .../yandex_smart_home/handlers.py | 2 +- custom_components/yandex_smart_home/http.py | 2 +- .../yandex_smart_home/schema/base.py | 21 ++++++++++++++ .../yandex_smart_home/schema/capability.py | 21 +++++++------- .../schema/capability_color.py | 16 +++++----- .../schema/capability_mode.py | 6 ++-- .../schema/capability_onoff.py | 6 ++-- .../schema/capability_range.py | 10 ++++--- .../schema/capability_toggle.py | 6 ++-- .../schema/capability_video.py | 10 +++---- .../yandex_smart_home/schema/device.py | 29 +++++++++---------- .../yandex_smart_home/schema/property.py | 11 ++++--- .../schema/property_event.py | 5 ++-- .../schema/property_float.py | 4 +-- .../yandex_smart_home/schema/response.py | 6 ++-- tests/test_capability_color.py | 18 +++++------- tests/test_capability_range.py | 24 +++++++-------- tests/test_device.py | 12 ++++---- tests/test_handlers.py | 18 ++++++------ 20 files changed, 127 insertions(+), 106 deletions(-) create mode 100644 custom_components/yandex_smart_home/schema/base.py diff --git a/custom_components/yandex_smart_home/device.py b/custom_components/yandex_smart_home/device.py index 346aee82..11a5a83b 100644 --- a/custom_components/yandex_smart_home/device.py +++ b/custom_components/yandex_smart_home/device.py @@ -295,7 +295,7 @@ async def execute( ) if error_code_template := self._error_code_template: - if error_code := error_code_template.async_render(capability=action.dict(), parse_result=False): + if error_code := error_code_template.async_render(capability=action.as_dict(), parse_result=False): if error_code not in const.ERROR_CODES: raise SmartHomeError(const.ERR_INTERNAL_ERROR, f"Invalid error code for {self.id}: {error_code!r}") @@ -390,7 +390,7 @@ def __init__(self, device: Device, event_entity_id: str, initial_report: bool = for device_capability in [c for c in device.get_capabilities() if c.reportable]: if (capability_state := device_capability.get_instance_state()) is not None: - self.capabilities.append(capability_state.dict()) + self.capabilities.append(capability_state.as_dict()) for device_property in [c for c in device.get_properties() if c.reportable]: if device_property.value_entity_id != event_entity_id: @@ -404,7 +404,7 @@ def __init__(self, device: Device, event_entity_id: str, initial_report: bool = property_state = device_property.get_instance_state() if property_state is not None: - self.properties.append(property_state.dict()) + self.properties.append(property_state.as_dict()) @property def should_report(self) -> bool: diff --git a/custom_components/yandex_smart_home/handlers.py b/custom_components/yandex_smart_home/handlers.py index 66dab226..ba85108e 100644 --- a/custom_components/yandex_smart_home/handlers.py +++ b/custom_components/yandex_smart_home/handlers.py @@ -150,7 +150,7 @@ async def async_devices_action(hass: HomeAssistant, data: RequestData, payload: hass.bus.async_fire( EVENT_DEVICE_ACTION, - {ATTR_ENTITY_ID: device_id, "capability": action.dict()}, + {ATTR_ENTITY_ID: device_id, "capability": action.as_dict()}, context=data.context, ) diff --git a/custom_components/yandex_smart_home/http.py b/custom_components/yandex_smart_home/http.py index 3960acd2..61bc7db4 100644 --- a/custom_components/yandex_smart_home/http.py +++ b/custom_components/yandex_smart_home/http.py @@ -105,7 +105,7 @@ async def _async_handle_request(self, hass: HomeAssistant, request: Request, dat result = await handlers.async_handle_request( hass, data, action=request.path.replace(self.url, "", 1), payload=await request.text() ) - response = json_response(text=result.json(exclude_none=True, ensure_ascii=False)) + response = json_response(text=result.as_json()) _LOGGER.debug(f"Response: {response.text}") return response diff --git a/custom_components/yandex_smart_home/schema/base.py b/custom_components/yandex_smart_home/schema/base.py new file mode 100644 index 00000000..d7bbaefa --- /dev/null +++ b/custom_components/yandex_smart_home/schema/base.py @@ -0,0 +1,21 @@ +"""Base class for API response schemas.""" +from typing import Any + +from pydantic import BaseModel +from pydantic.generics import GenericModel + + +class APIModel(BaseModel): + """Base API response model.""" + + def as_json(self) -> str: + """Generate a JSON representation of the model.""" + return super().json(exclude_none=True, ensure_ascii=False) + + def as_dict(self) -> dict[str, Any]: + """Generate a dictionary representation of the model.""" + return super().dict(exclude_none=True) + + +class GenericAPIModel(GenericModel, APIModel): + """Base generic API response model.""" diff --git a/custom_components/yandex_smart_home/schema/capability.py b/custom_components/yandex_smart_home/schema/capability.py index 05f603d9..f59f7ef0 100644 --- a/custom_components/yandex_smart_home/schema/capability.py +++ b/custom_components/yandex_smart_home/schema/capability.py @@ -2,8 +2,9 @@ from enum import StrEnum from typing import Annotated, Any, Literal, TypeVar, Union -from pydantic import BaseModel, Field +from pydantic import Field +from .base import APIModel from .capability_color import ( ColorSettingCapabilityInstance, ColorSettingCapabilityInstanceActionState, @@ -56,7 +57,7 @@ class CapabilityType(StrEnum): """All capability instances.""" -class CapabilityDescription(BaseModel): +class CapabilityDescription(APIModel): """Description of a capability for a device list request.""" type: CapabilityType @@ -65,56 +66,56 @@ class CapabilityDescription(BaseModel): parameters: CapabilityParameters | None -class CapabilityInstanceStateValue(BaseModel): +class CapabilityInstanceStateValue(APIModel): """Capability instance value.""" instance: CapabilityInstance value: Any -class CapabilityInstanceState(BaseModel): +class CapabilityInstanceState(APIModel): """Capability state for state query and callback requests.""" type: CapabilityType state: CapabilityInstanceStateValue -class OnOffCapabilityInstanceAction(BaseModel): +class OnOffCapabilityInstanceAction(APIModel): """New capability state for a state change request of on_off capability.""" type: Literal[CapabilityType.ON_OFF] = CapabilityType.ON_OFF state: OnOffCapabilityInstanceActionState -class ColorSettingCapabilityInstanceAction(BaseModel): +class ColorSettingCapabilityInstanceAction(APIModel): """New capability state for a state change request of color_setting capability.""" type: Literal[CapabilityType.COLOR_SETTING] = CapabilityType.COLOR_SETTING state: ColorSettingCapabilityInstanceActionState -class ModeCapabilityInstanceAction(BaseModel): +class ModeCapabilityInstanceAction(APIModel): """New capability state for a state change request of mode capability.""" type: Literal[CapabilityType.MODE] = CapabilityType.MODE state: ModeCapabilityInstanceActionState -class RangeCapabilityInstanceAction(BaseModel): +class RangeCapabilityInstanceAction(APIModel): """New capability state for a state change request of range capability.""" type: Literal[CapabilityType.RANGE] = CapabilityType.RANGE state: RangeCapabilityInstanceActionState -class ToggleCapabilityInstanceAction(BaseModel): +class ToggleCapabilityInstanceAction(APIModel): """New capability state for a state change request of toggle capability.""" type: Literal[CapabilityType.TOGGLE] = CapabilityType.TOGGLE state: ToggleCapabilityInstanceActionState -class VideoStreamCapabilityInstanceAction(BaseModel): +class VideoStreamCapabilityInstanceAction(APIModel): """New capability state for a state change request of video_stream capability.""" type: Literal[CapabilityType.VIDEO_STREAM] = CapabilityType.VIDEO_STREAM diff --git a/custom_components/yandex_smart_home/schema/capability_color.py b/custom_components/yandex_smart_home/schema/capability_color.py index e6b52bca..e2ff3ea9 100644 --- a/custom_components/yandex_smart_home/schema/capability_color.py +++ b/custom_components/yandex_smart_home/schema/capability_color.py @@ -6,7 +6,9 @@ from enum import StrEnum from typing import Annotated, Any, Literal, Self, Union -from pydantic import BaseModel, Field, root_validator +from pydantic import Field, root_validator + +from .base import APIModel class ColorSettingCapabilityInstance(StrEnum): @@ -49,14 +51,14 @@ class CapabilityParameterColorModel(StrEnum): HSV = "hsv" -class CapabilityParameterTemperatureK(BaseModel): +class CapabilityParameterTemperatureK(APIModel): """Color temperature range.""" min: int max: int -class CapabilityParameterColorScene(BaseModel): +class CapabilityParameterColorScene(APIModel): """Parameter of a scene instance.""" scenes: list[dict[Literal["id"], ColorScene]] @@ -66,7 +68,7 @@ def from_list(cls, scenes: list[ColorScene]) -> Self: return cls(scenes=[{"id": s} for s in scenes]) -class ColorSettingCapabilityParameters(BaseModel): +class ColorSettingCapabilityParameters(APIModel): """Parameters of a color_setting capability.""" color_model: CapabilityParameterColorModel | None @@ -81,21 +83,21 @@ def any_of(cls, values: dict[str, Any]) -> dict[str, Any]: return values -class RGBInstanceActionState(BaseModel): +class RGBInstanceActionState(APIModel): """New value for a rgb instance.""" instance: Literal[ColorSettingCapabilityInstance.RGB] = ColorSettingCapabilityInstance.RGB value: int -class TemperatureKInstanceActionState(BaseModel): +class TemperatureKInstanceActionState(APIModel): """New value for a temperature_k instance.""" instance: Literal[ColorSettingCapabilityInstance.TEMPERATURE_K] = ColorSettingCapabilityInstance.TEMPERATURE_K value: int -class SceneInstanceActionState(BaseModel): +class SceneInstanceActionState(APIModel): """New value for a scene instance.""" instance: Literal[ColorSettingCapabilityInstance.SCENE] = ColorSettingCapabilityInstance.SCENE diff --git a/custom_components/yandex_smart_home/schema/capability_mode.py b/custom_components/yandex_smart_home/schema/capability_mode.py index 3fb4d2d4..14d662da 100644 --- a/custom_components/yandex_smart_home/schema/capability_mode.py +++ b/custom_components/yandex_smart_home/schema/capability_mode.py @@ -5,7 +5,7 @@ from enum import StrEnum from typing import Literal, Self -from pydantic import BaseModel +from .base import APIModel class ModeCapabilityInstance(StrEnum): @@ -108,7 +108,7 @@ class ModeCapabilityMode(StrEnum): YOGURT = "yogurt" -class ModeCapabilityParameters(BaseModel): +class ModeCapabilityParameters(APIModel): """Parameters of a mode capability.""" instance: ModeCapabilityInstance @@ -119,7 +119,7 @@ def from_list(cls, instance: ModeCapabilityInstance, modes: list[ModeCapabilityM return cls(instance=instance, modes=[{"value": m} for m in modes]) -class ModeCapabilityInstanceActionState(BaseModel): +class ModeCapabilityInstanceActionState(APIModel): """New value for a mode capability.""" instance: ModeCapabilityInstance diff --git a/custom_components/yandex_smart_home/schema/capability_onoff.py b/custom_components/yandex_smart_home/schema/capability_onoff.py index 0237e6db..dc643985 100644 --- a/custom_components/yandex_smart_home/schema/capability_onoff.py +++ b/custom_components/yandex_smart_home/schema/capability_onoff.py @@ -4,7 +4,7 @@ """ from enum import StrEnum -from pydantic import BaseModel +from .base import APIModel class OnOffCapabilityInstance(StrEnum): @@ -13,13 +13,13 @@ class OnOffCapabilityInstance(StrEnum): ON = "on" -class OnOffCapabilityParameters(BaseModel): +class OnOffCapabilityParameters(APIModel): """Parameters of a on_off capability.""" split: bool -class OnOffCapabilityInstanceActionState(BaseModel): +class OnOffCapabilityInstanceActionState(APIModel): """New value for an on_off capability.""" instance: OnOffCapabilityInstance diff --git a/custom_components/yandex_smart_home/schema/capability_range.py b/custom_components/yandex_smart_home/schema/capability_range.py index 815e7e2c..f6d3e620 100644 --- a/custom_components/yandex_smart_home/schema/capability_range.py +++ b/custom_components/yandex_smart_home/schema/capability_range.py @@ -5,7 +5,9 @@ from enum import StrEnum from typing import Any -from pydantic import BaseModel, root_validator +from pydantic import root_validator + +from .base import APIModel class RangeCapabilityUnit(StrEnum): @@ -29,7 +31,7 @@ class RangeCapabilityInstance(StrEnum): VOLUME = "volume" -class RangeCapabilityRange(BaseModel): +class RangeCapabilityRange(APIModel): """Value range of a range capability.""" min: float @@ -40,7 +42,7 @@ def __str__(self) -> str: return f"[{self.min}, {self.max}]" -class RangeCapabilityParameters(BaseModel): +class RangeCapabilityParameters(APIModel): """Parameters of a range capability.""" instance: RangeCapabilityInstance @@ -64,7 +66,7 @@ def compute_unit(cls, values: dict[str, Any]) -> dict[str, Any]: return values -class RangeCapabilityInstanceActionState(BaseModel): +class RangeCapabilityInstanceActionState(APIModel): """New value for a range capability.""" instance: RangeCapabilityInstance diff --git a/custom_components/yandex_smart_home/schema/capability_toggle.py b/custom_components/yandex_smart_home/schema/capability_toggle.py index dbc71b56..621dc044 100644 --- a/custom_components/yandex_smart_home/schema/capability_toggle.py +++ b/custom_components/yandex_smart_home/schema/capability_toggle.py @@ -4,7 +4,7 @@ """ from enum import StrEnum -from pydantic import BaseModel +from .base import APIModel class ToggleCapabilityInstance(StrEnum): @@ -22,13 +22,13 @@ class ToggleCapabilityInstance(StrEnum): PAUSE = "pause" -class ToggleCapabilityParameters(BaseModel): +class ToggleCapabilityParameters(APIModel): """Parameters of a toggle capability.""" instance: ToggleCapabilityInstance -class ToggleCapabilityInstanceActionState(BaseModel): +class ToggleCapabilityInstanceActionState(APIModel): """New value for a toggle capability.""" instance: ToggleCapabilityInstance diff --git a/custom_components/yandex_smart_home/schema/capability_video.py b/custom_components/yandex_smart_home/schema/capability_video.py index ee81890c..3898c81b 100644 --- a/custom_components/yandex_smart_home/schema/capability_video.py +++ b/custom_components/yandex_smart_home/schema/capability_video.py @@ -5,7 +5,7 @@ from enum import StrEnum from typing import Literal -from pydantic import BaseModel +from .base import APIModel StreamProtocols = list[Literal["hls"]] @@ -16,26 +16,26 @@ class VideoStreamCapabilityInstance(StrEnum): GET_STREAM = "get_stream" -class VideoStreamCapabilityParameters(BaseModel): +class VideoStreamCapabilityParameters(APIModel): """Parameters of a video_stream capability.""" protocols: StreamProtocols -class GetStreamInstanceActionStateValue(BaseModel): +class GetStreamInstanceActionStateValue(APIModel): """New state value for a get_stream instance.""" protocols: StreamProtocols -class GetStreamInstanceActionState(BaseModel): +class GetStreamInstanceActionState(APIModel): """New value for a get_stream instance.""" instance: Literal[VideoStreamCapabilityInstance.GET_STREAM] value: GetStreamInstanceActionStateValue -class GetStreamInstanceActionResultValue(BaseModel): +class GetStreamInstanceActionResultValue(APIModel): """New value after a get_stream instance state changed.""" stream_url: str diff --git a/custom_components/yandex_smart_home/schema/device.py b/custom_components/yandex_smart_home/schema/device.py index 59fcede2..760a9af6 100644 --- a/custom_components/yandex_smart_home/schema/device.py +++ b/custom_components/yandex_smart_home/schema/device.py @@ -7,8 +7,7 @@ from enum import StrEnum from typing import Any, Literal -from pydantic import BaseModel - +from .base import APIModel from .capability import ( CapabilityDescription, CapabilityInstance, @@ -65,7 +64,7 @@ class DeviceType(StrEnum): OTHER = "devices.types.other" -class DeviceInfo(BaseModel): +class DeviceInfo(APIModel): """Extended device info.""" manufacturer: str | None @@ -74,7 +73,7 @@ class DeviceInfo(BaseModel): sw_version: str | None -class DeviceDescription(BaseModel): +class DeviceDescription(APIModel): """Device description for a device list request.""" id: str @@ -87,7 +86,7 @@ class DeviceDescription(BaseModel): device_info: DeviceInfo | None -class DeviceState(BaseModel): +class DeviceState(APIModel): """Device state for a state query request.""" id: str @@ -110,52 +109,52 @@ class DeviceStates(ResponsePayload): devices: list[DeviceState] -class StatesRequestDevice(BaseModel): +class StatesRequestDevice(APIModel): """Device for a state query request.""" id: str custom_data: dict[str, Any] | None -class StatesRequest(BaseModel): +class StatesRequest(APIModel): """Request body for a state query request.""" devices: list[StatesRequestDevice] -class ActionRequestDevice(BaseModel): +class ActionRequestDevice(APIModel): """Device for a state change request.""" id: str capabilities: list[CapabilityInstanceAction] -class ActionRequestPayload(BaseModel): +class ActionRequestPayload(APIModel): """Request payload for state change request.""" devices: list[ActionRequestDevice] -class ActionRequest(BaseModel): +class ActionRequest(APIModel): """Request body for a state change request.""" payload: ActionRequestPayload -class SuccessActionResult(BaseModel): +class SuccessActionResult(APIModel): """Success device action result.""" status: Literal["DONE"] = "DONE" -class FailedActionResult(BaseModel): +class FailedActionResult(APIModel): """Failed device action result.""" status: Literal["ERROR"] = "ERROR" error_code: ResponseCode -class ActionResultCapabilityState(BaseModel): +class ActionResultCapabilityState(APIModel): """Result of capability instance state change.""" instance: CapabilityInstance @@ -163,14 +162,14 @@ class ActionResultCapabilityState(BaseModel): action_result: SuccessActionResult | FailedActionResult -class ActionResultCapability(BaseModel): +class ActionResultCapability(APIModel): """Result of capability state change.""" type: CapabilityType state: ActionResultCapabilityState -class ActionResultDevice(BaseModel): +class ActionResultDevice(APIModel): """Device for a state change response.""" id: str diff --git a/custom_components/yandex_smart_home/schema/property.py b/custom_components/yandex_smart_home/schema/property.py index 1aabf8c4..6aaf6ae9 100644 --- a/custom_components/yandex_smart_home/schema/property.py +++ b/custom_components/yandex_smart_home/schema/property.py @@ -5,8 +5,7 @@ from enum import StrEnum from typing import Any, Literal -from pydantic import BaseModel - +from .base import APIModel from .property_event import EventPropertyInstance, EventPropertyParameters from .property_float import FloatPropertyInstance, FloatPropertyParameters @@ -18,7 +17,7 @@ class PropertyType(StrEnum): EVENT = "devices.properties.event" -class FloatPropertyDescription(BaseModel): +class FloatPropertyDescription(APIModel): """Description of a float property for a device list request.""" type: Literal[PropertyType.FLOAT] = PropertyType.FLOAT @@ -27,7 +26,7 @@ class FloatPropertyDescription(BaseModel): parameters: FloatPropertyParameters -class EventPropertyDescription(BaseModel): +class EventPropertyDescription(APIModel): """Description of an event property for a device list request.""" type: Literal[PropertyType.EVENT] = PropertyType.EVENT @@ -46,14 +45,14 @@ class EventPropertyDescription(BaseModel): """All property instances.""" -class PropertyInstanceStateValue(BaseModel): +class PropertyInstanceStateValue(APIModel): """Property instance value.""" instance: PropertyInstance value: Any -class PropertyInstanceState(BaseModel): +class PropertyInstanceState(APIModel): """Property state for state query and callback requests.""" type: PropertyType diff --git a/custom_components/yandex_smart_home/schema/property_event.py b/custom_components/yandex_smart_home/schema/property_event.py index 3f09d123..03521231 100644 --- a/custom_components/yandex_smart_home/schema/property_event.py +++ b/custom_components/yandex_smart_home/schema/property_event.py @@ -6,7 +6,8 @@ from typing import Any, Generic, Literal, TypeVar from pydantic import validator -from pydantic.generics import GenericModel + +from .base import GenericAPIModel class EventPropertyInstance(StrEnum): @@ -120,7 +121,7 @@ class WaterLeakInstanceEvent(StrEnum): """All events of event instances.""" -class EventPropertyParameters(GenericModel, Generic[EventInstanceEvent]): +class EventPropertyParameters(GenericAPIModel, Generic[EventInstanceEvent]): """Parameters of an event property.""" instance: EventPropertyInstance diff --git a/custom_components/yandex_smart_home/schema/property_float.py b/custom_components/yandex_smart_home/schema/property_float.py index f5b07a86..00b15d9a 100644 --- a/custom_components/yandex_smart_home/schema/property_float.py +++ b/custom_components/yandex_smart_home/schema/property_float.py @@ -5,7 +5,7 @@ from enum import StrEnum from typing import Literal -from pydantic import BaseModel +from .base import APIModel class FloatPropertyInstance(StrEnum): @@ -59,7 +59,7 @@ class TemperatureUnit(StrEnum): KELVIN = "unit.temperature.kelvin" -class FloatPropertyParameters(BaseModel): +class FloatPropertyParameters(APIModel): """Parameters of a float property.""" instance: FloatPropertyInstance diff --git a/custom_components/yandex_smart_home/schema/response.py b/custom_components/yandex_smart_home/schema/response.py index e6091bfa..c638761a 100644 --- a/custom_components/yandex_smart_home/schema/response.py +++ b/custom_components/yandex_smart_home/schema/response.py @@ -4,7 +4,7 @@ """ from enum import StrEnum -from pydantic import BaseModel +from .base import APIModel class ResponseCode(StrEnum): @@ -33,7 +33,7 @@ class ResponseCode(StrEnum): DEVICE_NOT_FOUND = "DEVICE_NOT_FOUND" -class ResponsePayload(BaseModel): +class ResponsePayload(APIModel): """Base class for an API response payload.""" @@ -44,7 +44,7 @@ class Error(ResponsePayload): error_message: str | None -class Response(BaseModel): +class Response(APIModel): """Base API response.""" request_id: str | None diff --git a/tests/test_capability_color.py b/tests/test_capability_color.py index 777cf2ae..ca829979 100644 --- a/tests/test_capability_color.py +++ b/tests/test_capability_color.py @@ -102,9 +102,9 @@ async def test_capability_color_setting_rgb(hass, color_modes, features): assert cap_cs.retrievable is True if light.ColorMode.RGBWW in color_modes or not color_modes: - assert cap_cs.parameters.dict(exclude_none=True) == {"color_model": "rgb"} + assert cap_cs.parameters.as_dict() == {"color_model": "rgb"} else: - assert cap_cs.parameters.dict(exclude_none=True) == { + assert cap_cs.parameters.as_dict() == { "color_model": "rgb", "temperature_k": {"max": 4500 if light.ColorMode.RGBW not in color_modes else 6500, "min": 4500}, } @@ -597,7 +597,7 @@ async def test_capability_color_setting_temperature_k_rgb(hass, color_modes): ), ) assert cap_cs.retrievable is True - assert cap_cs.parameters.dict(exclude_none=True) == { + assert cap_cs.parameters.as_dict() == { "color_model": "rgb", "temperature_k": {"max": 4500, "min": 4500}, } @@ -637,7 +637,7 @@ async def test_capability_color_setting_temperature_k_rgb_white(hass, color_mode ), ) assert cap_cs.retrievable is True - assert cap_cs.parameters.dict(exclude_none=True) == { + assert cap_cs.parameters.as_dict() == { "color_model": "rgb", "temperature_k": {"max": 6500, "min": 4500}, } @@ -691,7 +691,7 @@ async def test_capability_color_setting_temperature_k_rgbw(hass): ), ) assert cap_cs.retrievable is True - assert cap_cs.parameters.dict(exclude_none=True) == { + assert cap_cs.parameters.as_dict() == { "color_model": "rgb", "temperature_k": {"max": 6500, "min": 4500}, } @@ -810,9 +810,7 @@ async def test_capability_color_setting_scene(hass): hass, config, state, CapabilityType.COLOR_SETTING, ColorSettingCapabilityInstance.SCENE ), ) - assert cap_cs.parameters.dict(exclude_none=True) == { - "color_scene": {"scenes": [{"id": "alice"}, {"id": "garland"}]} - } + assert cap_cs.parameters.as_dict() == {"color_scene": {"scenes": [{"id": "alice"}, {"id": "garland"}]}} assert cap_scene.get_value() == "garland" assert cap_scene.get_description() is None @@ -829,9 +827,7 @@ async def test_capability_color_setting_scene(hass): ), ) assert cap_cs.retrievable is True - assert cap_cs.parameters.dict(exclude_none=True) == { - "color_scene": {"scenes": [{"id": "romance"}, {"id": "siren"}]} - } + assert cap_cs.parameters.as_dict() == {"color_scene": {"scenes": [{"id": "romance"}, {"id": "siren"}]}} assert cap_scene.get_value() is None cap_scene.state = State("light.test", STATE_OFF, dict({light.ATTR_EFFECT: "Rainbow"}, **attributes)) diff --git a/tests/test_capability_range.py b/tests/test_capability_range.py index f51ba50e..e2d348c7 100644 --- a/tests/test_capability_range.py +++ b/tests/test_capability_range.py @@ -81,7 +81,7 @@ def support_random_access(self) -> bool: max=range_max or cap._default_range.max, precision=range_prec or cap._default_range.precision, ) - assert cap.parameters.dict(exclude_none=True) == { + assert cap.parameters.as_dict() == { "instance": "volume", "random_access": True, "range": { @@ -198,7 +198,7 @@ async def test_capability_range_temperature_climate(hass): ) assert cap.retrievable is True assert cap.support_random_access is True - assert cap.parameters.dict(exclude_none=True) == { + assert cap.parameters.as_dict() == { "instance": "temperature", "random_access": True, "range": {"max": 25, "min": 10, "precision": 1}, @@ -222,7 +222,7 @@ async def test_capability_range_temperature_climate(hass): ) assert cap.retrievable is True assert cap.support_random_access is True - assert cap.parameters.dict(exclude_none=True) == { + assert cap.parameters.as_dict() == { "instance": "temperature", "random_access": True, "range": {"max": 27, "min": 12, "precision": 0.5}, @@ -269,7 +269,7 @@ async def test_capability_range_temperature_water_heater(hass): ) assert cap.retrievable is True assert cap.support_random_access is True - assert cap.parameters.dict(exclude_none=True) == { + assert cap.parameters.as_dict() == { "instance": "temperature", "random_access": True, "range": {"max": 90, "min": 30, "precision": 0.5}, @@ -330,7 +330,7 @@ async def test_capability_range_humidity_humidifier(hass): ) assert cap.retrievable is True assert cap.support_random_access is True - assert cap.parameters.dict(exclude_none=True) == { + assert cap.parameters.as_dict() == { "instance": "humidity", "random_access": True, "range": {"max": 80, "min": 10, "precision": 1}, @@ -380,7 +380,7 @@ async def test_capability_range_humidity_fan(hass): ) assert cap.retrievable is True assert cap.support_random_access is True - assert cap.parameters.dict(exclude_none=True) == { + assert cap.parameters.as_dict() == { "instance": "humidity", "random_access": True, "range": {"max": 100, "min": 0, "precision": 1}, @@ -419,7 +419,7 @@ async def test_capability_range_brightness_legacy(hass): ) assert cap.retrievable is True assert cap.support_random_access is True - assert cap.parameters.dict(exclude_none=True) == { + assert cap.parameters.as_dict() == { "instance": "brightness", "random_access": True, "range": {"max": 100, "min": 1, "precision": 1}, @@ -450,7 +450,7 @@ async def test_capability_range_brightness(hass, color_mode): ) assert cap.retrievable is True assert cap.support_random_access is True - assert cap.parameters.dict(exclude_none=True) == { + assert cap.parameters.as_dict() == { "instance": "brightness", "random_access": True, "range": {"max": 100, "min": 1, "precision": 1}, @@ -519,7 +519,7 @@ async def test_capability_range_volume_support_random(hass, features): ) assert cap.retrievable is True assert cap.support_random_access is True - assert cap.parameters.dict(exclude_none=True) == { + assert cap.parameters.as_dict() == { "instance": "volume", "random_access": True, "range": {"max": 100, "min": 0, "precision": 1}, @@ -706,7 +706,7 @@ async def test_capability_range_channel_set_random(hass): ) assert cap.retrievable is True assert cap.support_random_access is True - assert cap.parameters.dict(exclude_none=True) == { + assert cap.parameters.as_dict() == { "instance": "channel", "random_access": True, "range": {"max": 999, "min": 0, "precision": 1}, @@ -787,7 +787,7 @@ async def test_capability_range_channel_set_random_with_value(hass): ) assert cap.retrievable is True assert cap.support_random_access is True - assert cap.parameters.dict(exclude_none=True) == { + assert cap.parameters.as_dict() == { "instance": "channel", "random_access": True, "range": {"max": 999, "min": 0, "precision": 1}, @@ -894,7 +894,7 @@ async def test_capability_range_channel_set_relative(hass, features, device_clas else: assert cap.retrievable is False assert cap.support_random_access is False - assert cap.parameters.dict(exclude_none=True) == {"instance": "channel", "random_access": False} + assert cap.parameters.as_dict() == {"instance": "channel", "random_access": False} assert cap.get_value() is None calls_up = async_mock_service(hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_NEXT_TRACK) diff --git a/tests/test_device.py b/tests/test_device.py index e77094da..61624434 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -249,7 +249,7 @@ async def test_device_info(hass, registries): device = Device(hass, BASIC_CONFIG, state.entity_id, state) d = await device.describe(ent_reg, dev_reg, area_reg) assert d.id == "switch.test_1" - assert d.device_info.dict(exclude_none=True) == {"model": "switch.test_1", "manufacturer": "Acme Inc."} + assert d.device_info.as_dict() == {"model": "switch.test_1", "manufacturer": "Acme Inc."} state = State("switch.test_2", STATE_ON) device = dev_reg.async_get_or_create( @@ -268,7 +268,7 @@ async def test_device_info(hass, registries): device = Device(hass, BASIC_CONFIG, state.entity_id, state) d = await device.describe(ent_reg, dev_reg, area_reg) assert d.id == "switch.test_2" - assert d.device_info.dict(exclude_none=True) == { + assert d.device_info.as_dict() == { "manufacturer": "Acme Inc.", "model": "Ultra Switch | switch.test_2", "sw_version": "57", @@ -407,7 +407,7 @@ async def set_instance_state(self, _: Context, __: ToggleCapabilityInstanceActio state = State("switch.unavailable", STATE_UNAVAILABLE) device = Device(hass, BASIC_CONFIG, state.entity_id, state) - assert device.query().dict(exclude_none=True) == {"id": state.entity_id, "error_code": ERR_DEVICE_UNREACHABLE} + assert device.query().as_dict() == {"id": state.entity_id, "error_code": ERR_DEVICE_UNREACHABLE} state = State("switch.test", STATE_ON) state_pause = State("input_boolean.pause", STATE_OFF) @@ -471,7 +471,7 @@ async def set_instance_state(self, _: Context, __: ToggleCapabilityInstanceActio with patch.object(Device, "get_capabilities", return_value=[cap_onoff, cap_pause]), patch.object( Device, "get_properties", return_value=[prop_temp, prop_voltage, prop_humidity_custom, prop_button] ): - assert device.query().dict(exclude_none=True) == { + assert device.query().as_dict() == { "id": "switch.test", "capabilities": [ {"type": "devices.capabilities.on_off", "state": {"instance": "on", "value": True}}, @@ -530,7 +530,7 @@ async def set_instance_state(self, _: Context, __: ToggleCapabilityInstanceActio with patch.object(PauseCapability, "retrievable", PropertyMock(return_value=None)), patch.object( TemperatureSensor, "retrievable", PropertyMock(return_value=False) ): - assert device.query().dict(exclude_none=True) == { + assert device.query().as_dict() == { "id": "switch.test", "capabilities": [{"type": "devices.capabilities.on_off", "state": {"instance": "on", "value": True}}], "properties": [ @@ -542,7 +542,7 @@ async def set_instance_state(self, _: Context, __: ToggleCapabilityInstanceActio state_pause.state = STATE_UNAVAILABLE state_voltage.state = STATE_UNAVAILABLE prop_humidity_custom._native_value_source.state = STATE_UNAVAILABLE - assert device.query().dict(exclude_none=True) == { + assert device.query().as_dict() == { "id": "switch.test", "capabilities": [{"type": "devices.capabilities.on_off", "state": {"instance": "on", "value": True}}], "properties": [{"type": "devices.properties.float", "state": {"instance": "temperature", "value": 5.0}}], diff --git a/tests/test_handlers.py b/tests/test_handlers.py index 2e9e9ce2..9f9384b9 100644 --- a/tests/test_handlers.py +++ b/tests/test_handlers.py @@ -38,21 +38,21 @@ async def none(*_, **__): return None with patch("custom_components.yandex_smart_home.handlers.HANDLERS", r): - assert (await handlers.async_handle_request(hass, BASIC_DATA, "missing", "")).dict(exclude_none=True) == { + assert (await handlers.async_handle_request(hass, BASIC_DATA, "missing", "")).as_dict() == { "request_id": REQ_ID, "payload": {"error_code": "INTERNAL_ERROR"}, } assert caplog.messages == ["Unexpected action 'missing'"] caplog.clear() - assert (await handlers.async_handle_request(hass, BASIC_DATA, "error", "")).dict(exclude_none=True) == { + assert (await handlers.async_handle_request(hass, BASIC_DATA, "error", "")).as_dict() == { "request_id": REQ_ID, "payload": {"error_code": "INVALID_ACTION"}, } assert caplog.messages == ["foo (INVALID_ACTION)"] caplog.clear() - assert (await handlers.async_handle_request(hass, BASIC_DATA, "exception", "")).dict(exclude_none=True) == { + assert (await handlers.async_handle_request(hass, BASIC_DATA, "exception", "")).as_dict() == { "request_id": REQ_ID, "payload": {"error_code": "INTERNAL_ERROR"}, } @@ -60,7 +60,7 @@ async def none(*_, **__): assert "boooo" in caplog.records[-1].exc_text caplog.clear() - assert (await handlers.async_handle_request(hass, BASIC_DATA, "none", "")).dict(exclude_none=True) == { + assert (await handlers.async_handle_request(hass, BASIC_DATA, "none", "")).as_dict() == { "request_id": REQ_ID, } @@ -79,7 +79,7 @@ async def test_handler_devices_query(hass, caplog): {"devices": [{"id": switch_1.entity_id}, {"id": switch_not_expose.entity_id}, {"id": "invalid.foo"}]} ) - assert (await handlers.async_devices_query(hass, data, payload)).dict(exclude_none=True) == { + assert (await handlers.async_devices_query(hass, data, payload)).as_dict() == { "devices": [ { "id": "switch.test_1", @@ -94,7 +94,7 @@ async def test_handler_devices_query(hass, caplog): {"id": "invalid.foo", "error_code": "DEVICE_UNREACHABLE"}, ] } - assert (await handlers.async_device_list(hass, data, "")).dict(exclude_none=True) == { + assert (await handlers.async_device_list(hass, data, "")).as_dict() == { "user_id": "test", "devices": [ { @@ -220,7 +220,7 @@ def supported(self) -> bool: } } ) - assert (await handlers.async_devices_action(hass, BASIC_DATA, payload)).dict(exclude_none=True) == { + assert (await handlers.async_devices_action(hass, BASIC_DATA, payload)).as_dict() == { "devices": [ { "id": "switch.test_1", @@ -393,7 +393,7 @@ async def set_instance_state(self, context: Context, state: ToggleCapabilityInst } ) - assert (await handlers.async_devices_action(hass, data, payload)).dict(exclude_none=True) == { + assert (await handlers.async_devices_action(hass, data, payload)).as_dict() == { "devices": [ { "id": "switch.test", @@ -442,7 +442,7 @@ async def set_instance_state(self, context: Context, state: ToggleCapabilityInst } ) - assert (await handlers.async_devices_action(hass, data, payload)).dict(exclude_none=True) == { + assert (await handlers.async_devices_action(hass, data, payload)).as_dict() == { "devices": [ { "id": "switch.test",