From 4ba4f12c51c6186348ba6a48c3f83d8d38b9fba8 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 17 Jul 2026 10:25:17 +0000 Subject: [PATCH 01/14] Update to ophyd-async v0.20 --- pyproject.toml | 4 ++-- .../electron_analyser/mbs/mbs_detector.py | 2 -- .../electron_analyser/specs/specs_detector.py | 1 - .../vgscienta/vgscienta_detector.py | 1 - .../insertion_device/apple2_controller.py | 18 +++++++++++++----- src/dodal/testing/fixtures/devices/apple2.py | 9 +++++---- .../base/test_base_detector.py | 2 ++ .../insertion_device/test_apple2_undulator.py | 11 ++++++----- .../test_apple_knot_undulator.py | 7 +++---- uv.lock | 10 +++++----- 10 files changed, 36 insertions(+), 29 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0fed946874c..7b52b077fa3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ description = "Ophyd devices and other utils that could be used across DLS beaml dependencies = [ "click", "ophyd", - "ophyd-async[ca,pva]>=0.19.3", + "ophyd-async[ca,pva]>=0.20.0", "bluesky>=1.14.5", "pyepics", "pillow", @@ -51,7 +51,7 @@ dev = [ # Commented out due to dependency version conflict with pydantic 1.x # "copier", "myst-parser", - "ophyd_async[sim]>=v0.19.3", + "ophyd_async[sim]>=0.20.0", "pre-commit", "psutil", "pydata-sphinx-theme>=0.12", diff --git a/src/dodal/devices/electron_analyser/mbs/mbs_detector.py b/src/dodal/devices/electron_analyser/mbs/mbs_detector.py index 98b8ca5098d..7030b97ae54 100644 --- a/src/dodal/devices/electron_analyser/mbs/mbs_detector.py +++ b/src/dodal/devices/electron_analyser/mbs/mbs_detector.py @@ -47,8 +47,6 @@ def __init__( driver.slices, driver.iterations, driver.total_steps, - driver.acquire_time, - driver.acquire_period, driver.total_time, driver.energy_axis, driver.angle_axis, diff --git a/src/dodal/devices/electron_analyser/specs/specs_detector.py b/src/dodal/devices/electron_analyser/specs/specs_detector.py index 78117604264..55d9966e1b2 100644 --- a/src/dodal/devices/electron_analyser/specs/specs_detector.py +++ b/src/dodal/devices/electron_analyser/specs/specs_detector.py @@ -45,7 +45,6 @@ def __init__( driver.energy_step, driver.pass_energy, driver.slices, - driver.acquire_time, driver.iterations, driver.total_steps, driver.total_time, diff --git a/src/dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py b/src/dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py index a214939a4fc..cf9b10b1a8f 100644 --- a/src/dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py +++ b/src/dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py @@ -53,7 +53,6 @@ def __init__( driver.slices, driver.iterations, driver.total_steps, - driver.acquire_time, driver.total_time, driver.energy_axis, driver.angle_axis, diff --git a/src/dodal/devices/insertion_device/apple2_controller.py b/src/dodal/devices/insertion_device/apple2_controller.py index 8a14744bcc1..35d6b4ae7be 100644 --- a/src/dodal/devices/insertion_device/apple2_controller.py +++ b/src/dodal/devices/insertion_device/apple2_controller.py @@ -128,23 +128,31 @@ def __init__( phase = self.apple2().phase() # check if undulator phase is unlocked. if isinstance(phase, UndulatorPhaseAxes): - top_inner = phase.top_inner.user_readback - btm_outer = phase.btm_outer.user_readback + self.top_inner = phase.top_inner.user_readback + self.btm_outer = phase.btm_outer.user_readback else: # If locked phase axes make the locked phase 0. - top_inner = btm_outer = soft_signal_rw(float, initial_value=0.0) + self.top_inner = soft_signal_rw(float, initial_value=0.0) + self.btm_outer = soft_signal_rw(float, initial_value=0.0) with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL): # Hardware backed read/write for polarisation. + print(self.polarisation_setpoint.name) + print(phase.top_outer.user_readback.name) + print(self.top_inner.name) + print(phase.btm_inner.user_readback.name) + print(self.btm_outer.name) + print(self.apple2().gap().user_readback.name) + self.polarisation = derived_signal_rw( raw_to_derived=self._read_pol, set_derived=self._set_pol, pol=self.polarisation_setpoint, top_outer=phase.top_outer.user_readback, - top_inner=top_inner, + top_inner=self.top_inner, btm_inner=phase.btm_inner.user_readback, - btm_outer=btm_outer, + btm_outer=self.btm_outer, gap=self.apple2().gap().user_readback, ) with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL): diff --git a/src/dodal/testing/fixtures/devices/apple2.py b/src/dodal/testing/fixtures/devices/apple2.py index a8d06cedc14..ff7bc0d75de 100644 --- a/src/dodal/testing/fixtures/devices/apple2.py +++ b/src/dodal/testing/fixtures/devices/apple2.py @@ -109,8 +109,9 @@ async def mock_locked_apple2( mock_id_gap: UndulatorGap, mock_locked_phase_axes: UndulatorLockedPhaseAxes, ) -> Apple2[UndulatorLockedPhaseAxes]: - mock_locked_apple2 = Apple2[UndulatorLockedPhaseAxes]( - id_gap=mock_id_gap, - id_phase=mock_locked_phase_axes, - ) + with init_devices(mock=True): + mock_locked_apple2 = Apple2[UndulatorLockedPhaseAxes]( + id_gap=mock_id_gap, + id_phase=mock_locked_phase_axes, + ) return mock_locked_apple2 diff --git a/tests/devices/electron_analyser/base/test_base_detector.py b/tests/devices/electron_analyser/base/test_base_detector.py index fc4ff5e78b8..f83aef1084e 100644 --- a/tests/devices/electron_analyser/base/test_base_detector.py +++ b/tests/devices/electron_analyser/base/test_base_detector.py @@ -16,6 +16,7 @@ from tests.devices.electron_analyser.helper_util import ( generate_fixture_regions_pair, load_b07_specs_test_seq, + load_i05_mbs_test_xml_seq, load_i09_vgscienta_test_seq, ) @@ -81,6 +82,7 @@ def test_analyser_detector_set_called_region_logic_setup_with_region( [ pytest.param("ew4000", load_i09_vgscienta_test_seq()), pytest.param("b07b_specs150", load_b07_specs_test_seq()), + pytest.param("i05_mbs_analyser", load_i05_mbs_test_xml_seq()), ], indirect=["sim_detector"], ) diff --git a/tests/devices/insertion_device/test_apple2_undulator.py b/tests/devices/insertion_device/test_apple2_undulator.py index 80820441057..66c537be959 100644 --- a/tests/devices/insertion_device/test_apple2_undulator.py +++ b/tests/devices/insertion_device/test_apple2_undulator.py @@ -422,11 +422,12 @@ async def mock_locked_controller( configured_gap: float, configured_phase: float, ) -> DummyLockedApple2Controller: - mock_locked_controller = DummyLockedApple2Controller( - apple2=mock_locked_apple2, - gap_energy_motor_converter=lambda value, pol: configured_gap, - phase_energy_motor_converter=lambda value, pol: configured_phase, - ) + with init_devices(mock=True): + mock_locked_controller = DummyLockedApple2Controller( + apple2=mock_locked_apple2, + gap_energy_motor_converter=lambda value, pol: configured_gap, + phase_energy_motor_converter=lambda value, pol: configured_phase, + ) return mock_locked_controller diff --git a/tests/devices/insertion_device/test_apple_knot_undulator.py b/tests/devices/insertion_device/test_apple_knot_undulator.py index 007eee81ef0..cbc67906e76 100644 --- a/tests/devices/insertion_device/test_apple_knot_undulator.py +++ b/tests/devices/insertion_device/test_apple_knot_undulator.py @@ -1,8 +1,5 @@ import pytest -from ophyd_async.core import ( - init_devices, - set_mock_value, -) +from ophyd_async.core import init_devices, set_mock_value from dodal.devices.beamlines.i05_shared import ( energy_to_gap_converter, @@ -59,6 +56,8 @@ async def test_id_set_energy_const_pol( initial_phase_top_outer: float, expected_pol: Pol, ): + await mock_locked_apple2.connect(mock=True) + await mock_apple_knot_i05_controller.connect(mock=True) set_mock_value( mock_locked_apple2.phase().top_outer.user_readback, initial_phase_top_outer ) diff --git a/uv.lock b/uv.lock index 54cdb03efb8..0a7bc2865bb 100644 --- a/uv.lock +++ b/uv.lock @@ -824,7 +824,7 @@ requires-dist = [ { name = "numpy" }, { name = "opencv-python-headless" }, { name = "ophyd" }, - { name = "ophyd-async", extras = ["ca", "pva"], specifier = ">=0.19.3" }, + { name = "ophyd-async", extras = ["ca", "pva"], specifier = ">=0.20.0" }, { name = "pillow" }, { name = "pydantic", specifier = ">=2.0" }, { name = "pyepics" }, @@ -843,7 +843,7 @@ dev = [ { name = "ispyb" }, { name = "mypy" }, { name = "myst-parser" }, - { name = "ophyd-async", extras = ["sim"], specifier = ">=0.19.3" }, + { name = "ophyd-async", extras = ["sim"], specifier = ">=0.20.0" }, { name = "pre-commit" }, { name = "psutil" }, { name = "pydata-sphinx-theme", specifier = ">=0.12" }, @@ -2256,7 +2256,7 @@ wheels = [ [[package]] name = "ophyd-async" -version = "0.19.3" +version = "0.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bluesky" }, @@ -2270,9 +2270,9 @@ dependencies = [ { name = "stamina" }, { name = "velocity-profile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/20/1241051befc05c21c958206611f9ed058d81b8e8f92e3acf9f9d6936f2fc/ophyd_async-0.19.3.tar.gz", hash = "sha256:9cb76a8a08794a26c619a0420012e7248542501d036f16f3314221869e989984", size = 595679, upload-time = "2026-06-29T15:21:37.859Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/ee/0bc3ffcb7586c95be6d2d053ad7e1fb0f383e5e068bad9943bbc5bed885e/ophyd_async-0.20.0.tar.gz", hash = "sha256:2138489bb2543a08ca674cc86de243245209626886e502694c84ecdeeccf97ac", size = 652415, upload-time = "2026-07-17T08:50:46.01Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/2c/76ac5e485f6405fc0f1dbbd1c98b7c1137d4aa5c1e49ea9e69bbd8d22ae1/ophyd_async-0.19.3-py3-none-any.whl", hash = "sha256:ef6201edb52e5a54008d57cad1808626aa6493ba4a8fd015e08d14bcffbb697a", size = 232796, upload-time = "2026-06-29T15:21:36.46Z" }, + { url = "https://files.pythonhosted.org/packages/83/05/1001c7b589430a45219348c8c32622305304be2173b74d202fd596a39c3e/ophyd_async-0.20.0-py3-none-any.whl", hash = "sha256:3459199794587131bd03ee65b75122f6656fd7500d5fbf91cc5ecfd14c889ca3", size = 258059, upload-time = "2026-07-17T08:50:44.244Z" }, ] [package.optional-dependencies] From 7ccbc2836facfeeade88b620311070e08835328f Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 17 Jul 2026 10:37:22 +0000 Subject: [PATCH 02/14] Update naming issue --- .../insertion_device/apple2_controller.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/dodal/devices/insertion_device/apple2_controller.py b/src/dodal/devices/insertion_device/apple2_controller.py index 35d6b4ae7be..47027c6ede0 100644 --- a/src/dodal/devices/insertion_device/apple2_controller.py +++ b/src/dodal/devices/insertion_device/apple2_controller.py @@ -128,31 +128,22 @@ def __init__( phase = self.apple2().phase() # check if undulator phase is unlocked. if isinstance(phase, UndulatorPhaseAxes): - self.top_inner = phase.top_inner.user_readback - self.btm_outer = phase.btm_outer.user_readback + top_inner = phase.top_inner.user_readback + btm_outer = phase.btm_outer.user_readback else: # If locked phase axes make the locked phase 0. - self.top_inner = soft_signal_rw(float, initial_value=0.0) - self.btm_outer = soft_signal_rw(float, initial_value=0.0) + top_inner = btm_outer = 0 with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL): # Hardware backed read/write for polarisation. - - print(self.polarisation_setpoint.name) - print(phase.top_outer.user_readback.name) - print(self.top_inner.name) - print(phase.btm_inner.user_readback.name) - print(self.btm_outer.name) - print(self.apple2().gap().user_readback.name) - self.polarisation = derived_signal_rw( raw_to_derived=self._read_pol, set_derived=self._set_pol, pol=self.polarisation_setpoint, top_outer=phase.top_outer.user_readback, - top_inner=self.top_inner, + top_inner=top_inner, btm_inner=phase.btm_inner.user_readback, - btm_outer=self.btm_outer, + btm_outer=btm_outer, gap=self.apple2().gap().user_readback, ) with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL): From 8f040e2b0999eaf8d596105ceff0b43c57fc98cc Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 17 Jul 2026 12:39:06 +0000 Subject: [PATCH 03/14] Update remaining tests that are not bluesky verb errors --- .../high_field_magnet/high_field_magnet.py | 1 - .../insertion_device/apple2_controller.py | 2 +- tests/devices/beamlines/b07_1/test_ccmc.py | 12 +++++++ .../i06_shared/test_i06_apple2controller.py | 25 +++++++++------ .../test_high_field_magnet.py | 16 ++-------- tests/devices/beamlines/i22/test_dcm.py | 32 +++++++++++++++++++ .../test_apple2_controller.py | 13 ++++---- tests/devices/test_undulator.py | 10 +++++- 8 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/dodal/devices/beamlines/i10_1/high_field_magnet/high_field_magnet.py b/src/dodal/devices/beamlines/i10_1/high_field_magnet/high_field_magnet.py index 7cd9b78fd75..742196cec17 100644 --- a/src/dodal/devices/beamlines/i10_1/high_field_magnet/high_field_magnet.py +++ b/src/dodal/devices/beamlines/i10_1/high_field_magnet/high_field_magnet.py @@ -107,7 +107,6 @@ def __init__( read_pv=prefix + "RBV:SETPOINTFIELD", write_pv=prefix + "SET:SETPOINTFIELD", ) - self._set_success = True self._fly_info: FlyMagInfo | None = None self._fly_status: WatchableAsyncStatus | None = None diff --git a/src/dodal/devices/insertion_device/apple2_controller.py b/src/dodal/devices/insertion_device/apple2_controller.py index 47027c6ede0..8a10d5259ea 100644 --- a/src/dodal/devices/insertion_device/apple2_controller.py +++ b/src/dodal/devices/insertion_device/apple2_controller.py @@ -132,7 +132,7 @@ def __init__( btm_outer = phase.btm_outer.user_readback else: # If locked phase axes make the locked phase 0. - top_inner = btm_outer = 0 + top_inner = btm_outer = 0.0 with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL): # Hardware backed read/write for polarisation. diff --git a/tests/devices/beamlines/b07_1/test_ccmc.py b/tests/devices/beamlines/b07_1/test_ccmc.py index 6ce7e586f8d..47191aff902 100644 --- a/tests/devices/beamlines/b07_1/test_ccmc.py +++ b/tests/devices/beamlines/b07_1/test_ccmc.py @@ -36,12 +36,24 @@ async def test_read_config_includes(mock_ccmc: ChannelCutMonochromator): f"{mock_ccmc.name}-_xyz-x-motor_egu": partial_reading(""), f"{mock_ccmc.name}-_xyz-x-offset": partial_reading(0.0), f"{mock_ccmc.name}-_xyz-x-velocity": partial_reading(1000.0), + f"{mock_ccmc.name}-_xyz-x-encoder_resolution": partial_reading(0), + f"{mock_ccmc.name}-_xyz-x-motor_resolution": partial_reading(0), + f"{mock_ccmc.name}-_xyz-x-steps_per_revolution": partial_reading(0), + f"{mock_ccmc.name}-_xyz-x-units_per_revolution": partial_reading(0), f"{mock_ccmc.name}-_xyz-y-motor_egu": partial_reading(""), f"{mock_ccmc.name}-_xyz-y-offset": partial_reading(0.0), f"{mock_ccmc.name}-_xyz-y-velocity": partial_reading(1000.0), + f"{mock_ccmc.name}-_xyz-y-encoder_resolution": partial_reading(0), + f"{mock_ccmc.name}-_xyz-y-motor_resolution": partial_reading(0), + f"{mock_ccmc.name}-_xyz-y-steps_per_revolution": partial_reading(0), + f"{mock_ccmc.name}-_xyz-y-units_per_revolution": partial_reading(0), f"{mock_ccmc.name}-_xyz-z-motor_egu": partial_reading(""), f"{mock_ccmc.name}-_xyz-z-offset": partial_reading(0.0), f"{mock_ccmc.name}-_xyz-z-velocity": partial_reading(1000.0), + f"{mock_ccmc.name}-_xyz-z-encoder_resolution": partial_reading(0), + f"{mock_ccmc.name}-_xyz-z-motor_resolution": partial_reading(0), + f"{mock_ccmc.name}-_xyz-z-steps_per_revolution": partial_reading(0), + f"{mock_ccmc.name}-_xyz-z-units_per_revolution": partial_reading(0), }, ) diff --git a/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py b/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py index c97e1044aa0..8a2aa1c72b9 100644 --- a/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py +++ b/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py @@ -1,6 +1,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest +from ophyd_async.core import init_devices from dodal.devices.beamlines.i06_shared import I06Apple2Controller from dodal.devices.insertion_device import ( @@ -13,16 +14,19 @@ ) from dodal.devices.insertion_device.energy_motor_lookup import EnergyMotorLookup +# add mock_id_gap, mock_phase and mock_jaw_phase_axes to pytest. +pytest_plugins = ["dodal.testing.fixtures.devices.apple2"] + @pytest.fixture async def mock_apple2( mock_id_gap: UndulatorGap, mock_phase_axes: UndulatorPhaseAxes, ) -> Apple2[UndulatorPhaseAxes]: - mock_apple2 = Apple2[UndulatorPhaseAxes]( - id_gap=mock_id_gap, - id_phase=mock_phase_axes, - ) + with init_devices(mock=True): + mock_apple2 = Apple2[UndulatorPhaseAxes]( + id_gap=mock_id_gap, id_phase=mock_phase_axes + ) return mock_apple2 @@ -38,12 +42,13 @@ async def mock_i06_controller( mock_inverse_gap_energy_motor_lut.find_value_in_lookup_table = MagicMock( return_value=100 ) - mock_i06_controller = I06Apple2Controller( - apple2=mock_apple2, - gap_energy_motor_lut=mock_gap_energy_motor_lut, - phase_energy_motor_lut=mock_phase_energy_motor_lut, - inverse_gap_energy_motor_lut=mock_inverse_gap_energy_motor_lut, - ) + with init_devices(mock=True): + mock_i06_controller = I06Apple2Controller( + apple2=mock_apple2, + gap_energy_motor_lut=mock_gap_energy_motor_lut, + phase_energy_motor_lut=mock_phase_energy_motor_lut, + inverse_gap_energy_motor_lut=mock_inverse_gap_energy_motor_lut, + ) return mock_i06_controller diff --git a/tests/devices/beamlines/i10_1/high_field_magnet/test_high_field_magnet.py b/tests/devices/beamlines/i10_1/high_field_magnet/test_high_field_magnet.py index 86f8fb89c09..b9395cf4ac3 100644 --- a/tests/devices/beamlines/i10_1/high_field_magnet/test_high_field_magnet.py +++ b/tests/devices/beamlines/i10_1/high_field_magnet/test_high_field_magnet.py @@ -9,6 +9,7 @@ from ophyd_async.core import ( DEFAULT_TIMEOUT, AsyncStatus, + get_mock_put, init_devices, set_mock_value, ) @@ -36,11 +37,9 @@ async def test_locate(high_field_magnet: HighFieldMagnet): async def test_stop_success(high_field_magnet: HighFieldMagnet): - set_mock_value(high_field_magnet.user_readback, 7.5) - set_mock_value(high_field_magnet.user_readback, 1.5) + initial_val = await high_field_magnet.user_readback.get_value() await high_field_magnet.stop() - assert high_field_magnet._set_success is False - assert await high_field_magnet.user_setpoint.get_value() == 1.5 + get_mock_put(high_field_magnet.user_setpoint).assert_awaited_once_with(initial_val) async def test_set_raises_runtime_error_when_stopped( @@ -151,15 +150,6 @@ async def test_read(high_field_magnet: HighFieldMagnet): ) -async def test_tolerance_logic_stop_clears_set_success_and_restores_setpoint( - high_field_magnet: HighFieldMagnet, -): - set_mock_value(high_field_magnet.movable_logic.readback, 1.5) - await high_field_magnet.stop() - assert high_field_magnet._set_success is False - assert await high_field_magnet.movable_logic.setpoint.get_value() == 1.5 - - async def test_tolerance_logic_calculate_timeout_with_zero_speed( high_field_magnet: HighFieldMagnet, ): diff --git a/tests/devices/beamlines/i22/test_dcm.py b/tests/devices/beamlines/i22/test_dcm.py index b262b655d32..23eff96ea56 100644 --- a/tests/devices/beamlines/i22/test_dcm.py +++ b/tests/devices/beamlines/i22/test_dcm.py @@ -95,6 +95,10 @@ async def test_configuration(dcm: DCM): "dcm-bragg_in_degrees-motor_egu": partial_reading(""), "dcm-bragg_in_degrees-offset": partial_reading(0.0), "dcm-bragg_in_degrees-velocity": partial_reading(1000.0), + "dcm-bragg_in_degrees-encoder_resolution": partial_reading(0), + "dcm-bragg_in_degrees-motor_resolution": partial_reading(0), + "dcm-bragg_in_degrees-steps_per_revolution": partial_reading(0), + "dcm-bragg_in_degrees-units_per_revolution": partial_reading(0), "dcm-crystal_1_d_spacing": partial_reading(0.31356), "dcm-crystal_1_reflection": partial_reading([1, 1, 1]), "dcm-crystal_1_type": partial_reading("silicon"), @@ -106,23 +110,51 @@ async def test_configuration(dcm: DCM): "dcm-energy_in_keV-motor_egu": partial_reading(""), "dcm-energy_in_keV-offset": partial_reading(0.0), "dcm-energy_in_keV-velocity": partial_reading(1000.0), + "dcm-energy_in_keV-encoder_resolution": partial_reading(0), + "dcm-energy_in_keV-motor_resolution": partial_reading(0), + "dcm-energy_in_keV-steps_per_revolution": partial_reading(0), + "dcm-energy_in_keV-units_per_revolution": partial_reading(0), "dcm-offset_in_mm-motor_egu": partial_reading(""), "dcm-offset_in_mm-offset": partial_reading(0.0), "dcm-offset_in_mm-velocity": partial_reading(1000.0), + "dcm-offset_in_mm-encoder_resolution": partial_reading(0), + "dcm-offset_in_mm-motor_resolution": partial_reading(0), + "dcm-offset_in_mm-steps_per_revolution": partial_reading(0), + "dcm-offset_in_mm-units_per_revolution": partial_reading(0), "dcm-perp-motor_egu": partial_reading(""), "dcm-perp-offset": partial_reading(0.0), "dcm-perp-velocity": partial_reading(1000.0), + "dcm-perp-encoder_resolution": partial_reading(0), + "dcm-perp-motor_resolution": partial_reading(0), + "dcm-perp-steps_per_revolution": partial_reading(0), + "dcm-perp-units_per_revolution": partial_reading(0), "dcm-wavelength_in_a-motor_egu": partial_reading(""), "dcm-wavelength_in_a-offset": partial_reading(0.0), "dcm-wavelength_in_a-velocity": partial_reading(1000.0), + "dcm-wavelength_in_a-encoder_resolution": partial_reading(0), + "dcm-wavelength_in_a-motor_resolution": partial_reading(0), + "dcm-wavelength_in_a-steps_per_revolution": partial_reading(0), + "dcm-wavelength_in_a-units_per_revolution": partial_reading(0), "dcm-xtal_1-roll_in_mrad-motor_egu": partial_reading(""), "dcm-xtal_1-roll_in_mrad-offset": partial_reading(0.0), "dcm-xtal_1-roll_in_mrad-velocity": partial_reading(1000.0), + "dcm-xtal_1-roll_in_mrad-encoder_resolution": partial_reading(0), + "dcm-xtal_1-roll_in_mrad-motor_resolution": partial_reading(0), + "dcm-xtal_1-roll_in_mrad-steps_per_revolution": partial_reading(0), + "dcm-xtal_1-roll_in_mrad-units_per_revolution": partial_reading(0), "dcm-xtal_2-pitch_in_mrad-motor_egu": partial_reading(""), "dcm-xtal_2-pitch_in_mrad-offset": partial_reading(0.0), "dcm-xtal_2-pitch_in_mrad-velocity": partial_reading(1000.0), + "dcm-xtal_2-pitch_in_mrad-encoder_resolution": partial_reading(0), + "dcm-xtal_2-pitch_in_mrad-motor_resolution": partial_reading(0), + "dcm-xtal_2-pitch_in_mrad-steps_per_revolution": partial_reading(0), + "dcm-xtal_2-pitch_in_mrad-units_per_revolution": partial_reading(0), "dcm-xtal_2-roll_in_mrad-motor_egu": partial_reading(""), "dcm-xtal_2-roll_in_mrad-offset": partial_reading(0.0), "dcm-xtal_2-roll_in_mrad-velocity": partial_reading(1000.0), + "dcm-xtal_2-roll_in_mrad-encoder_resolution": partial_reading(0), + "dcm-xtal_2-roll_in_mrad-motor_resolution": partial_reading(0), + "dcm-xtal_2-roll_in_mrad-steps_per_revolution": partial_reading(0), + "dcm-xtal_2-roll_in_mrad-units_per_revolution": partial_reading(0), }, ) diff --git a/tests/devices/insertion_device/test_apple2_controller.py b/tests/devices/insertion_device/test_apple2_controller.py index 9a7fae1fe2c..01d061171c6 100644 --- a/tests/devices/insertion_device/test_apple2_controller.py +++ b/tests/devices/insertion_device/test_apple2_controller.py @@ -135,12 +135,13 @@ async def mock_energy_readback_controller( configured_phase: float, configured_energy: float, ) -> DummyEnergyReadbackApple2Controller: - mock_locked_controller = DummyEnergyReadbackApple2Controller( - apple2=mock_locked_apple2, - gap_energy_motor_converter=lambda value, pol: configured_gap, - phase_energy_motor_converter=lambda value, pol: configured_phase, - inverse_gap_energy_motor_converter=lambda value, pol: configured_energy, - ) + with init_devices(mock=True): + mock_locked_controller = DummyEnergyReadbackApple2Controller( + apple2=mock_locked_apple2, + gap_energy_motor_converter=lambda value, pol: configured_gap, + phase_energy_motor_converter=lambda value, pol: configured_phase, + inverse_gap_energy_motor_converter=lambda value, pol: configured_energy, + ) return mock_locked_controller diff --git a/tests/devices/test_undulator.py b/tests/devices/test_undulator.py index c04e1fd4317..cdb69bbd223 100644 --- a/tests/devices/test_undulator.py +++ b/tests/devices/test_undulator.py @@ -76,6 +76,10 @@ async def test_undulator_mm_config_default_parameters(undulator_in_mm: Undulator "undulator_mm-gap_motor-motor_egu": partial_reading(""), "undulator_mm-gap_motor-offset": partial_reading(0.0), "undulator_mm-gap_motor-velocity": partial_reading(1000.0), + "undulator_mm-gap_motor-encoder_resolution": partial_reading(0), + "undulator_mm-gap_motor-motor_resolution": partial_reading(0), + "undulator_mm-gap_motor-steps_per_revolution": partial_reading(0), + "undulator_mm-gap_motor-units_per_revolution": partial_reading(0), }, ) @@ -104,10 +108,14 @@ async def test_configuration_includes_configuration_fields(undulator: UndulatorI { "undulator-gap_motor-motor_egu": partial_reading(""), "undulator-gap_motor-velocity": partial_reading(1000.0), + "undulator-gap_motor-encoder_resolution": partial_reading(0), + "undulator-gap_motor-offset": partial_reading(0.0), + "undulator-gap_motor-motor_resolution": partial_reading(0), + "undulator-gap_motor-steps_per_revolution": partial_reading(0), + "undulator-gap_motor-units_per_revolution": partial_reading(0), "undulator-length": partial_reading(2.0), "undulator-poles": partial_reading(80), "undulator-gap_discrepancy_tolerance_mm": partial_reading(0.002), - "undulator-gap_motor-offset": partial_reading(0.0), "undulator-undulator_period": partial_reading(27), }, ) From d5c165f21f4fb21bbc5aa4230bcb3e7ed5b8d380 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 17 Jul 2026 12:52:52 +0000 Subject: [PATCH 04/14] Fix apple2 mock_config:_client errors --- src/dodal/testing/fixtures/devices/apple2.py | 4 +++- tests/devices/test_undulator.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dodal/testing/fixtures/devices/apple2.py b/src/dodal/testing/fixtures/devices/apple2.py index ff7bc0d75de..9c44e1adb5a 100644 --- a/src/dodal/testing/fixtures/devices/apple2.py +++ b/src/dodal/testing/fixtures/devices/apple2.py @@ -26,7 +26,9 @@ def mock_config_client() -> ConfigClient: mock_config_client.get_file_contents = MagicMock(spec=["get_file_contents"]) - def my_side_effect(file_path, reset_cached_result) -> str: + def my_side_effect( + file_path, desired_return_type, reset_cached_result, force_parser + ) -> str: assert reset_cached_result is True with open(file_path) as f: return f.read() diff --git a/tests/devices/test_undulator.py b/tests/devices/test_undulator.py index cdb69bbd223..8f01fdd3631 100644 --- a/tests/devices/test_undulator.py +++ b/tests/devices/test_undulator.py @@ -108,8 +108,8 @@ async def test_configuration_includes_configuration_fields(undulator: UndulatorI { "undulator-gap_motor-motor_egu": partial_reading(""), "undulator-gap_motor-velocity": partial_reading(1000.0), - "undulator-gap_motor-encoder_resolution": partial_reading(0), "undulator-gap_motor-offset": partial_reading(0.0), + "undulator-gap_motor-encoder_resolution": partial_reading(0), "undulator-gap_motor-motor_resolution": partial_reading(0), "undulator-gap_motor-steps_per_revolution": partial_reading(0), "undulator-gap_motor-units_per_revolution": partial_reading(0), From 572edb6a9b8aaea0f643cba49a92bdccf9e194d8 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 17 Jul 2026 12:54:30 +0000 Subject: [PATCH 05/14] update comment --- tests/devices/beamlines/i06_shared/test_i06_apple2controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py b/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py index 8a2aa1c72b9..597d365c962 100644 --- a/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py +++ b/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py @@ -14,7 +14,7 @@ ) from dodal.devices.insertion_device.energy_motor_lookup import EnergyMotorLookup -# add mock_id_gap, mock_phase and mock_jaw_phase_axes to pytest. +# add mock_id_gap, mock_phase and mock_phase_axes pytest_plugins = ["dodal.testing.fixtures.devices.apple2"] From ab0dbcfb6435c213bc07febc2cd0313114f07ddf Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 17 Jul 2026 12:55:14 +0000 Subject: [PATCH 06/14] Remove unneeded connects --- tests/devices/insertion_device/test_apple_knot_undulator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/devices/insertion_device/test_apple_knot_undulator.py b/tests/devices/insertion_device/test_apple_knot_undulator.py index cbc67906e76..205a720c9e6 100644 --- a/tests/devices/insertion_device/test_apple_knot_undulator.py +++ b/tests/devices/insertion_device/test_apple_knot_undulator.py @@ -56,8 +56,6 @@ async def test_id_set_energy_const_pol( initial_phase_top_outer: float, expected_pol: Pol, ): - await mock_locked_apple2.connect(mock=True) - await mock_apple_knot_i05_controller.connect(mock=True) set_mock_value( mock_locked_apple2.phase().top_outer.user_readback, initial_phase_top_outer ) From 83d0d5b35bf710318c69edac54fbbbbd3712c4f2 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Fri, 17 Jul 2026 14:50:32 +0000 Subject: [PATCH 07/14] Fix fasteiger test --- .../plans/configure_arm_trigger_and_disarm_detector.py | 2 +- .../test_configure_arm_trigger_and_disarm_detector.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dodal/plans/configure_arm_trigger_and_disarm_detector.py b/src/dodal/plans/configure_arm_trigger_and_disarm_detector.py index 8691010812d..17f847b0184 100644 --- a/src/dodal/plans/configure_arm_trigger_and_disarm_detector.py +++ b/src/dodal/plans/configure_arm_trigger_and_disarm_detector.py @@ -54,7 +54,7 @@ def configure_arm_trigger_and_disarm_detector( yield from bps.kickoff(eiger, wait=True) LOGGER.info(f"Kickoff Eiger: {time.time() - start}s") start = time.time() - yield from bps.trigger(eiger.detector.trigger, wait=True) + yield from bps.trigger(eiger.detector.trigger_, wait=True) LOGGER.info(f"Triggering Eiger: {time.time() - start}s") start = time.time() yield from bps.complete(eiger, wait=True) diff --git a/tests/plans/test_configure_arm_trigger_and_disarm_detector.py b/tests/plans/test_configure_arm_trigger_and_disarm_detector.py index 17c8fdd6f0a..4dee443f930 100644 --- a/tests/plans/test_configure_arm_trigger_and_disarm_detector.py +++ b/tests/plans/test_configure_arm_trigger_and_disarm_detector.py @@ -8,6 +8,7 @@ TriggerInfo, callback_on_mock_execute, get_mock, + init_devices, set_mock_value, ) from ophyd_async.fastcs.eiger import EigerDetector as FastEiger @@ -21,8 +22,8 @@ @pytest.fixture async def fake_eiger() -> FastEiger: - fake_eiger = FastEiger("", MagicMock()) - await fake_eiger.connect(mock=True) + with init_devices(mock=True): + fake_eiger = FastEiger("", MagicMock()) set_mock_value(fake_eiger.detector.bit_depth_image, 32) return fake_eiger @@ -55,7 +56,7 @@ def set_detector_into_writing_state(*args, **kwargs) -> None: def set_frames_written(*args, **kwargs) -> None: set_mock_value(fake_eiger.od.fp.frames_written, 1) - callback_on_mock_execute(fake_eiger.detector.trigger, set_frames_written) + callback_on_mock_execute(fake_eiger.arm_when_ready, set_frames_written) run_engine( configure_arm_trigger_and_disarm_detector( From bdbeec81d14cb0cc05ba2aed9b2314dda2de9542 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Wed, 22 Jul 2026 13:24:55 +0000 Subject: [PATCH 08/14] Update to use 0.21 and use env variable --- pyproject.toml | 4 ++-- src/dodal/devices/cryostream.py | 6 +++--- src/dodal/testing/fixtures/devices/apple2.py | 2 +- tests/conftest.py | 5 +++++ uv.lock | 10 +++++----- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7b52b077fa3..c04ae8eecaa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ description = "Ophyd devices and other utils that could be used across DLS beaml dependencies = [ "click", "ophyd", - "ophyd-async[ca,pva]>=0.20.0", + "ophyd-async[ca,pva]>=0.20.1", "bluesky>=1.14.5", "pyepics", "pillow", @@ -51,7 +51,7 @@ dev = [ # Commented out due to dependency version conflict with pydantic 1.x # "copier", "myst-parser", - "ophyd_async[sim]>=0.20.0", + "ophyd_async[sim]>=0.20.1", "pre-commit", "psutil", "pydata-sphinx-theme>=0.12", diff --git a/src/dodal/devices/cryostream.py b/src/dodal/devices/cryostream.py index daaf254e329..ad8d5451ade 100644 --- a/src/dodal/devices/cryostream.py +++ b/src/dodal/devices/cryostream.py @@ -60,10 +60,10 @@ def __init__(self, prefix: str, name: str = ""): self.purge = epics_signal_x(f"{prefix}PURGE.PROC") self.hold = epics_signal_x(f"{prefix}HOLD.PROC") self.start = epics_signal_x(f"{prefix}RESTART.PROC") - self.pause = epics_signal_x(f"{prefix}PAUSE.PROC") - self.resume = epics_signal_x(f"{prefix}RESUME.PROC") + self.pause_ = epics_signal_x(f"{prefix}PAUSE.PROC") + self.resume_ = epics_signal_x(f"{prefix}RESUME.PROC") self.end = epics_signal_x(f"{prefix}END.PROC") - self.stop = epics_signal_x(f"{prefix}STOP.PROC") + self.stop_ = epics_signal_x(f"{prefix}STOP.PROC") self.plat = epics_signal_x(f"{prefix}PLAT.PROC") self.cool = epics_signal_x(f"{prefix}COOL.PROC") self.ramp = epics_signal_x(f"{prefix}RAMP.PROC") diff --git a/src/dodal/testing/fixtures/devices/apple2.py b/src/dodal/testing/fixtures/devices/apple2.py index 9c44e1adb5a..db19930a4f6 100644 --- a/src/dodal/testing/fixtures/devices/apple2.py +++ b/src/dodal/testing/fixtures/devices/apple2.py @@ -27,7 +27,7 @@ def mock_config_client() -> ConfigClient: mock_config_client.get_file_contents = MagicMock(spec=["get_file_contents"]) def my_side_effect( - file_path, desired_return_type, reset_cached_result, force_parser + file_path, desired_return_type=str, reset_cached_result=False, force_parser=None ) -> str: assert reset_cached_result is True with open(file_path) as f: diff --git a/tests/conftest.py b/tests/conftest.py index 7f4c6a77ce3..f02451cc962 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -68,6 +68,11 @@ ] +@pytest.fixture(autouse=True) +def _allowed_bluesky_attr(): + os.environ["OPHYD_ASYNC_ALLOW_RESERVED_ATTRS"] = "YES" + + @pytest.fixture(autouse=True) def reset_path_provider(): yield diff --git a/uv.lock b/uv.lock index 0a7bc2865bb..f3713a774cc 100644 --- a/uv.lock +++ b/uv.lock @@ -824,7 +824,7 @@ requires-dist = [ { name = "numpy" }, { name = "opencv-python-headless" }, { name = "ophyd" }, - { name = "ophyd-async", extras = ["ca", "pva"], specifier = ">=0.20.0" }, + { name = "ophyd-async", extras = ["ca", "pva"], specifier = ">=0.20.1" }, { name = "pillow" }, { name = "pydantic", specifier = ">=2.0" }, { name = "pyepics" }, @@ -843,7 +843,7 @@ dev = [ { name = "ispyb" }, { name = "mypy" }, { name = "myst-parser" }, - { name = "ophyd-async", extras = ["sim"], specifier = ">=0.20.0" }, + { name = "ophyd-async", extras = ["sim"], specifier = ">=0.20.1" }, { name = "pre-commit" }, { name = "psutil" }, { name = "pydata-sphinx-theme", specifier = ">=0.12" }, @@ -2256,7 +2256,7 @@ wheels = [ [[package]] name = "ophyd-async" -version = "0.20.0" +version = "0.20.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bluesky" }, @@ -2270,9 +2270,9 @@ dependencies = [ { name = "stamina" }, { name = "velocity-profile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/ee/0bc3ffcb7586c95be6d2d053ad7e1fb0f383e5e068bad9943bbc5bed885e/ophyd_async-0.20.0.tar.gz", hash = "sha256:2138489bb2543a08ca674cc86de243245209626886e502694c84ecdeeccf97ac", size = 652415, upload-time = "2026-07-17T08:50:46.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/e1/8faa491911abd3426273daf2249769ca54786ca2a70f928afa89dd75e226/ophyd_async-0.20.1.tar.gz", hash = "sha256:a98f502d8868e35ab593aef2819730f3263445a256b7729bf43aa791feae575e", size = 660613, upload-time = "2026-07-22T11:25:59.086Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/05/1001c7b589430a45219348c8c32622305304be2173b74d202fd596a39c3e/ophyd_async-0.20.0-py3-none-any.whl", hash = "sha256:3459199794587131bd03ee65b75122f6656fd7500d5fbf91cc5ecfd14c889ca3", size = 258059, upload-time = "2026-07-17T08:50:44.244Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a3/fead51df9547035d674b98c6c583064decd09a282ef2bb94122b254fac2d/ophyd_async-0.20.1-py3-none-any.whl", hash = "sha256:95f83ba007d33a86b4eb15b423deddc65dcd3f7b228b424523087c3b10eeb718", size = 255614, upload-time = "2026-07-22T11:25:57.768Z" }, ] [package.optional-dependencies] From 377f5d40cbeafec9bac5fa7242215a9fae1e6165 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Wed, 22 Jul 2026 13:30:06 +0000 Subject: [PATCH 09/14] Fix ConfigClient issue --- src/dodal/testing/fixtures/devices/apple2.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/dodal/testing/fixtures/devices/apple2.py b/src/dodal/testing/fixtures/devices/apple2.py index db19930a4f6..3d2e6244caf 100644 --- a/src/dodal/testing/fixtures/devices/apple2.py +++ b/src/dodal/testing/fixtures/devices/apple2.py @@ -1,5 +1,3 @@ -from unittest.mock import MagicMock - import pytest from daq_config_server import ConfigClient from ophyd_async.core import ( @@ -24,8 +22,6 @@ def mock_config_client() -> ConfigClient: mock_config_client = ConfigClient() - mock_config_client.get_file_contents = MagicMock(spec=["get_file_contents"]) - def my_side_effect( file_path, desired_return_type=str, reset_cached_result=False, force_parser=None ) -> str: @@ -33,7 +29,7 @@ def my_side_effect( with open(file_path) as f: return f.read() - mock_config_client.get_file_contents.side_effect = my_side_effect + mock_config_client.get_file_contents = my_side_effect # type: ignore return mock_config_client From b011accfce61969ebf8bcd54011db2d05595cbd7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 13:48:57 +0000 Subject: [PATCH 10/14] Bump ophyd-async pin to >=0.20.1 and fix set_mock_attr test failures ophyd-async 0.20.1 adds set_mock_attr (in ophyd_async.core) and enforces that test code can no longer assign directly to attribute names that collide with bluesky protocol verbs (set, read, trigger, kickoff, complete, prepare, describe, check_value). Replace direct assignments like `signal.set = AsyncMock(...)` with `set_mock_attr(signal, "set", AsyncMock(...))` across the affected test files and fixtures. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EQPRwf3pUved4ihrBLPi1g --- tests/devices/beamlines/i03/test_beamsize.py | 20 +++++++----- .../beamlines/i03/test_undulator_dcm.py | 32 +++++++++++++------ tests/devices/beamlines/i04/test_beamsize.py | 20 +++++++----- .../i06_shared/test_i06_apple2controller.py | 4 +-- .../beamlines/i09_2/test_i09_2_motors.py | 4 +-- .../devices/beamlines/i10/test_i10_apple2.py | 7 ++-- tests/devices/beamlines/i15_1/test_hexapod.py | 6 ++-- .../devices/beamlines/i17/test_i17_apple2.py | 4 +-- .../beamlines/i21/test_toolpoint_motion.py | 12 +++---- .../base/test_detector_logic.py | 3 +- .../test_apple2_controller.py | 3 +- .../insertion_device/test_apple2_undulator.py | 3 +- tests/devices/insertion_device/test_energy.py | 25 ++++++++------- .../insertion_device/test_polarisation.py | 4 +-- tests/devices/oav/conftest.py | 20 ++++++++---- tests/devices/oav/test_oav.py | 10 ++++-- tests/devices/test_attenuator.py | 17 +++++++--- tests/devices/test_bart_robot.py | 5 +-- tests/devices/test_common_mirrors.py | 11 +++++-- tests/devices/test_focusing_mirror.py | 3 +- tests/devices/test_gridscan.py | 3 +- tests/devices/test_smargon.py | 12 +++++-- tests/devices/test_zebra.py | 6 ++-- tests/devices/test_zocalo_results.py | 8 +++-- uv.lock | 10 ++++++ 25 files changed, 162 insertions(+), 90 deletions(-) diff --git a/tests/devices/beamlines/i03/test_beamsize.py b/tests/devices/beamlines/i03/test_beamsize.py index d76db1c5c7c..5f514fed357 100644 --- a/tests/devices/beamlines/i03/test_beamsize.py +++ b/tests/devices/beamlines/i03/test_beamsize.py @@ -2,7 +2,7 @@ from unittest.mock import AsyncMock import pytest -from ophyd_async.core import set_mock_value +from ophyd_async.core import set_mock_attr, set_mock_value from dodal.devices.aperturescatterguard import ( ApertureScatterguard, @@ -26,14 +26,18 @@ async def test_beamsize_gives_min_of_aperture_and_beam_width_and_height( ): set_mock_value(ap_sg.aperture.medium, 1) - ap_sg.diameter.read = AsyncMock( - return_value={ - "test_ap_sg-diameter": { - "value": aperture_diameter, - "timestamp": 1763051436.7372239, - "alarm_severity": 0, + set_mock_attr( + ap_sg.diameter, + "read", + AsyncMock( + return_value={ + "test_ap_sg-diameter": { + "value": aperture_diameter, + "timestamp": 1763051436.7372239, + "alarm_severity": 0, + } } - } + ), ) # see https://github.com/bluesky/ophyd-async/issues/1132 beamsize = Beamsize(aperture_scatterguard=ap_sg) diff --git a/tests/devices/beamlines/i03/test_undulator_dcm.py b/tests/devices/beamlines/i03/test_undulator_dcm.py index f94093dc46e..36e00fff54e 100644 --- a/tests/devices/beamlines/i03/test_undulator_dcm.py +++ b/tests/devices/beamlines/i03/test_undulator_dcm.py @@ -11,7 +11,13 @@ BeamlinePitchLookupTable, BeamlineRollLookupTable, ) -from ophyd_async.core import AsyncStatus, get_mock_put, init_devices, set_mock_value +from ophyd_async.core import ( + AsyncStatus, + get_mock_put, + init_devices, + set_mock_attr, + set_mock_value, +) from dodal.common.beamlines.beamline_utils import set_config_client from dodal.common.enums import EnabledDisabledUpper @@ -188,11 +194,15 @@ async def test_dcm_offset_only_set_when_energy_set_completes( release_dcm = asyncio.Event() release_undulator = asyncio.Event() - fake_undulator_dcm.dcm_ref().energy_in_keV.set = MagicMock( - return_value=AsyncStatus(release_dcm.wait()) + set_mock_attr( + fake_undulator_dcm.dcm_ref().energy_in_keV, + "set", + MagicMock(return_value=AsyncStatus(release_dcm.wait())), ) - fake_undulator_dcm.undulator_ref().gap_motor.set = MagicMock( - return_value=AsyncStatus(release_undulator.wait()) + set_mock_attr( + fake_undulator_dcm.undulator_ref().gap_motor, + "set", + MagicMock(return_value=AsyncStatus(release_undulator.wait())), ) offset_put = get_mock_put(fake_undulator_dcm.dcm_ref().offset_in_mm.user_setpoint) @@ -216,11 +226,15 @@ async def test_energy_set_only_complete_when_all_statuses_are_finished( release_dcm = asyncio.Event() release_undulator = asyncio.Event() - fake_undulator_dcm.dcm_ref().energy_in_keV.set = MagicMock( - return_value=AsyncStatus(release_dcm.wait()) + set_mock_attr( + fake_undulator_dcm.dcm_ref().energy_in_keV, + "set", + MagicMock(return_value=AsyncStatus(release_dcm.wait())), ) - fake_undulator_dcm.undulator_ref().gap_motor.set = MagicMock( - return_value=AsyncStatus(release_undulator.wait()) + set_mock_attr( + fake_undulator_dcm.undulator_ref().gap_motor, + "set", + MagicMock(return_value=AsyncStatus(release_undulator.wait())), ) status = fake_undulator_dcm.set(5.0) diff --git a/tests/devices/beamlines/i04/test_beamsize.py b/tests/devices/beamlines/i04/test_beamsize.py index 8f2916003dd..7eb25ee5ca3 100644 --- a/tests/devices/beamlines/i04/test_beamsize.py +++ b/tests/devices/beamlines/i04/test_beamsize.py @@ -2,7 +2,7 @@ from unittest.mock import AsyncMock import pytest -from ophyd_async.core import set_mock_value +from ophyd_async.core import set_mock_attr, set_mock_value from dodal.devices.aperturescatterguard import ( ApertureScatterguard, @@ -30,14 +30,18 @@ async def test_beamsize_gives_min_of_aperture_and_transfocator_width_and_height( ): set_mock_value(ap_sg.aperture.medium, 1) - ap_sg.diameter.read = AsyncMock( - return_value={ - "test_ap_sg-diameter": { - "value": aperture_diameter, - "timestamp": 1763051436.7372239, - "alarm_severity": 0, + set_mock_attr( + ap_sg.diameter, + "read", + AsyncMock( + return_value={ + "test_ap_sg-diameter": { + "value": aperture_diameter, + "timestamp": 1763051436.7372239, + "alarm_severity": 0, + } } - } + ), ) # see https://github.com/bluesky/ophyd-async/issues/1132 set_mock_value(fake_transfocator.current_horizontal_size_rbv, transfocator_sizes[0]) diff --git a/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py b/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py index 597d365c962..d6186a84248 100644 --- a/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py +++ b/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py @@ -1,7 +1,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest -from ophyd_async.core import init_devices +from ophyd_async.core import init_devices, set_mock_attr from dodal.devices.beamlines.i06_shared import I06Apple2Controller from dodal.devices.insertion_device import ( @@ -56,7 +56,7 @@ async def test_set_motors_from_energy_and_polarisation_sets_correct_values( mock_i06_controller: I06Apple2Controller, mock_apple2: Apple2[UndulatorPhaseAxes], ): - mock_apple2.set = AsyncMock() + set_mock_attr(mock_apple2, "set", AsyncMock()) # Mock polarisation setpoint check mock_i06_controller._check_and_get_pol_setpoint = AsyncMock(return_value=Pol.LH) await mock_i06_controller.energy.set(100.0) diff --git a/tests/devices/beamlines/i09_2/test_i09_2_motors.py b/tests/devices/beamlines/i09_2/test_i09_2_motors.py index e90e566f812..7436c20d822 100644 --- a/tests/devices/beamlines/i09_2/test_i09_2_motors.py +++ b/tests/devices/beamlines/i09_2/test_i09_2_motors.py @@ -1,7 +1,7 @@ from unittest.mock import AsyncMock import pytest -from ophyd_async.core import init_devices +from ophyd_async.core import init_devices, set_mock_attr from ophyd_async.testing import assert_reading, partial_reading from dodal.devices.beamlines.i09_2 import I092SampleManipulator, PiezoElectricMotor @@ -38,7 +38,7 @@ async def piezo_motor() -> PiezoElectricMotor: async def test_piezo_motor_stop(piezo_motor: PiezoElectricMotor) -> None: - piezo_motor.motor_stop.set = AsyncMock() + set_mock_attr(piezo_motor.motor_stop, "set", AsyncMock()) await piezo_motor.stop() piezo_motor.motor_stop.set.assert_awaited_once_with(1) diff --git a/tests/devices/beamlines/i10/test_i10_apple2.py b/tests/devices/beamlines/i10/test_i10_apple2.py index 35a2465cb87..8a0251a0d19 100644 --- a/tests/devices/beamlines/i10/test_i10_apple2.py +++ b/tests/devices/beamlines/i10/test_i10_apple2.py @@ -12,6 +12,7 @@ callback_on_mock_put, get_mock_put, init_devices, + set_mock_attr, set_mock_value, ) from ophyd_async.testing import assert_emitted @@ -266,7 +267,7 @@ async def test_fail_i10_apple2_controller_set_energy_has_default( mock_id_energy: InsertionDeviceEnergy, mock_id_controller: I10Apple2Controller, ): - mock_id_controller.energy.set = AsyncMock() + set_mock_attr(mock_id_controller.energy, "set", AsyncMock()) await mock_id_energy.set(600) mock_id_controller.energy.set.assert_awaited_once_with( 600, timeout=MAXIMUM_MOVE_TIME @@ -389,7 +390,7 @@ async def test_id_polarisation_set_has_default_timeout( mock_id_controller: I10Apple2Controller, ): set_mock_value(mock_id_controller._energy, 700) - mock_id_controller.polarisation.set = AsyncMock() + set_mock_attr(mock_id_controller.polarisation, "set", AsyncMock()) await mock_id_pol.set(Pol.LV) mock_id_controller.polarisation.set.assert_awaited_once_with( Pol.LV, timeout=MAXIMUM_MOVE_TIME @@ -539,7 +540,7 @@ async def test_linear_arbitrary_pol_set_default_timeout( mock_linear_arbitrary_angle: LinearArbitraryAngle, mock_id_controller: I10Apple2Controller, ): - mock_id_controller.linear_arbitrary_angle.set = AsyncMock() + set_mock_attr(mock_id_controller.linear_arbitrary_angle, "set", AsyncMock()) await mock_linear_arbitrary_angle.set(60) mock_id_controller.linear_arbitrary_angle.set.assert_awaited_once_with( 60, timeout=MAXIMUM_MOVE_TIME diff --git a/tests/devices/beamlines/i15_1/test_hexapod.py b/tests/devices/beamlines/i15_1/test_hexapod.py index b7f751fe170..b4a4aa70dbd 100644 --- a/tests/devices/beamlines/i15_1/test_hexapod.py +++ b/tests/devices/beamlines/i15_1/test_hexapod.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock, call import pytest -from ophyd_async.core import get_mock_put, init_devices, set_mock_value +from ophyd_async.core import get_mock_put, init_devices, set_mock_attr, set_mock_value from ophyd_async.epics.motor import MotorLimitsError from dodal.devices.beamlines.i15_1.hexapod import CombinedMove, Hexapod @@ -126,7 +126,7 @@ async def test_given_set_with_all_values_then_motors_set_in_order(hexapod: Hexap async def test_given_set_fails_then_defer_moves_turned_back_off(hexapod: Hexapod): class MyError(Exception): ... - hexapod.x.user_setpoint.set = MagicMock(side_effect=MyError()) + set_mock_attr(hexapod.x.user_setpoint, "set", MagicMock(side_effect=MyError())) with pytest.raises(MyError): await hexapod.set(CombinedMove(x=10)) @@ -141,7 +141,7 @@ async def test_given_motor_does_not_change_setpoint_then_deferred_move_times_out hexapod.DEFERRED_MOVE_SET_TIMEOUT = 0.01 # type: ignore # Override the callback so it doesn't change the `user_setpoint` - hexapod.x.user_setpoint.set = MagicMock() + set_mock_attr(hexapod.x.user_setpoint, "set", MagicMock()) with pytest.raises(TimeoutError): await hexapod.set(CombinedMove(x=10)) diff --git a/tests/devices/beamlines/i17/test_i17_apple2.py b/tests/devices/beamlines/i17/test_i17_apple2.py index 40473b54d5d..81d2cf70e92 100644 --- a/tests/devices/beamlines/i17/test_i17_apple2.py +++ b/tests/devices/beamlines/i17/test_i17_apple2.py @@ -1,7 +1,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest -from ophyd_async.core import init_devices +from ophyd_async.core import init_devices, set_mock_attr from dodal.devices.beamlines.i17.i17_apple2 import I17Apple2Controller from dodal.devices.insertion_device import ( @@ -48,7 +48,7 @@ async def test_set_motors_from_energy_and_polarisation_sets_correct_values( mock_id_controller: I17Apple2Controller, mock_apple2: Apple2[UndulatorPhaseAxes], ): - mock_apple2.set = AsyncMock() + set_mock_attr(mock_apple2, "set", AsyncMock()) # Mock polarisation setpoint check mock_id_controller._check_and_get_pol_setpoint = AsyncMock(return_value=Pol.LH) await mock_id_controller.energy.set(100.0) diff --git a/tests/devices/beamlines/i21/test_toolpoint_motion.py b/tests/devices/beamlines/i21/test_toolpoint_motion.py index be6991bf495..b8d0f31d410 100644 --- a/tests/devices/beamlines/i21/test_toolpoint_motion.py +++ b/tests/devices/beamlines/i21/test_toolpoint_motion.py @@ -5,7 +5,7 @@ import numpy as np import pytest -from ophyd_async.core import init_devices, set_mock_value +from ophyd_async.core import init_devices, set_mock_attr, set_mock_value from ophyd_async.epics.motor import MotorLimitsError from ophyd_async.testing import assert_reading, partial_reading @@ -199,11 +199,11 @@ async def test_uvw_check_value_calls_all_motors( uvw: ToolPointMotion, ) -> None: smp = uvw.smp_ref() - smp.x.check_value = AsyncMock() - smp.y.check_value = AsyncMock() - smp.z.check_value = AsyncMock() - smp.tilt.check_value = AsyncMock() - smp.azimuth.check_value = AsyncMock() + set_mock_attr(smp.x, "check_value", AsyncMock()) + set_mock_attr(smp.y, "check_value", AsyncMock()) + set_mock_attr(smp.z, "check_value", AsyncMock()) + set_mock_attr(smp.tilt, "check_value", AsyncMock()) + set_mock_attr(smp.azimuth, "check_value", AsyncMock()) values = XYZTiltAzimuthMotorPositions( x=1.0, y=2.0, z=3.0, tilt_deg=10.0, azimuth_deg=20.0 diff --git a/tests/devices/electron_analyser/base/test_detector_logic.py b/tests/devices/electron_analyser/base/test_detector_logic.py index 2a1b6bc657c..2634fd7ba7e 100644 --- a/tests/devices/electron_analyser/base/test_detector_logic.py +++ b/tests/devices/electron_analyser/base/test_detector_logic.py @@ -9,6 +9,7 @@ StandardDetector, get_mock_put, init_devices, + set_mock_attr, soft_signal_rw, ) from ophyd_async.epics.adcore import ADImageMode @@ -139,7 +140,7 @@ async def test_region_logic_setup_with_region_sets_region_for_epics_and_sets_dri region: BaseRegion, region_logic: RegionLogic, ) -> None: - region_logic.driver.set = AsyncMock() + set_mock_attr(region_logic.driver, "set", AsyncMock()) # Patch switch_energy_mode so we can check on calls, but still run the real function with patch.object( diff --git a/tests/devices/insertion_device/test_apple2_controller.py b/tests/devices/insertion_device/test_apple2_controller.py index 01d061171c6..bc6e91d3343 100644 --- a/tests/devices/insertion_device/test_apple2_controller.py +++ b/tests/devices/insertion_device/test_apple2_controller.py @@ -3,6 +3,7 @@ import pytest from ophyd_async.core import ( init_devices, + set_mock_attr, set_mock_value, ) @@ -184,7 +185,7 @@ async def test_id_controller_energy_sets_correct_values( configured_gap: float, configured_phase: float, ): - mock_locked_apple2.set = AsyncMock() + set_mock_attr(mock_locked_apple2, "set", AsyncMock()) mock_locked_controller._check_and_get_pol_setpoint = AsyncMock(return_value=Pol.LH) await mock_locked_controller.energy.set(100.0) expected_val = Apple2Val( diff --git a/tests/devices/insertion_device/test_apple2_undulator.py b/tests/devices/insertion_device/test_apple2_undulator.py index 66c537be959..200456a2bdd 100644 --- a/tests/devices/insertion_device/test_apple2_undulator.py +++ b/tests/devices/insertion_device/test_apple2_undulator.py @@ -10,6 +10,7 @@ callback_on_mock_put, get_mock_put, init_devices, + set_mock_attr, set_mock_value, ) from ophyd_async.testing import ( @@ -461,7 +462,7 @@ async def test_id_controller_energy_sets_correct_values( configured_gap: float, configured_phase: float, ): - mock_locked_apple2.set = AsyncMock() + set_mock_attr(mock_locked_apple2, "set", AsyncMock()) mock_locked_controller._check_and_get_pol_setpoint = AsyncMock(return_value=Pol.LH) await mock_locked_controller.energy.set(100.0) expected_val = Apple2Val( diff --git a/tests/devices/insertion_device/test_energy.py b/tests/devices/insertion_device/test_energy.py index 1afd2421eef..fb5442cc249 100644 --- a/tests/devices/insertion_device/test_energy.py +++ b/tests/devices/insertion_device/test_energy.py @@ -9,6 +9,7 @@ FlyMotorInfo, get_mock_put, init_devices, + set_mock_attr, set_mock_value, soft_signal_rw, wait_for_value, @@ -43,8 +44,8 @@ async def test_mock_beam_controller_set_moves_both_devices( mock_id_energy: InsertionDeviceEnergy, mock_pgm: PlaneGratingMonochromator, ): - mock_id_energy.set = AsyncMock() - mock_pgm.energy.set = AsyncMock() + set_mock_attr(mock_id_energy, "set", AsyncMock()) + set_mock_attr(mock_pgm.energy, "set", AsyncMock()) await mock_beam_energy.set(100.0) @@ -56,7 +57,7 @@ async def test_insertion_device_energy_set( mock_id_energy: InsertionDeviceEnergy, mock_id_controller: DummyApple2Controller, ): - mock_id_controller.energy.set = AsyncMock() + set_mock_attr(mock_id_controller.energy, "set", AsyncMock()) await mock_id_energy.set(1500.0) @@ -89,7 +90,7 @@ async def test_insertion_device_energy_prepare_success( mock_id_controller.apple2().gap().acceleration_time, acceleration_time ) mock_id_controller._polarisation_setpoint_set(Pol.LH) - mock_id_energy.set = AsyncMock() + set_mock_attr(mock_id_energy, "set", AsyncMock()) mid_gap_position = end_gap + start_gap / 2.0 mock_id_controller.gap_energy_motor_converter = Mock( side_effect=[start_gap, end_gap, mid_gap_position] @@ -112,7 +113,7 @@ async def test_insertion_deviceenergy_kickoff_call_gap_kickoff( mock_id_energy: InsertionDeviceEnergy, mock_id_gap: UndulatorGap, ): - mock_id_gap.kickoff = AsyncMock() + set_mock_attr(mock_id_gap, "kickoff", AsyncMock()) await mock_id_energy.kickoff() mock_id_gap.kickoff.assert_awaited_once() @@ -121,7 +122,7 @@ def test_insertion_device_energy_complete_call_gap_complete( mock_id_energy: InsertionDeviceEnergy, mock_id_gap: UndulatorGap, ): - mock_id_gap.complete = MagicMock() + set_mock_attr(mock_id_gap, "complete", MagicMock()) mock_id_energy.complete() mock_id_gap.complete.assert_called_once() @@ -144,8 +145,8 @@ async def test_beam_energy_prepare_success( mock_id_energy: InsertionDeviceEnergy, ): fly_info = FlyMotorInfo(start_position=700, end_position=800, time_for_move=10) - mock_id_energy.prepare = AsyncMock() - mock_pgm.energy.prepare = AsyncMock() + set_mock_attr(mock_id_energy, "prepare", AsyncMock()) + set_mock_attr(mock_pgm.energy, "prepare", AsyncMock()) run_engine(prepare(mock_beam_energy, fly_info)) mock_id_energy.prepare.assert_awaited_once_with(fly_info) mock_pgm.energy.prepare.assert_awaited_once_with(fly_info) @@ -174,8 +175,8 @@ async def test_beam_energy_kickoff_set_correct_delay( set_mock_value(mock_pgm.energy.high_limit_travel, 1000) set_mock_value(mock_pgm.energy.acceleration_time, pgm_acc_time) set_mock_value(mock_id_gap.gate, UndulatorGateStatus.CLOSE) - mock_id_gap.kickoff = AsyncMock() - mock_pgm.energy.kickoff = AsyncMock() + set_mock_attr(mock_id_gap, "kickoff", AsyncMock()) + set_mock_attr(mock_pgm.energy, "kickoff", AsyncMock()) await mock_beam_energy.prepare(fly_info) await mock_beam_energy.kickoff() mock_sleep.assert_called_with(pgm_acc_time - id_acc_time) @@ -204,8 +205,8 @@ async def get_status(signal): mock_id_gap._fly_status = get_status(fake_id_fly_status) # type: ignore mock_pgm.energy._fly_status = get_status(fake_pgm_fly_status) # type: ignore - mock_id_energy.kickoff = AsyncMock() - mock_pgm.energy.kickoff = AsyncMock() + set_mock_attr(mock_id_energy, "kickoff", AsyncMock()) + set_mock_attr(mock_pgm.energy, "kickoff", AsyncMock()) pgm_status = mock_pgm.energy.complete() id_status = mock_id_gap.complete() assert not id_status.done diff --git a/tests/devices/insertion_device/test_polarisation.py b/tests/devices/insertion_device/test_polarisation.py index 3709ed22dfe..22c62ec4605 100644 --- a/tests/devices/insertion_device/test_polarisation.py +++ b/tests/devices/insertion_device/test_polarisation.py @@ -2,7 +2,7 @@ import pytest from bluesky.protocols import Location -from ophyd_async.core import init_devices +from ophyd_async.core import init_devices, set_mock_attr from dodal.devices.insertion_device import ( MAXIMUM_MOVE_TIME, @@ -29,7 +29,7 @@ async def test_polarisation_set_calls_controller_methods( mock_id_controller: DummyApple2Controller, mock_id_polarisation: InsertionDevicePolarisation, ): - mock_id_controller.polarisation.set = AsyncMock() + set_mock_attr(mock_id_controller.polarisation, "set", AsyncMock()) pol = Pol.PC await mock_id_polarisation.set(pol=pol) mock_id_controller.polarisation.set.assert_called_once_with( diff --git a/tests/devices/oav/conftest.py b/tests/devices/oav/conftest.py index 8da3a5512f5..dc705050257 100644 --- a/tests/devices/oav/conftest.py +++ b/tests/devices/oav/conftest.py @@ -2,7 +2,7 @@ import pytest from daq_config_server import ConfigClient -from ophyd_async.core import init_devices, set_mock_value +from ophyd_async.core import init_devices, set_mock_attr, set_mock_value from dodal.devices.oav.oav_detector import OAVBeamCentreFile, OAVBeamCentrePV from dodal.devices.oav.oav_parameters import OAVConfig, OAVConfigBeamCentre @@ -17,8 +17,10 @@ async def oav() -> OAVBeamCentreFile: async with init_devices(mock=True, connect=True): oav = OAVBeamCentreFile("", config=oav_config, name="oav") zoom_levels_list = ["1.0x", "3.0x", "5.0x", "7.5x", "10.0x"] - oav.zoom_controller.level.describe = AsyncMock( - return_value={"level": {"choices": zoom_levels_list}} + set_mock_attr( + oav.zoom_controller.level, + "describe", + AsyncMock(return_value={"level": {"choices": zoom_levels_list}}), ) set_mock_value(oav.grid_snapshot.x_size, 1024) set_mock_value(oav.grid_snapshot.y_size, 768) @@ -32,8 +34,10 @@ async def oav_beam_centre_pv_roi() -> OAVBeamCentrePV: async with init_devices(mock=True, connect=True): oav = OAVBeamCentrePV("", config=oav_config, name="oav") zoom_levels_list = ["1.0x", "3.0x", "5.0x", "7.5x", "10.0x"] - oav.zoom_controller.level.describe = AsyncMock( - return_value={"level": {"choices": zoom_levels_list}} + set_mock_attr( + oav.zoom_controller.level, + "describe", + AsyncMock(return_value={"level": {"choices": zoom_levels_list}}), ) set_mock_value(oav.grid_snapshot.x_size, 1024) set_mock_value(oav.grid_snapshot.y_size, 768) @@ -49,8 +53,10 @@ async def oav_beam_centre_pv_fs() -> OAVBeamCentrePV: "", config=oav_config, name="oav", mjpeg_prefix="XTAL", overlay_channel=3 ) zoom_levels_list = ["1.0x", "3.0x", "5.0x", "7.5x", "10.0x"] - oav.zoom_controller.level.describe = AsyncMock( - return_value={"level": {"choices": zoom_levels_list}} + set_mock_attr( + oav.zoom_controller.level, + "describe", + AsyncMock(return_value={"level": {"choices": zoom_levels_list}}), ) set_mock_value(oav.grid_snapshot.x_size, 1024) set_mock_value(oav.grid_snapshot.y_size, 768) diff --git a/tests/devices/oav/test_oav.py b/tests/devices/oav/test_oav.py index 5c4987e2c63..1c2bfa346e5 100644 --- a/tests/devices/oav/test_oav.py +++ b/tests/devices/oav/test_oav.py @@ -2,7 +2,7 @@ import pytest from daq_config_server import ConfigClient -from ophyd_async.core import init_devices, set_mock_value +from ophyd_async.core import init_devices, set_mock_attr, set_mock_value from dodal.devices.oav.oav_detector import ( OAV, @@ -24,8 +24,12 @@ def null_controller() -> NullZoomController: async def test_zoom_controller(): zoom_controller = ZoomController("", "zoom_controller") await zoom_controller.connect(mock=True) - zoom_controller.level.describe = AsyncMock( - return_value={"zoom_controller-level": {"choices": ["1.0x", "3.0x"]}} + set_mock_attr( + zoom_controller.level, + "describe", + AsyncMock( + return_value={"zoom_controller-level": {"choices": ["1.0x", "3.0x"]}} + ), ) status = zoom_controller.set("3.0x") await status diff --git a/tests/devices/test_attenuator.py b/tests/devices/test_attenuator.py index b506c214237..c7cc2b630f3 100644 --- a/tests/devices/test_attenuator.py +++ b/tests/devices/test_attenuator.py @@ -4,7 +4,12 @@ import pytest from bluesky import plan_stubs as bps from bluesky.run_engine import RunEngine -from ophyd_async.core import callback_on_mock_put, init_devices, set_mock_value +from ophyd_async.core import ( + callback_on_mock_put, + init_devices, + set_mock_attr, + set_mock_value, +) from dodal.devices.attenuator.attenuator import ( BinaryFilterAttenuator, @@ -92,10 +97,12 @@ async def _move_filter_after_short_delay(_): await asyncio.sleep(MOCK_TIMEOUT_S / 2) await attenuator._filters[0].done_move.set(0) - attenuator._auto_move_on_desired_transmission_set.set = AsyncMock() - attenuator._use_current_energy.trigger = AsyncMock() - attenuator._desired_transmission.set = AsyncMock( - side_effect=_move_filter_after_short_delay + set_mock_attr(attenuator._auto_move_on_desired_transmission_set, "set", AsyncMock()) + set_mock_attr(attenuator._use_current_energy, "trigger", AsyncMock()) + set_mock_attr( + attenuator._desired_transmission, + "set", + AsyncMock(side_effect=_move_filter_after_short_delay), ) status = attenuator.set(0.2) await asyncio.sleep(MOCK_TIMEOUT_S) diff --git a/tests/devices/test_bart_robot.py b/tests/devices/test_bart_robot.py index bbe9dbe0d68..204eb6a0b1f 100644 --- a/tests/devices/test_bart_robot.py +++ b/tests/devices/test_bart_robot.py @@ -10,6 +10,7 @@ callback_on_mock_put, get_mock, get_mock_put, + set_mock_attr, set_mock_value, ) @@ -152,7 +153,7 @@ async def test_given_program_not_running_but_pin_not_unmounting_when_load_pin_th device = bart_robot _set_fast_robot_timeouts(device) set_mock_value(device.gonio_pin_sensor, PinMounted.PIN_MOUNTED) - device.load.trigger = AsyncMock(side_effect=device.load.trigger) + set_mock_attr(device.load, "trigger", AsyncMock(side_effect=device.load.trigger)) with pytest.raises(RobotLoadError): await device.set(SampleLocation(15, 10)) device.load.trigger.assert_called_once() @@ -168,7 +169,7 @@ async def test_given_program_not_running_and_pin_unmounting_but_new_pin_not_moun device = bart_robot _set_fast_robot_timeouts(device) set_mock_value(device.gonio_pin_sensor, PinMounted.NO_PIN_MOUNTED) - device.load.trigger = AsyncMock(side_effect=device.load.trigger) + set_mock_attr(device.load, "trigger", AsyncMock(side_effect=device.load.trigger)) with pytest.raises(RobotLoadError) as exc_info: await device.set(SampleLocation(15, 10)) diff --git a/tests/devices/test_common_mirrors.py b/tests/devices/test_common_mirrors.py index 73e200ad476..df5dffb5d06 100644 --- a/tests/devices/test_common_mirrors.py +++ b/tests/devices/test_common_mirrors.py @@ -4,7 +4,12 @@ from bluesky.plan_stubs import mv, stop from bluesky.protocols import Location from bluesky.run_engine import RunEngine -from ophyd_async.core import StrictEnum, init_devices, set_mock_value +from ophyd_async.core import ( + StrictEnum, + init_devices, + set_mock_attr, + set_mock_value, +) from ophyd_async.testing import assert_reading, partial_reading from dodal.devices.common_mirror import ( @@ -152,7 +157,7 @@ async def test_stop_xyz_switching_mirror( xyz_switching_mirror: XYZSwitchingMirror, run_engine: RunEngine, ): - xyz_switching_mirror.mirror_abort.trigger = AsyncMock() + set_mock_attr(xyz_switching_mirror.mirror_abort, "trigger", AsyncMock()) run_engine(stop(xyz_switching_mirror)) xyz_switching_mirror.mirror_abort.trigger.assert_awaited_once() @@ -228,6 +233,6 @@ async def test_stop_xyz_piezo_switching_mirror( xyz_piezo_switching_mirror: XYZPiezoSwitchingMirror, run_engine: RunEngine, ): - xyz_piezo_switching_mirror.mirror_abort.trigger = AsyncMock() + set_mock_attr(xyz_piezo_switching_mirror.mirror_abort, "trigger", AsyncMock()) run_engine(stop(xyz_piezo_switching_mirror)) xyz_piezo_switching_mirror.mirror_abort.trigger.assert_awaited_once() diff --git a/tests/devices/test_focusing_mirror.py b/tests/devices/test_focusing_mirror.py index feb79c6f21c..697d9583e4d 100644 --- a/tests/devices/test_focusing_mirror.py +++ b/tests/devices/test_focusing_mirror.py @@ -11,6 +11,7 @@ callback_on_mock_put, get_mock_put, init_devices, + set_mock_attr, set_mock_value, ) @@ -181,7 +182,7 @@ def test_mirror_set_voltage_sets_and_waits_set_fail( def failed(*args, **kwargs): raise AssertionError("Test Failure") - mirror_voltage_with_set._setpoint_v.set = failed + set_mock_attr(mirror_voltage_with_set._setpoint_v, "set", failed) def plan(): with pytest.raises(FailedStatus) as e: diff --git a/tests/devices/test_gridscan.py b/tests/devices/test_gridscan.py index 9addb9003ef..7e20c4d5bf6 100644 --- a/tests/devices/test_gridscan.py +++ b/tests/devices/test_gridscan.py @@ -15,6 +15,7 @@ callback_on_mock_put, get_mock_put, init_devices, + set_mock_attr, set_mock_put_proceeds, set_mock_value, ) @@ -459,7 +460,7 @@ async def test_timeout_on_complete_triggers_stop_and_logs_error( zebra_fast_grid_scan: ZebraFastGridScanThreeD, ): zebra_fast_grid_scan.COMPLETE_STATUS = 0.01 - zebra_fast_grid_scan.stop_cmd.trigger = AsyncMock() + set_mock_attr(zebra_fast_grid_scan.stop_cmd, "trigger", AsyncMock()) set_mock_value(zebra_fast_grid_scan.status, 1) with pytest.raises(TimeoutError): await zebra_fast_grid_scan.complete() diff --git a/tests/devices/test_smargon.py b/tests/devices/test_smargon.py index e7d273cfbf6..5ebba27d137 100644 --- a/tests/devices/test_smargon.py +++ b/tests/devices/test_smargon.py @@ -4,7 +4,13 @@ import pytest from bluesky import plan_stubs as bps from bluesky.run_engine import RunEngine -from ophyd_async.core import get_mock_put, init_devices, observe_value, set_mock_value +from ophyd_async.core import ( + get_mock_put, + init_devices, + observe_value, + set_mock_attr, + set_mock_value, +) from ophyd_async.epics.motor import MotorLimitsError from dodal.devices.smargon import CombinedMove, DeferMoves, Smargon, StubPosition @@ -188,7 +194,7 @@ async def test_given_set_with_all_values_then_motors_set_in_order(smargon: Smarg async def test_given_set_fails_then_defer_moves_turned_back_off(smargon: Smargon): class MyError(Exception): ... - smargon.x.user_setpoint.set = MagicMock(side_effect=MyError()) + set_mock_attr(smargon.x.user_setpoint, "set", MagicMock(side_effect=MyError())) with pytest.raises(MyError): await smargon.set(CombinedMove(x=10)) @@ -203,7 +209,7 @@ async def test_given_motor_does_not_change_setpoint_then_deferred_move_times_out smargon.DEFERRED_MOVE_SET_TIMEOUT = 0.01 # type: ignore # Override the callback so it doesn't change the `user_setpoint` - smargon.x.user_setpoint.set = MagicMock() + set_mock_attr(smargon.x.user_setpoint, "set", MagicMock()) with pytest.raises(TimeoutError): await smargon.set(CombinedMove(x=10)) diff --git a/tests/devices/test_zebra.py b/tests/devices/test_zebra.py index edcc9d20822..fa8420fc1e8 100644 --- a/tests/devices/test_zebra.py +++ b/tests/devices/test_zebra.py @@ -1,7 +1,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest -from ophyd_async.core import set_mock_value +from ophyd_async.core import set_mock_attr, set_mock_value from dodal.devices.zebra.zebra import ( ArmDemand, @@ -36,8 +36,8 @@ async def mock_arm(demand): set_mock_value(fake_pc.arm.disarm_set, not demand) set_mock_value(fake_pc.arm.arm_set, demand) - fake_pc.arm.arm_set.set = AsyncMock(side_effect=mock_arm) - fake_pc.arm.disarm_set.set = AsyncMock(side_effect=mock_arm) + set_mock_attr(fake_pc.arm.arm_set, "set", AsyncMock(side_effect=mock_arm)) + set_mock_attr(fake_pc.arm.disarm_set, "set", AsyncMock(side_effect=mock_arm)) await fake_pc.gate_source.set(TrigSource.EXTERNAL) await fake_pc.gate_trigger.set(I03Axes.OMEGA) diff --git a/tests/devices/test_zocalo_results.py b/tests/devices/test_zocalo_results.py index e719ea6c6ca..d1cc3366d59 100644 --- a/tests/devices/test_zocalo_results.py +++ b/tests/devices/test_zocalo_results.py @@ -7,7 +7,7 @@ import pytest from bluesky.run_engine import RunEngine from bluesky.utils import FailedStatus -from ophyd_async.core import AsyncStatus +from ophyd_async.core import AsyncStatus, set_mock_attr from dodal.devices.zocalo.zocalo_constants import ZOCALO_ENV from dodal.devices.zocalo.zocalo_results import ( @@ -96,7 +96,11 @@ async def device(results): async def mock_trigger(results): await zocalo_results._put_results(results, test_recipe_parameters) - zocalo_results.trigger = MagicMock(side_effect=partial(mock_trigger, results)) # type: ignore + set_mock_attr( + zocalo_results, + "trigger", + MagicMock(side_effect=partial(mock_trigger, results)), + ) await zocalo_results.connect() return zocalo_results diff --git a/uv.lock b/uv.lock index 5406f354cd4..54177cd3742 100644 --- a/uv.lock +++ b/uv.lock @@ -1109,7 +1109,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/c6/dba32cab7e3a625b011aa5647486e2d28423a48845a2998c126dd69c85e1/greenlet-3.4.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:805bebb4945094acbab757d34d6e1098be6de8966009ab9ca54f06ff492def58", size = 285504, upload-time = "2026-04-08T15:52:14.071Z" }, { url = "https://files.pythonhosted.org/packages/54/f4/7cb5c2b1feb9a1f50e038be79980dfa969aa91979e5e3a18fdbcfad2c517/greenlet-3.4.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:439fc2f12b9b512d9dfa681c5afe5f6b3232c708d13e6f02c845e0d9f4c2d8c6", size = 605476, upload-time = "2026-04-08T16:24:37.064Z" }, { url = "https://files.pythonhosted.org/packages/d6/af/b66ab0b2f9a4c5a867c136bf66d9599f34f21a1bcca26a2884a29c450bd9/greenlet-3.4.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a70ed1cb0295bee1df57b63bf7f46b4e56a5c93709eea769c1fec1bb23a95875", size = 618336, upload-time = "2026-04-08T16:30:56.59Z" }, + { url = "https://files.pythonhosted.org/packages/6d/31/56c43d2b5de476f77d36ceeec436328533bff960a4cba9a07616e93063ab/greenlet-3.4.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c5696c42e6bb5cfb7c6ff4453789081c66b9b91f061e5e9367fa15792644e76", size = 625045, upload-time = "2026-04-08T16:40:37.111Z" }, { url = "https://files.pythonhosted.org/packages/e5/5c/8c5633ece6ba611d64bf2770219a98dd439921d6424e4e8cf16b0ac74ea5/greenlet-3.4.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c660bce1940a1acae5f51f0a064f1bc785d07ea16efcb4bc708090afc4d69e83", size = 613515, upload-time = "2026-04-08T15:56:32.478Z" }, + { url = "https://files.pythonhosted.org/packages/80/ca/704d4e2c90acb8bdf7ae593f5cbc95f58e82de95cc540fb75631c1054533/greenlet-3.4.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:89995ce5ddcd2896d89615116dd39b9703bfa0c07b583b85b89bf1b5d6eddf81", size = 419745, upload-time = "2026-04-08T16:43:04.022Z" }, { url = "https://files.pythonhosted.org/packages/a9/df/950d15bca0d90a0e7395eb777903060504cdb509b7b705631e8fb69ff415/greenlet-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee407d4d1ca9dc632265aee1c8732c4a2d60adff848057cdebfe5fe94eb2c8a2", size = 1574623, upload-time = "2026-04-08T16:26:18.596Z" }, { url = "https://files.pythonhosted.org/packages/1a/e7/0839afab829fcb7333c9ff6d80c040949510055d2d4d63251f0d1c7c804e/greenlet-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:956215d5e355fffa7c021d168728321fd4d31fd730ac609b1653b450f6a4bc71", size = 1639579, upload-time = "2026-04-08T15:57:29.231Z" }, { url = "https://files.pythonhosted.org/packages/d9/2b/b4482401e9bcaf9f5c97f67ead38db89c19520ff6d0d6699979c6efcc200/greenlet-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:5cb614ace7c27571270354e9c9f696554d073f8aa9319079dcba466bbdead711", size = 238233, upload-time = "2026-04-08T17:02:54.286Z" }, @@ -1117,7 +1119,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/65/8b/3669ad3b3f247a791b2b4aceb3aa5a31f5f6817bf547e4e1ff712338145a/greenlet-3.4.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1a54a921561dd9518d31d2d3db4d7f80e589083063ab4d3e2e950756ef809e1a", size = 286902, upload-time = "2026-04-08T15:52:12.138Z" }, { url = "https://files.pythonhosted.org/packages/38/3e/3c0e19b82900873e2d8469b590a6c4b3dfd2b316d0591f1c26b38a4879a5/greenlet-3.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16dec271460a9a2b154e3b1c2fa1050ce6280878430320e85e08c166772e3f97", size = 606099, upload-time = "2026-04-08T16:24:38.408Z" }, { url = "https://files.pythonhosted.org/packages/b5/33/99fef65e7754fc76a4ed14794074c38c9ed3394a5bd129d7f61b705f3168/greenlet-3.4.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90036ce224ed6fe75508c1907a77e4540176dcf0744473627785dd519c6f9996", size = 618837, upload-time = "2026-04-08T16:30:58.298Z" }, + { url = "https://files.pythonhosted.org/packages/44/57/eae2cac10421feae6c0987e3dc106c6d86262b1cb379e171b017aba893a6/greenlet-3.4.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6f0def07ec9a71d72315cf26c061aceee53b306c36ed38c35caba952ea1b319d", size = 624901, upload-time = "2026-04-08T16:40:38.981Z" }, { url = "https://files.pythonhosted.org/packages/36/f7/229f3aed6948faa20e0616a0b8568da22e365ede6a54d7d369058b128afd/greenlet-3.4.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1c4f6b453006efb8310affb2d132832e9bbb4fc01ce6df6b70d810d38f1f6dc", size = 615062, upload-time = "2026-04-08T15:56:33.766Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8a/0e73c9b94f31d1cc257fe79a0eff621674141cdae7d6d00f40de378a1e42/greenlet-3.4.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:0e1254cf0cbaa17b04320c3a78575f29f3c161ef38f59c977108f19ffddaf077", size = 423927, upload-time = "2026-04-08T16:43:05.293Z" }, { url = "https://files.pythonhosted.org/packages/08/97/d988180011aa40135c46cd0d0cf01dd97f7162bae14139b4a3ef54889ba5/greenlet-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b2d9a138ffa0e306d0e2b72976d2fb10b97e690d40ab36a472acaab0838e2de", size = 1573511, upload-time = "2026-04-08T16:26:20.058Z" }, { url = "https://files.pythonhosted.org/packages/d4/0f/a5a26fe152fb3d12e6a474181f6e9848283504d0afd095f353d85726374b/greenlet-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8424683caf46eb0eb6f626cb95e008e8cc30d0cb675bdfa48200925c79b38a08", size = 1640396, upload-time = "2026-04-08T15:57:30.88Z" }, { url = "https://files.pythonhosted.org/packages/42/cf/bb2c32d9a100e36ee9f6e38fad6b1e082b8184010cb06259b49e1266ca01/greenlet-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0a53fb071531d003b075c444014ff8f8b1a9898d36bb88abd9ac7b3524648a2", size = 238892, upload-time = "2026-04-08T17:03:10.094Z" }, @@ -1125,7 +1129,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7a/75/7e9cd1126a1e1f0cd67b0eda02e5221b28488d352684704a78ed505bd719/greenlet-3.4.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:43748988b097f9c6f09364f260741aa73c80747f63389824435c7a50bfdfd5c1", size = 285856, upload-time = "2026-04-08T15:52:45.82Z" }, { url = "https://files.pythonhosted.org/packages/9d/c4/3e2df392e5cb199527c4d9dbcaa75c14edcc394b45040f0189f649631e3c/greenlet-3.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5566e4e2cd7a880e8c27618e3eab20f3494452d12fd5129edef7b2f7aa9a36d1", size = 610208, upload-time = "2026-04-08T16:24:39.674Z" }, { url = "https://files.pythonhosted.org/packages/da/af/750cdfda1d1bd30a6c28080245be8d0346e669a98fdbae7f4102aa95fff3/greenlet-3.4.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1054c5a3c78e2ab599d452f23f7adafef55062a783a8e241d24f3b633ba6ff82", size = 621269, upload-time = "2026-04-08T16:30:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/e0/93/c8c508d68ba93232784bbc1b5474d92371f2897dfc6bc281b419f2e0d492/greenlet-3.4.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:98eedd1803353daf1cd9ef23eef23eda5a4d22f99b1f998d273a8b78b70dd47f", size = 628455, upload-time = "2026-04-08T16:40:40.698Z" }, { url = "https://files.pythonhosted.org/packages/54/78/0cbc693622cd54ebe25207efbb3a0eb07c2639cb8594f6e3aaaa0bb077a8/greenlet-3.4.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f82cb6cddc27dd81c96b1506f4aa7def15070c3b2a67d4e46fd19016aacce6cf", size = 617549, upload-time = "2026-04-08T15:56:34.893Z" }, + { url = "https://files.pythonhosted.org/packages/7f/46/cfaaa0ade435a60550fd83d07dfd5c41f873a01da17ede5c4cade0b9bab8/greenlet-3.4.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:b7857e2202aae67bc5725e0c1f6403c20a8ff46094ece015e7d474f5f7020b55", size = 426238, upload-time = "2026-04-08T16:43:06.865Z" }, { url = "https://files.pythonhosted.org/packages/ba/c0/8966767de01343c1ff47e8b855dc78e7d1a8ed2b7b9c83576a57e289f81d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:227a46251ecba4ff46ae742bc5ce95c91d5aceb4b02f885487aff269c127a729", size = 1575310, upload-time = "2026-04-08T16:26:21.671Z" }, { url = "https://files.pythonhosted.org/packages/b8/38/bcdc71ba05e9a5fda87f63ffc2abcd1f15693b659346df994a48c968003d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5b99e87be7eba788dd5b75ba1cde5639edffdec5f91fe0d734a249535ec3408c", size = 1640435, upload-time = "2026-04-08T15:57:32.572Z" }, { url = "https://files.pythonhosted.org/packages/a1/c2/19b664b7173b9e4ef5f77e8cef9f14c20ec7fce7920dc1ccd7afd955d093/greenlet-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:849f8bc17acd6295fcb5de8e46d55cc0e52381c56eaf50a2afd258e97bc65940", size = 238760, upload-time = "2026-04-08T17:04:03.878Z" }, @@ -1133,7 +1139,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/02/bde66806e8f169cf90b14d02c500c44cdbe02c8e224c9c67bafd1b8cadd1/greenlet-3.4.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:10a07aca6babdd18c16a3f4f8880acfffc2b88dfe431ad6aa5f5740759d7d75e", size = 286291, upload-time = "2026-04-08T17:09:34.307Z" }, { url = "https://files.pythonhosted.org/packages/05/1f/39da1c336a87d47c58352fb8a78541ce63d63ae57c5b9dae1fe02801bbc2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:076e21040b3a917d3ce4ad68fb5c3c6b32f1405616c4a57aa83120979649bd3d", size = 656749, upload-time = "2026-04-08T16:24:41.721Z" }, { url = "https://files.pythonhosted.org/packages/d3/6c/90ee29a4ee27af7aa2e2ec408799eeb69ee3fcc5abcecac6ddd07a5cd0f2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e82689eea4a237e530bb5cb41b180ef81fa2160e1f89422a67be7d90da67f615", size = 669084, upload-time = "2026-04-08T16:31:01.372Z" }, + { url = "https://files.pythonhosted.org/packages/d2/4a/74078d3936712cff6d3c91a930016f476ce4198d84e224fe6d81d3e02880/greenlet-3.4.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:06c2d3b89e0c62ba50bd7adf491b14f39da9e7e701647cb7b9ff4c99bee04b19", size = 673405, upload-time = "2026-04-08T16:40:42.527Z" }, { url = "https://files.pythonhosted.org/packages/07/49/d4cad6e5381a50947bb973d2f6cf6592621451b09368b8c20d9b8af49c5b/greenlet-3.4.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df3b0b2289ec686d3c821a5fee44259c05cfe824dd5e6e12c8e5f5df23085cf", size = 665621, upload-time = "2026-04-08T15:56:35.995Z" }, + { url = "https://files.pythonhosted.org/packages/79/3e/df8a83ab894751bc31e1106fdfaa80ca9753222f106b04de93faaa55feb7/greenlet-3.4.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:070b8bac2ff3b4d9e0ff36a0d19e42103331d9737e8504747cd1e659f76297bd", size = 471670, upload-time = "2026-04-08T16:43:08.512Z" }, { url = "https://files.pythonhosted.org/packages/37/31/d1edd54f424761b5d47718822f506b435b6aab2f3f93b465441143ea5119/greenlet-3.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8bff29d586ea415688f4cec96a591fcc3bf762d046a796cdadc1fdb6e7f2d5bf", size = 1622259, upload-time = "2026-04-08T16:26:23.201Z" }, { url = "https://files.pythonhosted.org/packages/b0/c6/6d3f9cdcb21c4e12a79cb332579f1c6aa1af78eb68059c5a957c7812d95e/greenlet-3.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a569c2fb840c53c13a2b8967c63621fafbd1a0e015b9c82f408c33d626a2fda", size = 1686916, upload-time = "2026-04-08T15:57:34.282Z" }, { url = "https://files.pythonhosted.org/packages/63/45/c1ca4a1ad975de4727e52d3ffe641ae23e1d7a8ffaa8ff7a0477e1827b92/greenlet-3.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:207ba5b97ea8b0b60eb43ffcacf26969dd83726095161d676aac03ff913ee50d", size = 239821, upload-time = "2026-04-08T17:03:48.423Z" }, @@ -1141,7 +1149,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/8f/18d72b629783f5e8d045a76f5325c1e938e659a9e4da79c7dcd10169a48d/greenlet-3.4.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d70012e51df2dbbccfaf63a40aaf9b40c8bed37c3e3a38751c926301ce538ece", size = 294681, upload-time = "2026-04-08T15:52:35.778Z" }, { url = "https://files.pythonhosted.org/packages/9e/ad/5fa86ec46769c4153820d58a04062285b3b9e10ba3d461ee257b68dcbf53/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a58bec0751f43068cd40cff31bb3ca02ad6000b3a51ca81367af4eb5abc480c8", size = 658899, upload-time = "2026-04-08T16:24:43.32Z" }, { url = "https://files.pythonhosted.org/packages/43/f0/4e8174ca0e87ae748c409f055a1ba161038c43cc0a5a6f1433a26ac2e5bf/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05fa0803561028f4b2e3b490ee41216a842eaee11aed004cc343a996d9523aa2", size = 665284, upload-time = "2026-04-08T16:31:02.833Z" }, + { url = "https://files.pythonhosted.org/packages/ef/92/466b0d9afd44b8af623139a3599d651c7564fa4152f25f117e1ee5949ffb/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c4cd56a9eb7a6444edbc19062f7b6fbc8f287c663b946e3171d899693b1c19fa", size = 665872, upload-time = "2026-04-08T16:40:43.912Z" }, { url = "https://files.pythonhosted.org/packages/19/da/991cf7cd33662e2df92a1274b7eb4d61769294d38a1bba8a45f31364845e/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e60d38719cb80b3ab5e85f9f1aed4960acfde09868af6762ccb27b260d68f4ed", size = 661861, upload-time = "2026-04-08T15:56:37.269Z" }, + { url = "https://files.pythonhosted.org/packages/0d/14/3395a7ef3e260de0325152ddfe19dffb3e49fe10873b94654352b53ad48e/greenlet-3.4.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:1f85f204c4d54134ae850d401fa435c89cd667d5ce9dc567571776b45941af72", size = 489237, upload-time = "2026-04-08T16:43:09.993Z" }, { url = "https://files.pythonhosted.org/packages/36/c5/6c2c708e14db3d9caea4b459d8464f58c32047451142fe2cfd90e7458f41/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f50c804733b43eded05ae694691c9aa68bca7d0a867d67d4a3f514742a2d53f", size = 1622182, upload-time = "2026-04-08T16:26:24.777Z" }, { url = "https://files.pythonhosted.org/packages/7a/4c/50c5fed19378e11a29fabab1f6be39ea95358f4a0a07e115a51ca93385d8/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2d4f0635dc4aa638cda4b2f5a07ae9a2cff9280327b581a3fcb6f317b4fbc38a", size = 1685050, upload-time = "2026-04-08T15:57:36.453Z" }, { url = "https://files.pythonhosted.org/packages/db/72/85ae954d734703ab48e622c59d4ce35d77ce840c265814af9c078cacc7aa/greenlet-3.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1a4a48f24681300c640f143ba7c404270e1ebbbcf34331d7104a4ff40f8ea705", size = 245554, upload-time = "2026-04-08T17:03:50.044Z" }, From dba111ce4cc945ad52cbd16081f28d1e2c88e2f7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 14:01:23 +0000 Subject: [PATCH 11/14] Remove temporary OPHYD_ASYNC_ALLOW_RESERVED_ATTRS mask The set_mock_attr fixes in the previous commit cover every reserved-attr NameError that this autouse fixture was blanket-suppressing, so the mask is no longer needed. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EQPRwf3pUved4ihrBLPi1g --- tests/conftest.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f02451cc962..7f4c6a77ce3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -68,11 +68,6 @@ ] -@pytest.fixture(autouse=True) -def _allowed_bluesky_attr(): - os.environ["OPHYD_ASYNC_ALLOW_RESERVED_ATTRS"] = "YES" - - @pytest.fixture(autouse=True) def reset_path_provider(): yield From 7fc58a407037d55d3b7bbbe89fb256420b5fca2e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 14:09:49 +0000 Subject: [PATCH 12/14] Fix pyright regression from set_mock_attr changes set_mock_attr(device, "attr", mock) doesn't let pyright narrow device.attr to the mock's type, so later device.attr.assert_*() calls failed with reportAttributeAccessIssue. Capture set_mock_attr's return value (it returns the mock unchanged, per its own docstring) and assert on that instead. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01EQPRwf3pUved4ihrBLPi1g --- .../i06_shared/test_i06_apple2controller.py | 4 +- .../beamlines/i09_2/test_i09_2_motors.py | 4 +- .../devices/beamlines/i10/test_i10_apple2.py | 20 ++++----- .../devices/beamlines/i17/test_i17_apple2.py | 4 +- .../beamlines/i21/test_toolpoint_motion.py | 20 ++++----- .../base/test_detector_logic.py | 4 +- .../test_apple2_controller.py | 4 +- .../insertion_device/test_apple2_undulator.py | 4 +- tests/devices/insertion_device/test_energy.py | 42 +++++++++---------- .../insertion_device/test_polarisation.py | 6 +-- tests/devices/test_attenuator.py | 12 +++--- tests/devices/test_bart_robot.py | 12 ++++-- tests/devices/test_common_mirrors.py | 12 ++++-- tests/devices/test_gridscan.py | 4 +- 14 files changed, 76 insertions(+), 76 deletions(-) diff --git a/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py b/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py index d6186a84248..ca356e5a8f2 100644 --- a/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py +++ b/tests/devices/beamlines/i06_shared/test_i06_apple2controller.py @@ -56,7 +56,7 @@ async def test_set_motors_from_energy_and_polarisation_sets_correct_values( mock_i06_controller: I06Apple2Controller, mock_apple2: Apple2[UndulatorPhaseAxes], ): - set_mock_attr(mock_apple2, "set", AsyncMock()) + mock_set = set_mock_attr(mock_apple2, "set", AsyncMock()) # Mock polarisation setpoint check mock_i06_controller._check_and_get_pol_setpoint = AsyncMock(return_value=Pol.LH) await mock_i06_controller.energy.set(100.0) @@ -75,7 +75,7 @@ async def test_set_motors_from_energy_and_polarisation_sets_correct_values( btm_outer=0.0, ), ) - mock_apple2.set.assert_awaited_once_with(id_motor_values=expected_val) + mock_set.assert_awaited_once_with(id_motor_values=expected_val) async def test_energy_readback_with_inverse_lut( diff --git a/tests/devices/beamlines/i09_2/test_i09_2_motors.py b/tests/devices/beamlines/i09_2/test_i09_2_motors.py index 7436c20d822..f13bc4b08b9 100644 --- a/tests/devices/beamlines/i09_2/test_i09_2_motors.py +++ b/tests/devices/beamlines/i09_2/test_i09_2_motors.py @@ -38,9 +38,9 @@ async def piezo_motor() -> PiezoElectricMotor: async def test_piezo_motor_stop(piezo_motor: PiezoElectricMotor) -> None: - set_mock_attr(piezo_motor.motor_stop, "set", AsyncMock()) + mock_set = set_mock_attr(piezo_motor.motor_stop, "set", AsyncMock()) await piezo_motor.stop() - piezo_motor.motor_stop.set.assert_awaited_once_with(1) + mock_set.assert_awaited_once_with(1) async def test_piezo_motor_set(piezo_motor: PiezoElectricMotor) -> None: diff --git a/tests/devices/beamlines/i10/test_i10_apple2.py b/tests/devices/beamlines/i10/test_i10_apple2.py index 8a0251a0d19..b25a9b8a24d 100644 --- a/tests/devices/beamlines/i10/test_i10_apple2.py +++ b/tests/devices/beamlines/i10/test_i10_apple2.py @@ -267,11 +267,9 @@ async def test_fail_i10_apple2_controller_set_energy_has_default( mock_id_energy: InsertionDeviceEnergy, mock_id_controller: I10Apple2Controller, ): - set_mock_attr(mock_id_controller.energy, "set", AsyncMock()) + mock_set = set_mock_attr(mock_id_controller.energy, "set", AsyncMock()) await mock_id_energy.set(600) - mock_id_controller.energy.set.assert_awaited_once_with( - 600, timeout=MAXIMUM_MOVE_TIME - ) + mock_set.assert_awaited_once_with(600, timeout=MAXIMUM_MOVE_TIME) async def test_beam_energy_re_scan( @@ -390,11 +388,9 @@ async def test_id_polarisation_set_has_default_timeout( mock_id_controller: I10Apple2Controller, ): set_mock_value(mock_id_controller._energy, 700) - set_mock_attr(mock_id_controller.polarisation, "set", AsyncMock()) + mock_set = set_mock_attr(mock_id_controller.polarisation, "set", AsyncMock()) await mock_id_pol.set(Pol.LV) - mock_id_controller.polarisation.set.assert_awaited_once_with( - Pol.LV, timeout=MAXIMUM_MOVE_TIME - ) + mock_set.assert_awaited_once_with(Pol.LV, timeout=MAXIMUM_MOVE_TIME) @pytest.mark.parametrize( @@ -540,11 +536,11 @@ async def test_linear_arbitrary_pol_set_default_timeout( mock_linear_arbitrary_angle: LinearArbitraryAngle, mock_id_controller: I10Apple2Controller, ): - set_mock_attr(mock_id_controller.linear_arbitrary_angle, "set", AsyncMock()) - await mock_linear_arbitrary_angle.set(60) - mock_id_controller.linear_arbitrary_angle.set.assert_awaited_once_with( - 60, timeout=MAXIMUM_MOVE_TIME + mock_set = set_mock_attr( + mock_id_controller.linear_arbitrary_angle, "set", AsyncMock() ) + await mock_linear_arbitrary_angle.set(60) + mock_set.assert_awaited_once_with(60, timeout=MAXIMUM_MOVE_TIME) @pytest.mark.parametrize( diff --git a/tests/devices/beamlines/i17/test_i17_apple2.py b/tests/devices/beamlines/i17/test_i17_apple2.py index 81d2cf70e92..c61d3e70384 100644 --- a/tests/devices/beamlines/i17/test_i17_apple2.py +++ b/tests/devices/beamlines/i17/test_i17_apple2.py @@ -48,7 +48,7 @@ async def test_set_motors_from_energy_and_polarisation_sets_correct_values( mock_id_controller: I17Apple2Controller, mock_apple2: Apple2[UndulatorPhaseAxes], ): - set_mock_attr(mock_apple2, "set", AsyncMock()) + mock_set = set_mock_attr(mock_apple2, "set", AsyncMock()) # Mock polarisation setpoint check mock_id_controller._check_and_get_pol_setpoint = AsyncMock(return_value=Pol.LH) await mock_id_controller.energy.set(100.0) @@ -67,4 +67,4 @@ async def test_set_motors_from_energy_and_polarisation_sets_correct_values( btm_outer=0.0, ), ) - mock_apple2.set.assert_awaited_once_with(id_motor_values=expected_val) + mock_set.assert_awaited_once_with(id_motor_values=expected_val) diff --git a/tests/devices/beamlines/i21/test_toolpoint_motion.py b/tests/devices/beamlines/i21/test_toolpoint_motion.py index b8d0f31d410..7b769bd30c1 100644 --- a/tests/devices/beamlines/i21/test_toolpoint_motion.py +++ b/tests/devices/beamlines/i21/test_toolpoint_motion.py @@ -199,22 +199,22 @@ async def test_uvw_check_value_calls_all_motors( uvw: ToolPointMotion, ) -> None: smp = uvw.smp_ref() - set_mock_attr(smp.x, "check_value", AsyncMock()) - set_mock_attr(smp.y, "check_value", AsyncMock()) - set_mock_attr(smp.z, "check_value", AsyncMock()) - set_mock_attr(smp.tilt, "check_value", AsyncMock()) - set_mock_attr(smp.azimuth, "check_value", AsyncMock()) + mock_check_x = set_mock_attr(smp.x, "check_value", AsyncMock()) + mock_check_y = set_mock_attr(smp.y, "check_value", AsyncMock()) + mock_check_z = set_mock_attr(smp.z, "check_value", AsyncMock()) + mock_check_tilt = set_mock_attr(smp.tilt, "check_value", AsyncMock()) + mock_check_azimuth = set_mock_attr(smp.azimuth, "check_value", AsyncMock()) values = XYZTiltAzimuthMotorPositions( x=1.0, y=2.0, z=3.0, tilt_deg=10.0, azimuth_deg=20.0 ) await uvw.check_value(values) - smp.x.check_value.assert_awaited_once_with(values.x) - smp.y.check_value.assert_awaited_once_with(values.y) - smp.z.check_value.assert_awaited_once_with(values.z) - smp.tilt.check_value.assert_awaited_once_with(values.tilt_deg) - smp.azimuth.check_value.assert_awaited_once_with(values.azimuth_deg) + mock_check_x.assert_awaited_once_with(values.x) + mock_check_y.assert_awaited_once_with(values.y) + mock_check_z.assert_awaited_once_with(values.z) + mock_check_tilt.assert_awaited_once_with(values.tilt_deg) + mock_check_azimuth.assert_awaited_once_with(values.azimuth_deg) async def test_check_values_raises_on_failure( diff --git a/tests/devices/electron_analyser/base/test_detector_logic.py b/tests/devices/electron_analyser/base/test_detector_logic.py index 2634fd7ba7e..9909e5ddab2 100644 --- a/tests/devices/electron_analyser/base/test_detector_logic.py +++ b/tests/devices/electron_analyser/base/test_detector_logic.py @@ -140,7 +140,7 @@ async def test_region_logic_setup_with_region_sets_region_for_epics_and_sets_dri region: BaseRegion, region_logic: RegionLogic, ) -> None: - set_mock_attr(region_logic.driver, "set", AsyncMock()) + mock_set = set_mock_attr(region_logic.driver, "set", AsyncMock()) # Patch switch_energy_mode so we can check on calls, but still run the real function with patch.object( @@ -164,7 +164,7 @@ async def test_region_logic_setup_with_region_sets_region_for_epics_and_sets_dri epics_region = mock_prepare_for_epics.call_args[0][0].prepare_for_epics( await region_logic.energy_source.get_value(), ) - region_logic.driver.set.assert_called_once_with(epics_region) + mock_set.assert_called_once_with(epics_region) @pytest.mark.parametrize(("driver", "region"), DRIVER_REGIONS_PAIR, indirect=["driver"]) diff --git a/tests/devices/insertion_device/test_apple2_controller.py b/tests/devices/insertion_device/test_apple2_controller.py index bc6e91d3343..29e6ed705cc 100644 --- a/tests/devices/insertion_device/test_apple2_controller.py +++ b/tests/devices/insertion_device/test_apple2_controller.py @@ -185,7 +185,7 @@ async def test_id_controller_energy_sets_correct_values( configured_gap: float, configured_phase: float, ): - set_mock_attr(mock_locked_apple2, "set", AsyncMock()) + mock_set = set_mock_attr(mock_locked_apple2, "set", AsyncMock()) mock_locked_controller._check_and_get_pol_setpoint = AsyncMock(return_value=Pol.LH) await mock_locked_controller.energy.set(100.0) expected_val = Apple2Val( @@ -195,7 +195,7 @@ async def test_id_controller_energy_sets_correct_values( ), gap=configured_gap, ) - mock_locked_apple2.set.assert_awaited_once_with(id_motor_values=expected_val) + mock_set.assert_awaited_once_with(id_motor_values=expected_val) async def test_id_controller_energy_read_correct_values_using_readback( diff --git a/tests/devices/insertion_device/test_apple2_undulator.py b/tests/devices/insertion_device/test_apple2_undulator.py index 200456a2bdd..93c72e51bb1 100644 --- a/tests/devices/insertion_device/test_apple2_undulator.py +++ b/tests/devices/insertion_device/test_apple2_undulator.py @@ -462,7 +462,7 @@ async def test_id_controller_energy_sets_correct_values( configured_gap: float, configured_phase: float, ): - set_mock_attr(mock_locked_apple2, "set", AsyncMock()) + mock_set = set_mock_attr(mock_locked_apple2, "set", AsyncMock()) mock_locked_controller._check_and_get_pol_setpoint = AsyncMock(return_value=Pol.LH) await mock_locked_controller.energy.set(100.0) expected_val = Apple2Val( @@ -472,4 +472,4 @@ async def test_id_controller_energy_sets_correct_values( ), gap=configured_gap, ) - mock_locked_apple2.set.assert_awaited_once_with(id_motor_values=expected_val) + mock_set.assert_awaited_once_with(id_motor_values=expected_val) diff --git a/tests/devices/insertion_device/test_energy.py b/tests/devices/insertion_device/test_energy.py index fb5442cc249..cea4bd50a24 100644 --- a/tests/devices/insertion_device/test_energy.py +++ b/tests/devices/insertion_device/test_energy.py @@ -44,26 +44,24 @@ async def test_mock_beam_controller_set_moves_both_devices( mock_id_energy: InsertionDeviceEnergy, mock_pgm: PlaneGratingMonochromator, ): - set_mock_attr(mock_id_energy, "set", AsyncMock()) - set_mock_attr(mock_pgm.energy, "set", AsyncMock()) + mock_id_energy_set = set_mock_attr(mock_id_energy, "set", AsyncMock()) + mock_pgm_energy_set = set_mock_attr(mock_pgm.energy, "set", AsyncMock()) await mock_beam_energy.set(100.0) - mock_id_energy.set.assert_called_once_with(energy=100.0) - mock_pgm.energy.set.assert_called_once_with(100.0) + mock_id_energy_set.assert_called_once_with(energy=100.0) + mock_pgm_energy_set.assert_called_once_with(100.0) async def test_insertion_device_energy_set( mock_id_energy: InsertionDeviceEnergy, mock_id_controller: DummyApple2Controller, ): - set_mock_attr(mock_id_controller.energy, "set", AsyncMock()) + mock_set = set_mock_attr(mock_id_controller.energy, "set", AsyncMock()) await mock_id_energy.set(1500.0) - mock_id_controller.energy.set.assert_awaited_once_with( - 1500.0, timeout=MAXIMUM_MOVE_TIME - ) + mock_set.assert_awaited_once_with(1500.0, timeout=MAXIMUM_MOVE_TIME) @pytest.mark.parametrize( @@ -90,7 +88,7 @@ async def test_insertion_device_energy_prepare_success( mock_id_controller.apple2().gap().acceleration_time, acceleration_time ) mock_id_controller._polarisation_setpoint_set(Pol.LH) - set_mock_attr(mock_id_energy, "set", AsyncMock()) + mock_set = set_mock_attr(mock_id_energy, "set", AsyncMock()) mid_gap_position = end_gap + start_gap / 2.0 mock_id_controller.gap_energy_motor_converter = Mock( side_effect=[start_gap, end_gap, mid_gap_position] @@ -101,7 +99,7 @@ async def test_insertion_device_energy_prepare_success( await mock_id_energy.prepare(fly_info) velocity = (end_gap - start_gap) / time_for_move ramp_up_start = start_gap - acceleration_time * velocity / 2.0 - mock_id_energy.set.assert_awaited_once_with(energy=750) + mock_set.assert_awaited_once_with(energy=750) get_mock_put( mock_id_controller.apple2().gap().user_setpoint ).assert_awaited_once_with(str(ramp_up_start)) @@ -113,18 +111,18 @@ async def test_insertion_deviceenergy_kickoff_call_gap_kickoff( mock_id_energy: InsertionDeviceEnergy, mock_id_gap: UndulatorGap, ): - set_mock_attr(mock_id_gap, "kickoff", AsyncMock()) + mock_kickoff = set_mock_attr(mock_id_gap, "kickoff", AsyncMock()) await mock_id_energy.kickoff() - mock_id_gap.kickoff.assert_awaited_once() + mock_kickoff.assert_awaited_once() def test_insertion_device_energy_complete_call_gap_complete( mock_id_energy: InsertionDeviceEnergy, mock_id_gap: UndulatorGap, ): - set_mock_attr(mock_id_gap, "complete", MagicMock()) + mock_complete = set_mock_attr(mock_id_gap, "complete", MagicMock()) mock_id_energy.complete() - mock_id_gap.complete.assert_called_once() + mock_complete.assert_called_once() async def test_insertion_device_energy_prepare_success_in_run_engine( @@ -145,11 +143,11 @@ async def test_beam_energy_prepare_success( mock_id_energy: InsertionDeviceEnergy, ): fly_info = FlyMotorInfo(start_position=700, end_position=800, time_for_move=10) - set_mock_attr(mock_id_energy, "prepare", AsyncMock()) - set_mock_attr(mock_pgm.energy, "prepare", AsyncMock()) + mock_id_energy_prepare = set_mock_attr(mock_id_energy, "prepare", AsyncMock()) + mock_pgm_energy_prepare = set_mock_attr(mock_pgm.energy, "prepare", AsyncMock()) run_engine(prepare(mock_beam_energy, fly_info)) - mock_id_energy.prepare.assert_awaited_once_with(fly_info) - mock_pgm.energy.prepare.assert_awaited_once_with(fly_info) + mock_id_energy_prepare.assert_awaited_once_with(fly_info) + mock_pgm_energy_prepare.assert_awaited_once_with(fly_info) @patch("asyncio.sleep", new_callable=AsyncMock) @@ -175,13 +173,13 @@ async def test_beam_energy_kickoff_set_correct_delay( set_mock_value(mock_pgm.energy.high_limit_travel, 1000) set_mock_value(mock_pgm.energy.acceleration_time, pgm_acc_time) set_mock_value(mock_id_gap.gate, UndulatorGateStatus.CLOSE) - set_mock_attr(mock_id_gap, "kickoff", AsyncMock()) - set_mock_attr(mock_pgm.energy, "kickoff", AsyncMock()) + mock_id_gap_kickoff = set_mock_attr(mock_id_gap, "kickoff", AsyncMock()) + mock_pgm_energy_kickoff = set_mock_attr(mock_pgm.energy, "kickoff", AsyncMock()) await mock_beam_energy.prepare(fly_info) await mock_beam_energy.kickoff() mock_sleep.assert_called_with(pgm_acc_time - id_acc_time) - mock_id_gap.kickoff.assert_awaited_once() - mock_pgm.energy.kickoff.assert_awaited_once() + mock_id_gap_kickoff.assert_awaited_once() + mock_pgm_energy_kickoff.assert_awaited_once() @patch("asyncio.sleep", new_callable=AsyncMock) diff --git a/tests/devices/insertion_device/test_polarisation.py b/tests/devices/insertion_device/test_polarisation.py index 22c62ec4605..ca5a3296964 100644 --- a/tests/devices/insertion_device/test_polarisation.py +++ b/tests/devices/insertion_device/test_polarisation.py @@ -29,12 +29,10 @@ async def test_polarisation_set_calls_controller_methods( mock_id_controller: DummyApple2Controller, mock_id_polarisation: InsertionDevicePolarisation, ): - set_mock_attr(mock_id_controller.polarisation, "set", AsyncMock()) + mock_set = set_mock_attr(mock_id_controller.polarisation, "set", AsyncMock()) pol = Pol.PC await mock_id_polarisation.set(pol=pol) - mock_id_controller.polarisation.set.assert_called_once_with( - pol, timeout=MAXIMUM_MOVE_TIME - ) + mock_set.assert_called_once_with(pol, timeout=MAXIMUM_MOVE_TIME) async def test_insertion_device_polarisation_locate( diff --git a/tests/devices/test_attenuator.py b/tests/devices/test_attenuator.py index c7cc2b630f3..de8cc4bfb30 100644 --- a/tests/devices/test_attenuator.py +++ b/tests/devices/test_attenuator.py @@ -97,8 +97,10 @@ async def _move_filter_after_short_delay(_): await asyncio.sleep(MOCK_TIMEOUT_S / 2) await attenuator._filters[0].done_move.set(0) - set_mock_attr(attenuator._auto_move_on_desired_transmission_set, "set", AsyncMock()) - set_mock_attr(attenuator._use_current_energy, "trigger", AsyncMock()) + mock_set = set_mock_attr( + attenuator._auto_move_on_desired_transmission_set, "set", AsyncMock() + ) + mock_trigger = set_mock_attr(attenuator._use_current_energy, "trigger", AsyncMock()) set_mock_attr( attenuator._desired_transmission, "set", @@ -110,7 +112,5 @@ async def _move_filter_after_short_delay(_): await attenuator._filters[0].done_move.set(1) await attenuator._filters[1].done_move.set(1) await status - attenuator._auto_move_on_desired_transmission_set.set.assert_awaited_once_with( - YesNo.YES - ) - attenuator._use_current_energy.trigger.assert_called_once() + mock_set.assert_awaited_once_with(YesNo.YES) + mock_trigger.assert_called_once() diff --git a/tests/devices/test_bart_robot.py b/tests/devices/test_bart_robot.py index 204eb6a0b1f..96f9fdc0c04 100644 --- a/tests/devices/test_bart_robot.py +++ b/tests/devices/test_bart_robot.py @@ -153,10 +153,12 @@ async def test_given_program_not_running_but_pin_not_unmounting_when_load_pin_th device = bart_robot _set_fast_robot_timeouts(device) set_mock_value(device.gonio_pin_sensor, PinMounted.PIN_MOUNTED) - set_mock_attr(device.load, "trigger", AsyncMock(side_effect=device.load.trigger)) + mock_trigger = set_mock_attr( + device.load, "trigger", AsyncMock(side_effect=device.load.trigger) + ) with pytest.raises(RobotLoadError): await device.set(SampleLocation(15, 10)) - device.load.trigger.assert_called_once() + mock_trigger.assert_called_once() last_log = patch_logger.mock_calls[1].args[0] assert WAIT_FOR_BEAMLINE_DISABLE_MSG in last_log @@ -169,12 +171,14 @@ async def test_given_program_not_running_and_pin_unmounting_but_new_pin_not_moun device = bart_robot _set_fast_robot_timeouts(device) set_mock_value(device.gonio_pin_sensor, PinMounted.NO_PIN_MOUNTED) - set_mock_attr(device.load, "trigger", AsyncMock(side_effect=device.load.trigger)) + mock_trigger = set_mock_attr( + device.load, "trigger", AsyncMock(side_effect=device.load.trigger) + ) with pytest.raises(RobotLoadError) as exc_info: await device.set(SampleLocation(15, 10)) try: - device.load.trigger.assert_called_once() + mock_trigger.assert_called_once() last_log = patch_logger.mock_calls[1].args[0] assert WAIT_FOR_BEAMLINE_DISABLE_MSG in last_log except AssertionError: diff --git a/tests/devices/test_common_mirrors.py b/tests/devices/test_common_mirrors.py index df5dffb5d06..d2312dc69b9 100644 --- a/tests/devices/test_common_mirrors.py +++ b/tests/devices/test_common_mirrors.py @@ -157,9 +157,11 @@ async def test_stop_xyz_switching_mirror( xyz_switching_mirror: XYZSwitchingMirror, run_engine: RunEngine, ): - set_mock_attr(xyz_switching_mirror.mirror_abort, "trigger", AsyncMock()) + mock_trigger = set_mock_attr( + xyz_switching_mirror.mirror_abort, "trigger", AsyncMock() + ) run_engine(stop(xyz_switching_mirror)) - xyz_switching_mirror.mirror_abort.trigger.assert_awaited_once() + mock_trigger.assert_awaited_once() @pytest.mark.parametrize( @@ -233,6 +235,8 @@ async def test_stop_xyz_piezo_switching_mirror( xyz_piezo_switching_mirror: XYZPiezoSwitchingMirror, run_engine: RunEngine, ): - set_mock_attr(xyz_piezo_switching_mirror.mirror_abort, "trigger", AsyncMock()) + mock_trigger = set_mock_attr( + xyz_piezo_switching_mirror.mirror_abort, "trigger", AsyncMock() + ) run_engine(stop(xyz_piezo_switching_mirror)) - xyz_piezo_switching_mirror.mirror_abort.trigger.assert_awaited_once() + mock_trigger.assert_awaited_once() diff --git a/tests/devices/test_gridscan.py b/tests/devices/test_gridscan.py index 7e20c4d5bf6..fbc9897e040 100644 --- a/tests/devices/test_gridscan.py +++ b/tests/devices/test_gridscan.py @@ -460,12 +460,12 @@ async def test_timeout_on_complete_triggers_stop_and_logs_error( zebra_fast_grid_scan: ZebraFastGridScanThreeD, ): zebra_fast_grid_scan.COMPLETE_STATUS = 0.01 - set_mock_attr(zebra_fast_grid_scan.stop_cmd, "trigger", AsyncMock()) + mock_trigger = set_mock_attr(zebra_fast_grid_scan.stop_cmd, "trigger", AsyncMock()) set_mock_value(zebra_fast_grid_scan.status, 1) with pytest.raises(TimeoutError): await zebra_fast_grid_scan.complete() mock_log_error.assert_called_once() - zebra_fast_grid_scan.stop_cmd.trigger.assert_awaited_once() + mock_trigger.assert_awaited_once() async def test_i02_1_gridscan_has_2d_behaviour( From 96a52b96d9941259c8ffa0ef89ad881159c8fb59 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Wed, 29 Jul 2026 09:50:26 +0000 Subject: [PATCH 13/14] Fix flakey undulator dcm test --- .../beamlines/i03/test_undulator_dcm.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/devices/beamlines/i03/test_undulator_dcm.py b/tests/devices/beamlines/i03/test_undulator_dcm.py index 7607a4e9033..4abf46b0d9d 100644 --- a/tests/devices/beamlines/i03/test_undulator_dcm.py +++ b/tests/devices/beamlines/i03/test_undulator_dcm.py @@ -216,14 +216,17 @@ async def test_dcm_offset_only_set_when_energy_set_completes( offset_put.assert_called_with(25.6) +@pytest.mark.parametrize("i", range(0, 100)) async def test_energy_set_only_complete_when_all_statuses_are_finished( - fake_undulator_dcm: UndulatorDCM, + fake_undulator_dcm: UndulatorDCM, i, event_loop_fuzzing ): - set_mock_value(fake_undulator_dcm.undulator_ref().current_gap, 5.0) - release_dcm = asyncio.Event() release_undulator = asyncio.Event() - + set_mock_attr( + fake_undulator_dcm.dcm_ref().offset_in_mm, + "set", + MagicMock(return_value=AsyncStatus(asyncio.sleep(0))), + ) set_mock_attr( fake_undulator_dcm.dcm_ref().energy_in_keV, "set", @@ -234,16 +237,17 @@ async def test_energy_set_only_complete_when_all_statuses_are_finished( "set", MagicMock(return_value=AsyncStatus(release_undulator.wait())), ) - status = fake_undulator_dcm.set(5.0) + done, _ = await asyncio.wait([status.task], timeout=0.1) + assert not done - await asyncio.wait([status.task], timeout=0.1) # type: ignore - assert not status.done release_dcm.set() - await asyncio.wait([status.task], timeout=0.1) # type: ignore - assert not status.done + done, _ = await asyncio.wait([status.task], timeout=0.1) + assert not done + release_undulator.set() - await asyncio.wait_for(status, timeout=0.02) + done, _ = await asyncio.wait([status.task], timeout=0.1) + assert status.task in done async def test_when_undulator_gap_is_disabled_setting_energy_errors_and_dcm_energy_is_not_set( From 8c6f7193b3aabef64d2b65539a4c0f3afadfed35 Mon Sep 17 00:00:00 2001 From: Oli Wenman Date: Wed, 29 Jul 2026 09:54:30 +0000 Subject: [PATCH 14/14] Removed event_loop_fuzzing --- tests/devices/beamlines/i03/test_undulator_dcm.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/devices/beamlines/i03/test_undulator_dcm.py b/tests/devices/beamlines/i03/test_undulator_dcm.py index 4abf46b0d9d..7631254b9fb 100644 --- a/tests/devices/beamlines/i03/test_undulator_dcm.py +++ b/tests/devices/beamlines/i03/test_undulator_dcm.py @@ -216,9 +216,8 @@ async def test_dcm_offset_only_set_when_energy_set_completes( offset_put.assert_called_with(25.6) -@pytest.mark.parametrize("i", range(0, 100)) async def test_energy_set_only_complete_when_all_statuses_are_finished( - fake_undulator_dcm: UndulatorDCM, i, event_loop_fuzzing + fake_undulator_dcm: UndulatorDCM, ): release_dcm = asyncio.Event() release_undulator = asyncio.Event()