From 7d145cd3b8cc7d7d6aa8d71c1d81437eeae1d260 Mon Sep 17 00:00:00 2001 From: Brett Adams Date: Wed, 1 Apr 2026 05:52:09 +1000 Subject: [PATCH] Add command compatibility scaffold for Tessie migration (#166458) --- homeassistant/components/tessie/entity.py | 39 +++++++++------------- homeassistant/components/tessie/helpers.py | 27 +++++++++++++-- homeassistant/components/tessie/models.py | 3 +- tests/components/tessie/test_init.py | 10 ++++++ 4 files changed, 52 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/tessie/entity.py b/homeassistant/components/tessie/entity.py index 98a424eefc1..e42ed57316c 100644 --- a/homeassistant/components/tessie/entity.py +++ b/homeassistant/components/tessie/entity.py @@ -2,21 +2,20 @@ from abc import abstractmethod from collections.abc import Awaitable, Callable +from inspect import isawaitable from typing import Any -from aiohttp import ClientError - -from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, TRANSLATED_ERRORS +from .const import DOMAIN from .coordinator import ( TessieEnergyHistoryCoordinator, TessieEnergySiteInfoCoordinator, TessieEnergySiteLiveCoordinator, TessieStateUpdateCoordinator, ) +from .helpers import handle_command, handle_legacy_command from .models import TessieEnergyData, TessieVehicleData @@ -93,30 +92,24 @@ class TessieEntity(TessieBaseEntity): self.async_write_ha_state() async def run( - self, func: Callable[..., Awaitable[dict[str, Any]]], **kargs: Any + self, + command: Callable[..., Awaitable[dict[str, Any]]] | Awaitable[dict[str, Any]], + **kargs: Any, ) -> None: - """Run a tessie_api function and handle exceptions.""" - try: - response = await func( + """Run a legacy tessie_api command function or awaitable Vehicle command.""" + if isawaitable(command): + await handle_command(command) + return + + await handle_legacy_command( + command( session=self._session, vin=self.vin, api_key=self._api_key, **kargs, - ) - except ClientError as e: - raise HomeAssistantError( - translation_domain=DOMAIN, - translation_key="cannot_connect", - ) from e - if response["result"] is False: - name: str = getattr(self, "name", self.entity_id) - reason: str = response.get("reason", "unknown") - translation_key = TRANSLATED_ERRORS.get(reason, "command_failed") - raise HomeAssistantError( - translation_domain=DOMAIN, - translation_key=translation_key, - translation_placeholders={"name": name, "message": reason}, - ) + ), + name=getattr(self, "name", self.entity_id), + ) def _async_update_attrs(self) -> None: """Update the attributes of the entity.""" diff --git a/homeassistant/components/tessie/helpers.py b/homeassistant/components/tessie/helpers.py index 41e619ac10d..321ad0d9aa0 100644 --- a/homeassistant/components/tessie/helpers.py +++ b/homeassistant/components/tessie/helpers.py @@ -1,17 +1,19 @@ """Tessie helper functions.""" +from collections.abc import Awaitable from typing import Any +from aiohttp import ClientError from tesla_fleet_api.exceptions import TeslaFleetError from homeassistant.exceptions import HomeAssistantError from . import _LOGGER -from .const import DOMAIN +from .const import DOMAIN, TRANSLATED_ERRORS -async def handle_command(command) -> dict[str, Any]: - """Handle a command.""" +async def handle_command(command: Awaitable[dict[str, Any]]) -> dict[str, Any]: + """Handle an awaitable Vehicle/EnergySite command.""" try: result = await command except TeslaFleetError as e: @@ -22,3 +24,22 @@ async def handle_command(command) -> dict[str, Any]: ) from e _LOGGER.debug("Command result: %s", result) return result + + +async def handle_legacy_command(command: Awaitable[dict[str, Any]], name: str) -> None: + """Handle a legacy tessie_api command result.""" + try: + response = await command + except ClientError as e: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="cannot_connect", + ) from e + if response["result"] is False: + reason: str = response.get("reason", "unknown") + translation_key = TRANSLATED_ERRORS.get(reason, "command_failed") + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key=translation_key, + translation_placeholders={"name": name, "message": reason}, + ) diff --git a/homeassistant/components/tessie/models.py b/homeassistant/components/tessie/models.py index e4e4bb34e81..c9b1105281e 100644 --- a/homeassistant/components/tessie/models.py +++ b/homeassistant/components/tessie/models.py @@ -4,7 +4,7 @@ from __future__ import annotations from dataclasses import dataclass -from tesla_fleet_api.tessie import EnergySite +from tesla_fleet_api.tessie import EnergySite, Vehicle from homeassistant.helpers.device_registry import DeviceInfo @@ -43,3 +43,4 @@ class TessieVehicleData: data_coordinator: TessieStateUpdateCoordinator device: DeviceInfo vin: str + api: Vehicle | None = None diff --git a/tests/components/tessie/test_init.py b/tests/components/tessie/test_init.py index 921ef93b1ae..e0ffd8fd57e 100644 --- a/tests/components/tessie/test_init.py +++ b/tests/components/tessie/test_init.py @@ -68,3 +68,13 @@ async def test_scopes_error(hass: HomeAssistant) -> None: ): entry = await setup_platform(hass) assert entry.state is ConfigEntryState.SETUP_RETRY + + +async def test_vehicle_api_handle_is_optional(hass: HomeAssistant) -> None: + """Test runtime vehicle API handle defaults to None during scaffold stage.""" + + entry = await setup_platform(hass) + assert entry.state is ConfigEntryState.LOADED + vehicles = entry.runtime_data.vehicles + assert vehicles + assert all(vehicle.api is None for vehicle in vehicles)