From 192d6c1b0e6048f0b6ba644f9eea34141f7f80d5 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Mon, 4 May 2026 20:15:19 -0700 Subject: [PATCH] fix: invalidate cached plans and devices on environment reload Closes #1503 reload_environment() previously left the @cached_property values for plans and devices populated, so accessing them after a reload returned the pre-reload data. Drop the cached entries from __dict__ so the next access recomputes against the new environment. Signed-off-by: SAY-5 --- src/blueapi/client/client.py | 4 ++++ tests/unit_tests/client/test_client.py | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/blueapi/client/client.py b/src/blueapi/client/client.py index 32d589089..54d318bcb 100644 --- a/src/blueapi/client/client.py +++ b/src/blueapi/client/client.py @@ -675,6 +675,10 @@ def reload_environment( raise BlueskyRemoteControlError( "Failed to tear down the environment" ) from e + # Invalidate cached plans/devices so subsequent access reflects the + # newly reloaded environment rather than the previous one. + self.__dict__.pop("plans", None) + self.__dict__.pop("devices", None) return self._wait_for_reload( status, timeout, diff --git a/tests/unit_tests/client/test_client.py b/tests/unit_tests/client/test_client.py index a41c22bb1..18279f814 100644 --- a/tests/unit_tests/client/test_client.py +++ b/tests/unit_tests/client/test_client.py @@ -350,6 +350,28 @@ def test_reload_environment_failure( client.reload_environment() +def test_reload_environment_refreshes_cached_plans_and_devices( + client: BlueapiClient, + mock_rest: Mock, +): + # Prime caches with the original plans and devices. + initial_plans = list(client.plans) + initial_devices = list(client.devices) + assert {p.name for p in initial_plans} == {"foo", "bar"} + assert set(initial_devices) == {"foo", "bar"} + + new_plans = PlanResponse(plans=[PlanModel(name="missing")]) + new_devices = DeviceResponse(devices=[DeviceModel(name="baz", protocols=[])]) + mock_rest.get_plans.return_value = new_plans + mock_rest.get_devices.return_value = new_devices + mock_rest.get_environment.return_value = NEW_ENV + + client.reload_environment() + + assert {p.name for p in client.plans} == {"missing"} + assert set(client.devices) == {"baz"} + + def test_abort( client: BlueapiClient, mock_rest: Mock,