From 502f7383bee0127d6608960ef0b2b0d68eb24abd Mon Sep 17 00:00:00 2001 From: jepson2k <55201008+Jepson2k@users.noreply.github.com> Date: Wed, 13 May 2026 19:23:42 -0400 Subject: [PATCH] remove RobotClient.async_client property to eliminate cross-loop foot-gun MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The property returned self._inner — the AsyncRobotClient bound to RobotClient's private background loop. Reaching for it from another loop and awaiting one of its methods produced a cryptic "Queue is bound to a different event loop" deep inside _request_ok_raw. Worse, _run()'s error message steered users toward the trap: "use AsyncRobotClient and `await` the method instead" got read as "grab the one hanging off my sync client" rather than "construct a fresh one in this loop." - Delete the async_client property (zero non-test callers across this repo and Waldo-Commander; re-added in 8cc47dc as part of a typed-stub revert). - Tighten _run()'s error message to point at constructing a fresh AsyncRobotClient in the calling loop. - Update the two test sites that monkeypatched client.async_client._request to use client._inner._request directly — they were already patching a private method, so the change is honest. --- parol6/client/sync_client.py | 7 +------ tests/unit/test_conversions.py | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/parol6/client/sync_client.py b/parol6/client/sync_client.py index fd94e13..0febb7e 100644 --- a/parol6/client/sync_client.py +++ b/parol6/client/sync_client.py @@ -102,7 +102,7 @@ def _run(coro: Coroutine[Any, Any, T]) -> T: # A loop is running in this thread; blocking would be unsafe. raise RuntimeError( "RobotClient was used while an event loop is running.\n" - "Use AsyncRobotClient and `await` the method instead." + "Construct an AsyncRobotClient in this loop and `await` it instead." ) @@ -166,11 +166,6 @@ def __enter__(self) -> "RobotClient": def __exit__(self, exc_type, exc, tb) -> None: self.close() - @property - def async_client(self) -> AsyncRobotClient: - """Access the underlying async client if you need it.""" - return self._inner - # Expose common configuration attributes @property def host(self) -> str: diff --git a/tests/unit/test_conversions.py b/tests/unit/test_conversions.py index 28182f9..b4804f9 100644 --- a/tests/unit/test_conversions.py +++ b/tests/unit/test_conversions.py @@ -29,7 +29,7 @@ def test_pose_identity_translation(monkeypatch): result = _pose_result(mat) mock_request = AsyncMock(return_value=result) - monkeypatch.setattr(client.async_client, "_request", mock_request) + monkeypatch.setattr(client._inner, "_request", mock_request) pose_rpy = client.pose() assert pose_rpy is not None @@ -49,7 +49,7 @@ def test_pose_malformed_payload(monkeypatch): client = RobotClient() mock_request = AsyncMock(return_value=PoseResultStruct(pose=[1, 2, 3])) - monkeypatch.setattr(client.async_client, "_request", mock_request) + monkeypatch.setattr(client._inner, "_request", mock_request) pose_rpy = client.pose() assert pose_rpy is None