From b3f7688a67c0f02dbc7d7e82e6aa8c5718b8fbaa Mon Sep 17 00:00:00 2001 From: rtuck99 Date: Wed, 8 Jan 2025 11:37:11 +0000 Subject: [PATCH 1/4] Add dodal device for i03 beamstop (#970) * Add a beamstop device for I03 * Add beamstop to the i03 beamline module * Rename IN_BEAM to DATA_COLLECTION * Rename signals as suggested in PR * Update comment as per PR review --- src/dodal/beamlines/i03.py | 17 ++++++ src/dodal/devices/i03/beamstop.py | 85 ++++++++++++++++++++++++++++++ tests/devices/i03/__init__.py | 0 tests/devices/i03/test_beamstop.py | 66 +++++++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 src/dodal/devices/i03/beamstop.py create mode 100644 tests/devices/i03/__init__.py create mode 100644 tests/devices/i03/test_beamstop.py diff --git a/src/dodal/beamlines/i03.py b/src/dodal/beamlines/i03.py index 1aaa02f245..07e116604a 100644 --- a/src/dodal/beamlines/i03.py +++ b/src/dodal/beamlines/i03.py @@ -24,6 +24,7 @@ from dodal.devices.fast_grid_scan import PandAFastGridScan, ZebraFastGridScan from dodal.devices.flux import Flux from dodal.devices.focusing_mirror import FocusingMirrorWithStripes, MirrorVoltages +from dodal.devices.i03.beamstop import Beamstop from dodal.devices.motors import XYZPositioner from dodal.devices.oav.oav_detector import OAV from dodal.devices.oav.oav_parameters import OAVConfig @@ -93,6 +94,22 @@ def attenuator( ) +def beamstop( + wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False +) -> Beamstop: + """Get the i03 beamstop device, instantiate it if it hasn't already been. + If this is called when already instantiated in i03, it will return the existing object. + """ + return device_instantiation( + Beamstop, + "beamstop", + "-MO-BS-01:", + wait_for_connection, + fake_with_ophyd_sim, + beamline_parameters=get_beamline_parameters(), + ) + + def dcm(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -> DCM: """Get the i03 DCM device, instantiate it if it hasn't already been. If this is called when already instantiated in i03, it will return the existing object. diff --git a/src/dodal/devices/i03/beamstop.py b/src/dodal/devices/i03/beamstop.py new file mode 100644 index 0000000000..f3ea91aee5 --- /dev/null +++ b/src/dodal/devices/i03/beamstop.py @@ -0,0 +1,85 @@ +from asyncio import gather +from math import isclose + +from ophyd_async.core import StandardReadable, StrictEnum +from ophyd_async.epics.motor import Motor + +from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters +from dodal.common.signal_utils import create_hardware_backed_soft_signal + + +class BeamstopPositions(StrictEnum): + """ + Beamstop positions. + GDA supports Standard/High/Low resolution positions, as well as parked and + robot load however all 3 resolution positions are the same. We also + do not use the robot load position in Hyperion. + + Until we support moving the beamstop it is only necessary to check whether the + beamstop is in beam or not. + + See Also: + https://github.com/DiamondLightSource/mx-bluesky/issues/484 + + Attributes: + DATA_COLLECTION: The beamstop is in beam ready for data collection + UNKNOWN: The beamstop is in some other position, check the device motor + positions to determine it. + """ + + DATA_COLLECTION = "Data Collection" + UNKNOWN = "Unknown" + + +class Beamstop(StandardReadable): + """ + Beamstop for I03. + + Attributes: + x: beamstop x position in mm + y: beamstop y position in mm + z: beamstop z position in mm + selected_pos: Get the current position of the beamstop as an enum. Currently this + is read-only. + """ + + def __init__( + self, + prefix: str, + beamline_parameters: GDABeamlineParameters, + name: str = "", + ): + with self.add_children_as_readables(): + self.x_mm = Motor(prefix + "X") + self.y_mm = Motor(prefix + "Y") + self.z_mm = Motor(prefix + "Z") + self.selected_pos = create_hardware_backed_soft_signal( + BeamstopPositions, self._get_selected_position + ) + + self._in_beam_xyz_mm = [ + float(beamline_parameters[f"in_beam_{axis}_STANDARD"]) + for axis in ("x", "y", "z") + ] + self._xyz_tolerance_mm = [ + float(beamline_parameters[f"bs_{axis}_tolerance"]) + for axis in ("x", "y", "z") + ] + + super().__init__(name) + + async def _get_selected_position(self) -> BeamstopPositions: + current_pos = await gather( + self.x_mm.user_readback.get_value(), + self.y_mm.user_readback.get_value(), + self.z_mm.user_readback.get_value(), + ) + if all( + isclose(axis_pos, axis_in_beam, abs_tol=axis_tolerance) + for axis_pos, axis_in_beam, axis_tolerance in zip( + current_pos, self._in_beam_xyz_mm, self._xyz_tolerance_mm, strict=False + ) + ): + return BeamstopPositions.DATA_COLLECTION + else: + return BeamstopPositions.UNKNOWN diff --git a/tests/devices/i03/__init__.py b/tests/devices/i03/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/devices/i03/test_beamstop.py b/tests/devices/i03/test_beamstop.py new file mode 100644 index 0000000000..26af226e23 --- /dev/null +++ b/tests/devices/i03/test_beamstop.py @@ -0,0 +1,66 @@ +from itertools import dropwhile +from unittest.mock import Mock + +import pytest +from bluesky import plan_stubs as bps +from bluesky.preprocessors import run_decorator +from bluesky.run_engine import RunEngine +from ophyd_async.testing import set_mock_value + +from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters +from dodal.devices.i03.beamstop import Beamstop, BeamstopPositions + + +@pytest.fixture +def beamline_parameters() -> GDABeamlineParameters: + return GDABeamlineParameters.from_file( + "tests/test_data/test_beamline_parameters.txt" + ) + + +@pytest.mark.parametrize( + "x, y, z, expected_pos", + [ + [0, 0, 0, BeamstopPositions.UNKNOWN], + [1.52, 44.78, 30.0, BeamstopPositions.DATA_COLLECTION], + [1.501, 44.776, 29.71, BeamstopPositions.DATA_COLLECTION], + [1.499, 44.776, 29.71, BeamstopPositions.UNKNOWN], + [1.501, 44.774, 29.71, BeamstopPositions.UNKNOWN], + [1.501, 44.776, 29.69, BeamstopPositions.UNKNOWN], + ], +) +async def test_beamstop_pos_select( + beamline_parameters: GDABeamlineParameters, + RE: RunEngine, + x: float, + y: float, + z: float, + expected_pos: BeamstopPositions, +): + beamstop = Beamstop("-MO-BS-01:", beamline_parameters, name="beamstop") + await beamstop.connect(mock=True) + set_mock_value(beamstop.x_mm.user_readback, x) + set_mock_value(beamstop.y_mm.user_readback, y) + set_mock_value(beamstop.z_mm.user_readback, z) + + mock_callback = Mock() + RE.subscribe(mock_callback, "event") + + @run_decorator() + def check_in_beam(): + current_pos = yield from bps.rd(beamstop.selected_pos) + assert current_pos == expected_pos + yield from bps.create() + yield from bps.read(beamstop) + yield from bps.save() + + RE(check_in_beam()) + + event_call = next( + dropwhile(lambda c: c.args[0] != "event", mock_callback.mock_calls) + ) + data = event_call.args[1]["data"] + assert data["beamstop-x_mm"] == x + assert data["beamstop-y_mm"] == y + assert data["beamstop-z_mm"] == z + assert data["beamstop-selected_pos"] == expected_pos From a29f3af8136fb2ada534d896770fdd67413da822 Mon Sep 17 00:00:00 2001 From: DiamondRC Date: Wed, 8 Jan 2025 15:55:35 +0000 Subject: [PATCH 2/4] Bimorph mirrors (#974) * Create bimorph_mirrors dir with __init__.py * Create bimorph_mirror.py with basic class structure * Add VOUT_RBV channel creation to __init__ * Move bimorph_mirror.py out of bimorph_mirrors directory * Create BimorphMirrorChannel with vout_rbv SignalR * Change BimorphMirrorChannel child creation to procedural * Replace BimorphMirror child decleration to use BimorphMirrorChannel * Remove unused imports * Remove unused import * Remove uneccesary Format in add_children_as_readables * Add super().__init__ call * Add VTRGT_RBV, STATUS to BimorphMirrorChannel * Add BimorphMirror.set method * Add bimorph test file * Fix STATUS type * Make BimorphMirror.set await values in VOUT_RBV * Add assertion that channel exists to BimorphMirror.set * Replace .get with square brackets in BimorphMirror.set to avoid typing errors * Add BimorphMirrorChannel.shift * Add BimorphMirror.all_shift, .add_volt * Add BimorphMirror.channels, .status, .temps * Rename previous BimorphMirror.channels to BimorphMirror.channel_list to avoid member name clash * Add BimorphMirror.reset_err_proc, .err * Add BimorphOnOff Strict Enum * Add BimorphMirror.on_off * Add BimorphMirrorMode StrictEnum * Add BimorphMirror.op_mode * Change BimorphMirror.on_off datatype to BimorphMirrorOnOff rather than StrictEnum * Add docstrings * Remove uneccesary signals from BimorphMirror * Add missing attribute to docstring * Remove duplicate signal decleration * Remove uneccesary BimorphMirror signal * Change BimorphMirrorChannel.status to datatype BimorphMirrorOnOff * Add BimorphMirrorStatus, set as BimorphMirror.status datatype * Change BimorphMirror.channels datatype to str * Change BimorphMirrorChannel to declare all signals declaratively * Remove uneccesary argument * Move all PV delimiters from suffix into prefix * Fix incorrect use of PVSuffix * Make BimorphMirrorChannel inherit from EpicsDevice * Remove :CHANNELS signal from BimorphMirror * Rename BimorphMirror.channel_list to .channels * Remove unused import * Replace BimorphMirror.set wait on rbv with wait=True * Add bimorph test_set_channels * Organise imports * Make wait_for_value in BimorphMirror.set use DEFAULT_TIMEOUT instead of None * Rename test_set_channels, fix said test, and add test_set_channels_triggers_alltrgt_proc * Remove whitespace * Remove unused import * Add comments * Parametrize test_set_channels_wait_for_readback * Abstract setting vout mock values into fixture * Parametrize test_set_channels_wait_for_readback * Add whitespace * Renam set_vout_mock_values to set_vout_mock_and_return_value * Add test_set_channels_waits_for_vout_readback unimplemented * Add test_read, and BIMORPH_NAME global variable * Make mirror fixture take number_of_channels argument * Correct BimorphMirrorChannel Format signal types * Add valid_bimorph_values fixture * Change test parametrization from bimorph input to number of channels * Add format changes generated by tox pre-commit * Change explcit channel number parameter to reference to global list of channel numbers * Implement test_set_channels_waits_for_vout_readback * Add test_set_invalid_chanel_throws_error * Add Raises section to BimorphMirror.set docstring * Make BiorphMirror.__init__ number_of_channels have no default value * Replace all asserts inside for loops, to comprehension and comparison * Remove BIMORPH_NAME global variable * Remove prng from test input generation * Add BimorphMirror.__init__ number_of_channels validation * Add test_init_mirror_with_invalid_channels_throws_error * Add BimorphMirror.__init__ docstring * Replace assertion in BimorphMirror.set arg validation with raise ValueError * Make test_set_invalid_channel_throws_error expect correct error type * Replace real PV prefix with fake * Add mock_vtrgt_vout_propogation, remove set_vout_mock fixtures * Remove unused import * Fix import for ophyd async 0.9.0a1 * Update src/dodal/devices/bimorph_mirror.py Co-authored-by: Callum Forrester * Update src/dodal/devices/bimorph_mirror.py Co-authored-by: Callum Forrester * Update src/dodal/devices/bimorph_mirror.py Co-authored-by: Callum Forrester * Remove BimorphMirror.number_of_channels * Rename BimorphMirror.alltrgt_proc, .on_off for readability * Rename BimorphMirrorChannel.vtrgt, .vout for readbility * Change BimorphMirror.set type hinting * Replace BimorphMirror.set sequential input validation with collection Co-authored-by: Callum Forrester * Add test_init_mirror_with_zero_channels * Ensure bimorph has finished moving after set * wait for value * Update test_bimorph_mirror.py * Crop PV name for BUSY toggle * Crop PV name for BUSY toggle * await IDLE status * Update test to account for idling --------- Co-authored-by: Daniel Fernandes Co-authored-by: Daniel Fernandes <65790536+dan-fernandes@users.noreply.github.com> Co-authored-by: Callum Forrester --- src/dodal/devices/bimorph_mirror.py | 6 ++++-- tests/devices/unit_tests/test_bimorph_mirror.py | 10 +++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/dodal/devices/bimorph_mirror.py b/src/dodal/devices/bimorph_mirror.py index bb29320720..0f071bed7d 100644 --- a/src/dodal/devices/bimorph_mirror.py +++ b/src/dodal/devices/bimorph_mirror.py @@ -91,7 +91,6 @@ def __init__(self, prefix: str, number_of_channels: int, name=""): self.commit_target_voltages = epics_signal_x(f"{prefix}ALLTRGT.PROC") self.status = epics_signal_r(BimorphMirrorStatus, f"{prefix}STATUS") self.err = epics_signal_r(str, f"{prefix}ERR") - super().__init__(name=name) @AsyncStatus.wrap @@ -129,7 +128,10 @@ async def set(self, value: Mapping[int, float], tolerance: float = 0.0001) -> No timeout=DEFAULT_TIMEOUT, ) for i, target in value.items() - ] + ], + wait_for_value( + self.status, BimorphMirrorStatus.IDLE, timeout=DEFAULT_TIMEOUT + ), ) diff --git a/tests/devices/unit_tests/test_bimorph_mirror.py b/tests/devices/unit_tests/test_bimorph_mirror.py index addb3435d7..105cba61e6 100644 --- a/tests/devices/unit_tests/test_bimorph_mirror.py +++ b/tests/devices/unit_tests/test_bimorph_mirror.py @@ -5,7 +5,7 @@ from ophyd_async.core import DeviceCollector from ophyd_async.testing import get_mock_put -from dodal.devices.bimorph_mirror import BimorphMirror +from dodal.devices.bimorph_mirror import BimorphMirror, BimorphMirrorStatus VALID_BIMORPH_CHANNELS = [8, 12, 16, 24] @@ -78,10 +78,14 @@ async def test_set_channels_waits_for_vout_readback( await mirror.set(valid_bimorph_values) - assert [ + expected_call_arg_list = [ call(mirror.channels[i].output_voltage, ANY, timeout=ANY) for i, val in valid_bimorph_values.items() - ] == mock_wait_for_value.call_args_list + ] + expected_call_arg_list.append( + call(mirror.status, BimorphMirrorStatus.IDLE, timeout=ANY) + ) + assert expected_call_arg_list == mock_wait_for_value.call_args_list @pytest.mark.parametrize("mirror", VALID_BIMORPH_CHANNELS, indirect=True) From a7a01e7d4fbfbbde1347a1fdf328557521eb97b4 Mon Sep 17 00:00:00 2001 From: Daniel Fernandes <65790536+dan-fernandes@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:00:08 +0000 Subject: [PATCH 3/4] Make BimorphMirrorChannel Movable (#977) Add BimorphMirror ophyd class, with child class BimorphMirrorChannel. --- src/dodal/devices/bimorph_mirror.py | 11 ++++++++++- tests/devices/unit_tests/test_bimorph_mirror.py | 13 +++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/dodal/devices/bimorph_mirror.py b/src/dodal/devices/bimorph_mirror.py index 0f071bed7d..01f2f2caaf 100644 --- a/src/dodal/devices/bimorph_mirror.py +++ b/src/dodal/devices/bimorph_mirror.py @@ -41,7 +41,7 @@ class BimorphMirrorStatus(StrictEnum): ERROR = "Error" -class BimorphMirrorChannel(StandardReadable, EpicsDevice): +class BimorphMirrorChannel(StandardReadable, Movable, EpicsDevice): """Collection of PVs comprising a single bimorph channel. Attributes: @@ -56,6 +56,15 @@ class BimorphMirrorChannel(StandardReadable, EpicsDevice): status: A[SignalR[BimorphMirrorOnOff], PvSuffix("STATUS"), Format.CONFIG_SIGNAL] shift: A[SignalW[float], PvSuffix("SHIFT")] + @AsyncStatus.wrap + async def set(self, value: float): + """Sets channel's VOUT to given value. + + Args: + value: float to set VOUT to + """ + await self.output_voltage.set(value) + class BimorphMirror(StandardReadable, Movable): """Class to represent CAENels Bimorph Mirrors. diff --git a/tests/devices/unit_tests/test_bimorph_mirror.py b/tests/devices/unit_tests/test_bimorph_mirror.py index 105cba61e6..3caa42029d 100644 --- a/tests/devices/unit_tests/test_bimorph_mirror.py +++ b/tests/devices/unit_tests/test_bimorph_mirror.py @@ -152,3 +152,16 @@ async def test_init_mirror_with_invalid_channels_throws_error(number_of_channels async def test_init_mirror_with_zero_channels(number_of_channels): mirror = BimorphMirror(prefix="FAKE-PREFIX", number_of_channels=number_of_channels) assert len(mirror.channels) == 0 + + +@pytest.mark.parametrize("mirror", VALID_BIMORPH_CHANNELS, indirect=True) +async def test_bimorph_mirror_channel_set( + mirror: BimorphMirror, + valid_bimorph_values: dict[int, float], +): + for value, channel in zip( + valid_bimorph_values.values(), mirror.channels.values(), strict=True + ): + assert await channel.output_voltage.get_value() != value + await channel.set(value) + assert await channel.output_voltage.get_value() == value From bbd76c6281bdea918f207f06014e411b1d8750f0 Mon Sep 17 00:00:00 2001 From: rtuck99 Date: Thu, 9 Jan 2025 15:44:52 +0000 Subject: [PATCH 4/4] Add comment for RotationDirection (#979) * Add comment for RotationDirection * Make ruff 0.9.0 happy --- conftest.py | 6 +++--- src/dodal/common/crystal_metadata.py | 6 +++--- src/dodal/common/udc_directory_provider.py | 4 +++- src/dodal/devices/attenuator/attenuator.py | 2 +- src/dodal/devices/bimorph_mirror.py | 2 +- src/dodal/devices/eiger_odin.py | 6 +++--- src/dodal/devices/zebra.py | 7 ++++++- src/dodal/log.py | 4 ++-- src/dodal/plans/wrapped.py | 6 +++--- .../common/beamlines/test_device_instantiation.py | 12 ++++++------ .../unit_tests/detector/test_det_resolution.py | 12 ++++++------ tests/devices/unit_tests/test_shutter.py | 6 +++--- tests/fake_zocalo/__main__.py | 6 +++--- tests/plans/test_compliance.py | 12 ++++++------ tests/test_utils.py | 14 ++++++-------- 15 files changed, 55 insertions(+), 50 deletions(-) diff --git a/conftest.py b/conftest.py index 91cfaa0608..aaa083a340 100644 --- a/conftest.py +++ b/conftest.py @@ -53,9 +53,9 @@ def patched_open(*args, **kwargs): requested_path = Path(args[0]) if requested_path.is_absolute(): for p in BANNED_PATHS: - assert not requested_path.is_relative_to( - p - ), f"Attempt to open {requested_path} from inside a unit test" + assert not requested_path.is_relative_to(p), ( + f"Attempt to open {requested_path} from inside a unit test" + ) return unpatched_open(*args, **kwargs) with patch("builtins.open", side_effect=patched_open): diff --git a/src/dodal/common/crystal_metadata.py b/src/dodal/common/crystal_metadata.py index 836c69d01e..fc8300d49f 100644 --- a/src/dodal/common/crystal_metadata.py +++ b/src/dodal/common/crystal_metadata.py @@ -55,7 +55,7 @@ def make_crystal_metadata_from_material( d_spacing = d_spacing_param or CrystalMetadata.calculate_default_d_spacing( material.value.lattice_parameter, reflection_plane ) - assert all( - isinstance(i, int) and i > 0 for i in reflection_plane - ), "Reflection plane indices must be positive integers" + assert all(isinstance(i, int) and i > 0 for i in reflection_plane), ( + "Reflection plane indices must be positive integers" + ) return CrystalMetadata(usage, material.value.name, reflection_plane, d_spacing) diff --git a/src/dodal/common/udc_directory_provider.py b/src/dodal/common/udc_directory_provider.py index 9f5ea707ac..a9ba9f9119 100644 --- a/src/dodal/common/udc_directory_provider.py +++ b/src/dodal/common/udc_directory_provider.py @@ -46,7 +46,9 @@ async def update(self, *, directory: Path, suffix: str = "", **kwargs): self._filename_provider.suffix = suffix def __call__(self, device_name: str | None = None) -> PathInfo: - assert self._output_directory, "Directory unknown for PandA to write into, update() needs to be called at least once" + assert self._output_directory, ( + "Directory unknown for PandA to write into, update() needs to be called at least once" + ) return PathInfo( directory_path=self._output_directory, filename=self._filename_provider(device_name), diff --git a/src/dodal/devices/attenuator/attenuator.py b/src/dodal/devices/attenuator/attenuator.py index 64b337aba7..9319095915 100644 --- a/src/dodal/devices/attenuator/attenuator.py +++ b/src/dodal/devices/attenuator/attenuator.py @@ -106,7 +106,7 @@ def __init__( with self.add_children_as_readables(): self.filters: DeviceVector[FilterMotor] = DeviceVector( { - index: FilterMotor(f"{prefix}MP{index+1}:", filter, name) + index: FilterMotor(f"{prefix}MP{index + 1}:", filter, name) for index, filter in enumerate(filter_selection) } ) diff --git a/src/dodal/devices/bimorph_mirror.py b/src/dodal/devices/bimorph_mirror.py index 01f2f2caaf..e290bb2b53 100644 --- a/src/dodal/devices/bimorph_mirror.py +++ b/src/dodal/devices/bimorph_mirror.py @@ -114,7 +114,7 @@ async def set(self, value: Mapping[int, float], tolerance: float = 0.0001) -> No if any(key not in self.channels for key in value): raise ValueError( - f"Attempting to put to non-existent channels: {[key for key in value if (key not in self.channels)]}" + f"Attempting to put to non-existent channels: {[key for key in value if (key not in self.channels)]}" ) # Write target voltages: diff --git a/src/dodal/devices/eiger_odin.py b/src/dodal/devices/eiger_odin.py index c5ccb943b8..28760867c7 100644 --- a/src/dodal/devices/eiger_odin.py +++ b/src/dodal/devices/eiger_odin.py @@ -91,10 +91,10 @@ def check_frames_dropped(self) -> tuple[bool, str]: def wait_for_no_errors(self, timeout) -> dict[SubscriptionStatus, str]: errors = {} for node_number, node_pv in enumerate(self.nodes): - errors[ - await_value(node_pv.error_status, False, timeout) - ] = f"Filewriter {node_number} is in an error state with error message\ + errors[await_value(node_pv.error_status, False, timeout)] = ( + f"Filewriter {node_number} is in an error state with error message\ - {node_pv.error_message.get()}" + ) return errors diff --git a/src/dodal/devices/zebra.py b/src/dodal/devices/zebra.py index ea3a72e0da..477a3284a8 100644 --- a/src/dodal/devices/zebra.py +++ b/src/dodal/devices/zebra.py @@ -81,6 +81,11 @@ class I24Axes: class RotationDirection(StrictEnum): + """ + Defines for a swept angle whether the scan width (sweep) is to be added or subtracted from + the initial angle to obtain the final angle. + """ + POSITIVE = "Positive" NEGATIVE = "Negative" @@ -281,7 +286,7 @@ def __str__(self) -> str: for input, (source, invert) in enumerate( zip(self.sources, self.invert, strict=False) ): - input_strings.append(f"INP{input+1}={'!' if invert else ''}{source}") + input_strings.append(f"INP{input + 1}={'!' if invert else ''}{source}") return ", ".join(input_strings) diff --git a/src/dodal/log.py b/src/dodal/log.py index 1d67f25f2b..a7e560adf8 100644 --- a/src/dodal/log.py +++ b/src/dodal/log.py @@ -152,7 +152,7 @@ def set_up_graylog_handler(logger: Logger, host: str, port: int): def set_up_INFO_file_handler(logger, path: Path, filename: str): """Set up a file handler for the logger, at INFO level, which will keep 30 days of logs, rotating once per day. Creates the directory if necessary.""" - print(f"Logging to INFO file handler {path/filename}") + print(f"Logging to INFO file handler {path / filename}") path.mkdir(parents=True, exist_ok=True) file_handler = TimedRotatingFileHandler( filename=path / filename, when="MIDNIGHT", backupCount=INFO_LOG_DAYS @@ -169,7 +169,7 @@ def set_up_DEBUG_memory_handler( log file when it sees a message of severity ERROR. Creates the directory if necessary""" debug_path = path / "debug" - print(f"Logging to DEBUG handler {debug_path/filename}") + print(f"Logging to DEBUG handler {debug_path / filename}") debug_path.mkdir(parents=True, exist_ok=True) file_handler = TimedRotatingFileHandler( filename=debug_path / filename, when="H", backupCount=DEBUG_LOG_FILES_TO_KEEP diff --git a/src/dodal/plans/wrapped.py b/src/dodal/plans/wrapped.py index 9589bb1e77..48875c5235 100644 --- a/src/dodal/plans/wrapped.py +++ b/src/dodal/plans/wrapped.py @@ -49,9 +49,9 @@ def count( Wraps bluesky.plans.count(det, num, delay, md=metadata) exposing only serializable parameters and metadata.""" if isinstance(delay, Sequence): - assert ( - len(delay) == num - 1 - ), f"Number of delays given must be {num - 1}: was given {len(delay)}" + assert len(delay) == num - 1, ( + f"Number of delays given must be {num - 1}: was given {len(delay)}" + ) metadata = metadata or {} metadata["shape"] = (num,) yield from bp.count(tuple(detectors), num, delay=delay, md=metadata) diff --git a/tests/common/beamlines/test_device_instantiation.py b/tests/common/beamlines/test_device_instantiation.py index 4584f98f76..a57aa3db9d 100644 --- a/tests/common/beamlines/test_device_instantiation.py +++ b/tests/common/beamlines/test_device_instantiation.py @@ -29,9 +29,9 @@ def test_device_creation(RE, module_and_devices_for_beamline): for name, device in devices.items() if not follows_bluesky_protocols(device) ] - assert ( - len(devices_not_following_bluesky_protocols) == 0 - ), f"{devices_not_following_bluesky_protocols} do not follow bluesky protocols" + assert len(devices_not_following_bluesky_protocols) == 0, ( + f"{devices_not_following_bluesky_protocols} do not follow bluesky protocols" + ) @pytest.mark.parametrize( @@ -56,6 +56,6 @@ def test_devices_are_identical(RE, module_and_devices_for_beamline): ] total_number_of_devices = len(devices_a) non_identical_number_of_devies = len(devices_a) - assert ( - len(non_identical_names) == 0 - ), f"{non_identical_number_of_devies}/{total_number_of_devices} devices were not identical: {non_identical_names}" + assert len(non_identical_names) == 0, ( + f"{non_identical_number_of_devies}/{total_number_of_devices} devices were not identical: {non_identical_names}" + ) diff --git a/tests/devices/unit_tests/detector/test_det_resolution.py b/tests/devices/unit_tests/detector/test_det_resolution.py index f451a9848f..88e029e263 100644 --- a/tests/devices/unit_tests/detector/test_det_resolution.py +++ b/tests/devices/unit_tests/detector/test_det_resolution.py @@ -55,9 +55,9 @@ def test_resolution( detector_params.use_roi_mode = roi get_detector_max_size.return_value = 434.6 actual_res = resolution(detector_params, wavelength_angstroms, det_distance_mm) - assert isclose( - expected_res, actual_res - ), f"expected={expected_res}, actual={actual_res}" + assert isclose(expected_res, actual_res), ( + f"expected={expected_res}, actual={actual_res}" + ) @pytest.mark.parametrize( @@ -126,6 +126,6 @@ def test_resolution_with_roi_realistic( actual_res = resolution(detector_params, wavelength_angstroms, det_distance_mm) - assert isclose( - actual_res, expected_res, rtol=1e-3 - ), f"expected={expected_res}, actual={actual_res}" + assert isclose(actual_res, expected_res, rtol=1e-3), ( + f"expected={expected_res}, actual={actual_res}" + ) diff --git a/tests/devices/unit_tests/test_shutter.py b/tests/devices/unit_tests/test_shutter.py index 515bda161e..1d6a0e0772 100644 --- a/tests/devices/unit_tests/test_shutter.py +++ b/tests/devices/unit_tests/test_shutter.py @@ -32,9 +32,9 @@ async def test_set_shutter_open( reading = await sim_shutter.read() shutter_position = reading.get("shutter-position_readback", {}) - assert ( - shutter_position["value"] is new_state - ), f"Unexpected value: {shutter_position['value']}" + assert shutter_position["value"] is new_state, ( + f"Unexpected value: {shutter_position['value']}" + ) async def test_given_shutter_in_auto_then_when_set_raises(sim_shutter: ZebraShutter): diff --git a/tests/fake_zocalo/__main__.py b/tests/fake_zocalo/__main__.py index 2184f8a602..6db11c9504 100644 --- a/tests/fake_zocalo/__main__.py +++ b/tests/fake_zocalo/__main__.py @@ -67,9 +67,9 @@ def get_dcgid_and_prefix(dcid: int, session_maker: sessionmaker) -> tuple[int, s .filter(DataCollection.dataCollectionId == dcid) .first() ) - assert ( - query is not None - ), f"Failed to find dcid {dcid} which matches any in dev ispyb" + assert query is not None, ( + f"Failed to find dcid {dcid} which matches any in dev ispyb" + ) dcgid, prefix = query except Exception as e: diff --git a/tests/plans/test_compliance.py b/tests/plans/test_compliance.py index de4ef2846e..f87843202e 100644 --- a/tests/plans/test_compliance.py +++ b/tests/plans/test_compliance.py @@ -53,13 +53,13 @@ def assert_hard_requirements(plan: PlanGenerator, signature: inspect.Signature): def assert_metadata_requirements(plan: PlanGenerator, signature: inspect.Signature): - assert ( - "metadata" in signature.parameters - ), f"'{plan.__name__}' does not allow metadata" + assert "metadata" in signature.parameters, ( + f"'{plan.__name__}' does not allow metadata" + ) metadata = signature.parameters["metadata"] - assert ( - metadata.annotation == dict[str, Any] | None and metadata.default is None - ), f"'{plan.__name__}' metadata is not optional" + assert metadata.annotation == dict[str, Any] | None and metadata.default is None, ( + f"'{plan.__name__}' metadata is not optional" + ) assert metadata.default is None, f"'{plan.__name__}' metadata default is mutable" diff --git a/tests/test_utils.py b/tests/test_utils.py index e6e2afdae9..7ece08d762 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -308,15 +308,13 @@ def test_get_run_number_uses_prefix(mock_list_dir: MagicMock): OPHYD_ASYNC_DEVICE_B = OphydV2Device(name="OPHYD_ASYNC_DEVICE_B") -def _filtering_test_cases() -> ( - Iterable[ - tuple[ - Mapping[str, AnyDevice], - Mapping[str, OphydV1Device], - Mapping[str, OphydV2Device], - ] +def _filtering_test_cases() -> Iterable[ + tuple[ + Mapping[str, AnyDevice], + Mapping[str, OphydV1Device], + Mapping[str, OphydV2Device], ] -): +]: yield {}, {}, {} yield ( {"oa": OPHYD_DEVICE_A},