From 6e52374447f83558f033d9e065498bc471632022 Mon Sep 17 00:00:00 2001 From: Jeremie Date: Sat, 16 May 2026 14:28:50 +0200 Subject: [PATCH 1/3] fix(remote_services): immediate result codes collapsed to UNKNOWN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `_send_command` got a terminal result with no `status_id`, it called `RemoteServiceStatus(result_code)` with a bare string ("PERFORMED", "ERROR", ...). The constructor then ran `if "status" in response:` on that string — a string-in-string check that is False for these codes — so `state` silently fell through to ExecutionState.UNKNOWN. Callers had no way to distinguish a successful remote action from a server timeout. Two changes: 1. `_send_command` (`remote_services.py:299`): wrap the bare code in the same dict shape the polling endpoint returns so the constructor extracts it through the existing path: RemoteServiceStatus({"status": {"result": result_code}}) 2. `RemoteServiceStatus.__init__`: guard with `isinstance(response, dict)` and tolerate result codes outside the ExecutionState enum (e.g. a future "REJECTED") with a try/except ValueError — falling back to UNKNOWN instead of crashing during construction. Concrete repro (no test infra needed): >>> from pyporscheconnectapi.remote_services import ( ... RemoteServiceStatus, ExecutionState, ... ) >>> # Before this patch: silent UNKNOWN. >>> # After: >>> s = RemoteServiceStatus({"status": {"result": "PERFORMED"}}) >>> s.state == ExecutionState.PERFORMED True Co-Authored-By: Claude Opus 4.7 (1M context) --- pyporscheconnectapi/remote_services.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/pyporscheconnectapi/remote_services.py b/pyporscheconnectapi/remote_services.py index 68ffd7e..86d3dbf 100644 --- a/pyporscheconnectapi/remote_services.py +++ b/pyporscheconnectapi/remote_services.py @@ -53,12 +53,20 @@ class RemoteServiceStatus: def __init__(self, response: dict, status_id: str | None = None) -> None: """Construct a new object from a dict.""" + # `response` must be a dict shaped like `{"status": {"result": "..."}}`. + # Callers passing a bare result code (e.g. "PERFORMED") would silently + # land in the UNKNOWN state because `"status" in "PERFORMED"` is False. status = None - if "status" in response: + if isinstance(response, dict) and "status" in response: status = response.get("status", {}).get("result") self.status = status - self.state = ExecutionState(status or "UNKNOWN") + # Tolerate result codes outside the enum (e.g. "REJECTED" if the API + # ever introduces one) rather than raising during construction. + try: + self.state = ExecutionState(status or "UNKNOWN") + except ValueError: + self.state = ExecutionState.UNKNOWN self.details = response self.status_id = status_id @@ -296,7 +304,14 @@ async def _send_command( _LOGGER.debug("Got result: %s (%s)", result_code, status_id) - status = await self._block_until_done(status_id) if status_id and result_code == "ACCEPTED" else RemoteServiceStatus(result_code) + # For an immediate (non-ACCEPTED) result, wrap the bare result code in + # the same dict shape the polling endpoint returns so the constructor + # can extract it the same way — otherwise `state` would silently stay + # UNKNOWN for PERFORMED/ERROR replies. + if status_id and result_code == "ACCEPTED": + status = await self._block_until_done(status_id) + else: + status = RemoteServiceStatus({"status": {"result": result_code}}) await asyncio.sleep(_POLLING_DELAY) await self._vehicle.get_stored_overview() From 8a13b7cf4f32f63eda4a51006ca2b99ca12cba44 Mon Sep 17 00:00:00 2001 From: Fredrik Ljunggren Date: Sat, 29 Aug 2026 16:35:05 +0200 Subject: [PATCH 2/3] Clean up comments in remote_services.py Removed comments clarifying response structure and error handling. --- pyporscheconnectapi/remote_services.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyporscheconnectapi/remote_services.py b/pyporscheconnectapi/remote_services.py index 86d3dbf..c07ef48 100644 --- a/pyporscheconnectapi/remote_services.py +++ b/pyporscheconnectapi/remote_services.py @@ -53,16 +53,12 @@ class RemoteServiceStatus: def __init__(self, response: dict, status_id: str | None = None) -> None: """Construct a new object from a dict.""" - # `response` must be a dict shaped like `{"status": {"result": "..."}}`. - # Callers passing a bare result code (e.g. "PERFORMED") would silently - # land in the UNKNOWN state because `"status" in "PERFORMED"` is False. status = None if isinstance(response, dict) and "status" in response: status = response.get("status", {}).get("result") self.status = status - # Tolerate result codes outside the enum (e.g. "REJECTED" if the API - # ever introduces one) rather than raising during construction. + try: self.state = ExecutionState(status or "UNKNOWN") except ValueError: From 8c302572d50be9fe6074af83b804c0954cb1c26d Mon Sep 17 00:00:00 2001 From: Fredrik Ljunggren Date: Sat, 29 Aug 2026 16:35:54 +0200 Subject: [PATCH 3/3] Remove comments from remote_services.py Removed comments explaining result handling for immediate results. --- pyporscheconnectapi/remote_services.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyporscheconnectapi/remote_services.py b/pyporscheconnectapi/remote_services.py index c07ef48..831307c 100644 --- a/pyporscheconnectapi/remote_services.py +++ b/pyporscheconnectapi/remote_services.py @@ -300,10 +300,6 @@ async def _send_command( _LOGGER.debug("Got result: %s (%s)", result_code, status_id) - # For an immediate (non-ACCEPTED) result, wrap the bare result code in - # the same dict shape the polling endpoint returns so the constructor - # can extract it the same way — otherwise `state` would silently stay - # UNKNOWN for PERFORMED/ERROR replies. if status_id and result_code == "ACCEPTED": status = await self._block_until_done(status_id) else: