From f5dddafc8cdf0f2e12f8ec3dc703b3436d10bffe Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 9 Dec 2025 09:37:13 +0000 Subject: [PATCH 1/8] Add mock config server fixture --- tests/conftest.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index b326effeba..e0c936160d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import importlib +import json import os from collections.abc import AsyncGenerator from pathlib import Path @@ -6,6 +7,7 @@ from unittest.mock import patch import pytest +from daq_config_server.converters.models import ConfigModel from ophyd_async.core import init_devices, set_mock_value from conftest import mock_attributes_table @@ -88,3 +90,33 @@ async def baton_in_commissioning_mode() -> AsyncGenerator[Baton]: set_mock_value(baton.commissioning, True) yield baton set_commissioning_signal(None) + + +def _fake_config_server_get_file_contents( + filepath: str | Path, + desired_return_type: type[str] | type[dict] | ConfigModel = str, + reset_cached_result: bool = True, +): + filepath = Path(filepath) + # Minimal logic required for unit tests + with filepath.open("r") as f: + contents = f.read() + print(contents) + if desired_return_type is str: + return contents + elif desired_return_type is dict: + print("return type is dict") + return json.loads(contents) + elif issubclass(desired_return_type, ConfigModel): # type: ignore + return desired_return_type.model_validate(json.loads(contents)) + + +@pytest.fixture(autouse=True) +def mock_config_server(): + # Don't actually talk to central service during unit tests, and reset caches between test + + with patch( + "daq_config_server.client.ConfigServer.get_file_contents", + side_effect=_fake_config_server_get_file_contents, + ): + yield From 923f23a56d088c2f3f7252fdfecb90d6b6d0dc5a Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Wed, 10 Dec 2025 10:41:45 +0000 Subject: [PATCH 2/8] Read det dinstance config through config server --- src/dodal/devices/detector/det_dist_to_beam_converter.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/dodal/devices/detector/det_dist_to_beam_converter.py b/src/dodal/devices/detector/det_dist_to_beam_converter.py index be169f543b..ab7b131e4e 100644 --- a/src/dodal/devices/detector/det_dist_to_beam_converter.py +++ b/src/dodal/devices/detector/det_dist_to_beam_converter.py @@ -1,5 +1,8 @@ from enum import Enum +from daq_config_server.client import ConfigServer +from daq_config_server.converters.models import GenericLookupTable + from dodal.devices.util.lookup_tables import ( linear_extrapolation_lut, parse_lookup_table, @@ -14,7 +17,11 @@ class Axis(Enum): class DetectorDistanceToBeamXYConverter: def __init__(self, lookup_file: str): self.lookup_file: str = lookup_file - lookup_table_columns: list = parse_lookup_table(self.lookup_file) + config_server = ConfigServer(url="https://daq-config.diamond.ac.uk") + + lookup_table_columns: list = config_server.get_file_contents( + lookup_file, GenericLookupTable + ).columns() self._d_to_x = linear_extrapolation_lut( lookup_table_columns[0], lookup_table_columns[1] ) From d55fd4d64859d8d33e749df80eb7dbc9503993b5 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Wed, 10 Dec 2025 10:42:03 +0000 Subject: [PATCH 3/8] Reformat test data --- .../detector/test_data/test_det_dist_converter.txt | 14 +++++++------- tests/devices/test_data/test_lookup_table.txt | 12 +++++++----- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/devices/detector/test_data/test_det_dist_converter.txt b/tests/devices/detector/test_data/test_det_dist_converter.txt index 084853e432..4ba3260e3d 100644 --- a/tests/devices/detector/test_data/test_det_dist_converter.txt +++ b/tests/devices/detector/test_data/test_det_dist_converter.txt @@ -1,7 +1,7 @@ -#Table giving position of beam X and Y as a function of detector distance -#Units mm mm mm -# Eiger values -# distance beamY beamX (values from mosflm) -Units mm mm mm -200 153.61 162.45 -500 153.57 159.96 +{ + "column_names": ["detector_distances_mm", "beam_centre_x_mm", "beam_centre_y_mm"], + "rows": [ + [200.0, 153.61, 162.45], + [500.0, 153.57, 159.96] + ] +} diff --git a/tests/devices/test_data/test_lookup_table.txt b/tests/devices/test_data/test_lookup_table.txt index 16fa297a05..40099c39f0 100644 --- a/tests/devices/test_data/test_lookup_table.txt +++ b/tests/devices/test_data/test_lookup_table.txt @@ -1,5 +1,7 @@ -# Beam converter lookup table for testing - -Units det_dist beam_x beam_y -100.0 150.0 160.0 -200.0 151.0 165.0 +{ + "column_names": ["detector_distances_mm", "beam_centre_x_mm", "beam_centre_y_mm"], + "rows": [ + [100.0, 150.0, 160.0], + [200.0, 151.0, 165.0] + ] +} From a1b8610cd4dad507b040e6db028b3793301e0785 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Wed, 10 Dec 2025 10:42:17 +0000 Subject: [PATCH 4/8] Fix tests --- tests/devices/detector/test_detector.py | 4 ++-- tests/devices/test_beam_converter.py | 10 ++++------ tests/devices/util/test_lookup_tables.py | 12 ------------ 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/tests/devices/detector/test_detector.py b/tests/devices/detector/test_detector.py index c27bfae9a8..e8ad8877a8 100644 --- a/tests/devices/detector/test_detector.py +++ b/tests/devices/detector/test_detector.py @@ -46,14 +46,14 @@ def test_if_path_provided_check_is_dir(tmp_path: Path): @patch( - "dodal.devices.detector.det_dist_to_beam_converter.parse_lookup_table", + "dodal.devices.detector.det_dist_to_beam_converter.ConfigServer", ) @patch( "dodal.devices.detector.det_dist_to_beam_converter.linear_extrapolation_lut", MagicMock(), ) def test_correct_det_dist_to_beam_converter_path_passed_in( - mocked_parse_table, tmp_path: Path + mocked_config_server, tmp_path: Path ): params = DetectorParams( expected_energy_ev=100, diff --git a/tests/devices/test_beam_converter.py b/tests/devices/test_beam_converter.py index 0d26cf7e28..ce94ab8fff 100644 --- a/tests/devices/test_beam_converter.py +++ b/tests/devices/test_beam_converter.py @@ -2,6 +2,7 @@ from unittest.mock import Mock, patch import pytest +from daq_config_server.converters.models import GenericLookupTable from dodal.devices.detector.det_dist_to_beam_converter import ( Axis, @@ -9,18 +10,15 @@ ) # fmt: off -LOOKUP_TABLE_TEST_VALUES = [ - (100.0, 200.0), # distance - (150.0, 151.0), # x - (160.0, 165.0), # y -] +LOOKUP_TABLE_TEST_VALUES = GenericLookupTable(column_names = ["detector_distances_mm", "beam_centre_x_mm", "beam_centre_y_mm"], rows=[[100.0, 150.0, 160.0], [200.0, 151.0, 165.0]]) + # fmt: on @pytest.fixture def fake_converter(): with patch( - "dodal.devices.detector.det_dist_to_beam_converter.parse_lookup_table", + "dodal.devices.detector.det_dist_to_beam_converter.ConfigServer.get_file_contents", return_value=LOOKUP_TABLE_TEST_VALUES, ): yield DetectorDistanceToBeamXYConverter("test.txt") diff --git a/tests/devices/util/test_lookup_tables.py b/tests/devices/util/test_lookup_tables.py index 35a78a0f03..f674ef7c55 100644 --- a/tests/devices/util/test_lookup_tables.py +++ b/tests/devices/util/test_lookup_tables.py @@ -27,18 +27,6 @@ async def test_energy_to_distance_table_correct_format(): assert table.shape == (50, 2) -@mark.parametrize( - "lut_path, num_columns", - [(TEST_BEAMLINE_DCM_ROLL_CONVERTER_TXT, 2), (TEST_DET_DIST_CONVERTER_LUT, 3)], -) -def test_parse_lookup_table_returns_list_of_the_same_length_as_num_of_columns( - lut_path, num_columns -): - lut_values = parse_lookup_table(lut_path) - - assert isinstance(lut_values, list) and len(lut_values) == num_columns - - @mark.parametrize("s, expected_t", [(2.0, 1.0), (3.0, 1.5), (5.0, 4.0), (5.25, 6.0)]) def test_linear_interpolation(s, expected_t): lut_converter = linear_interpolation_lut( From ebac59b234ccfa29e3173c92acc7f555d68b1a42 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 6 Jan 2026 11:21:11 +0000 Subject: [PATCH 5/8] Use DetectorXY lut config model --- .../devices/detector/det_dist_to_beam_converter.py | 5 ++--- tests/conftest.py | 4 +--- tests/devices/detector/test_detector.py | 10 ++-------- tests/devices/test_beam_converter.py | 4 ++-- tests/devices/test_data/test_lookup_table.txt | 1 - tests/devices/util/test_lookup_tables.py | 3 --- 6 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/dodal/devices/detector/det_dist_to_beam_converter.py b/src/dodal/devices/detector/det_dist_to_beam_converter.py index ab7b131e4e..e30462c64a 100644 --- a/src/dodal/devices/detector/det_dist_to_beam_converter.py +++ b/src/dodal/devices/detector/det_dist_to_beam_converter.py @@ -1,11 +1,10 @@ from enum import Enum from daq_config_server.client import ConfigServer -from daq_config_server.converters.models import GenericLookupTable +from daq_config_server.models import DetectorXYLookupTable from dodal.devices.util.lookup_tables import ( linear_extrapolation_lut, - parse_lookup_table, ) @@ -20,7 +19,7 @@ def __init__(self, lookup_file: str): config_server = ConfigServer(url="https://daq-config.diamond.ac.uk") lookup_table_columns: list = config_server.get_file_contents( - lookup_file, GenericLookupTable + lookup_file, DetectorXYLookupTable ).columns() self._d_to_x = linear_extrapolation_lut( lookup_table_columns[0], lookup_table_columns[1] diff --git a/tests/conftest.py b/tests/conftest.py index e0c936160d..8f5988600d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ from unittest.mock import patch import pytest -from daq_config_server.converters.models import ConfigModel +from daq_config_server.models import ConfigModel from ophyd_async.core import init_devices, set_mock_value from conftest import mock_attributes_table @@ -101,11 +101,9 @@ def _fake_config_server_get_file_contents( # Minimal logic required for unit tests with filepath.open("r") as f: contents = f.read() - print(contents) if desired_return_type is str: return contents elif desired_return_type is dict: - print("return type is dict") return json.loads(contents) elif issubclass(desired_return_type, ConfigModel): # type: ignore return desired_return_type.model_validate(json.loads(contents)) diff --git a/tests/devices/detector/test_detector.py b/tests/devices/detector/test_detector.py index e8ad8877a8..5694d68665 100644 --- a/tests/devices/detector/test_detector.py +++ b/tests/devices/detector/test_detector.py @@ -73,10 +73,7 @@ def test_correct_det_dist_to_beam_converter_path_passed_in( assert params.beam_xy_converter.lookup_file == "a fake directory" -@patch( - "dodal.devices.detector.det_dist_to_beam_converter.parse_lookup_table", -) -def test_run_number_correct_when_not_specified(mocked_parse_table, tmp_path): +def test_run_number_correct_when_not_specified(tmp_path): params = DetectorParams( expected_energy_ev=100, exposure_time_s=1.0, @@ -94,10 +91,7 @@ def test_run_number_correct_when_not_specified(mocked_parse_table, tmp_path): assert params.run_number == 1 -@patch( - "dodal.devices.detector.det_dist_to_beam_converter.parse_lookup_table", -) -def test_run_number_correct_when_specified(mocked_parse_table, tmp_path): +def test_run_number_correct_when_specified(tmp_path): params = DetectorParams( expected_energy_ev=100, exposure_time_s=1.0, diff --git a/tests/devices/test_beam_converter.py b/tests/devices/test_beam_converter.py index ce94ab8fff..0b3261f506 100644 --- a/tests/devices/test_beam_converter.py +++ b/tests/devices/test_beam_converter.py @@ -2,7 +2,7 @@ from unittest.mock import Mock, patch import pytest -from daq_config_server.converters.models import GenericLookupTable +from daq_config_server.models import DetectorXYLookupTable from dodal.devices.detector.det_dist_to_beam_converter import ( Axis, @@ -10,7 +10,7 @@ ) # fmt: off -LOOKUP_TABLE_TEST_VALUES = GenericLookupTable(column_names = ["detector_distances_mm", "beam_centre_x_mm", "beam_centre_y_mm"], rows=[[100.0, 150.0, 160.0], [200.0, 151.0, 165.0]]) +LOOKUP_TABLE_TEST_VALUES = DetectorXYLookupTable(rows=[[100.0, 150.0, 160.0], [200.0, 151.0, 165.0]]) # fmt: on diff --git a/tests/devices/test_data/test_lookup_table.txt b/tests/devices/test_data/test_lookup_table.txt index 40099c39f0..00f6357bfd 100644 --- a/tests/devices/test_data/test_lookup_table.txt +++ b/tests/devices/test_data/test_lookup_table.txt @@ -1,5 +1,4 @@ { - "column_names": ["detector_distances_mm", "beam_centre_x_mm", "beam_centre_y_mm"], "rows": [ [100.0, 150.0, 160.0], [200.0, 151.0, 165.0] diff --git a/tests/devices/util/test_lookup_tables.py b/tests/devices/util/test_lookup_tables.py index f674ef7c55..02fd3b9090 100644 --- a/tests/devices/util/test_lookup_tables.py +++ b/tests/devices/util/test_lookup_tables.py @@ -7,9 +7,6 @@ linear_interpolation_lut, parse_lookup_table, ) -from tests.devices.detector.test_data import ( - TEST_DET_DIST_CONVERTER_LUT, -) from tests.devices.test_data import ( TEST_BEAMLINE_UNDULATOR_TO_GAP_LUT, ) From 836eafc875c6d249cd3f5fa3c1e8c9dac566d4c2 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Tue, 6 Jan 2026 14:09:20 +0000 Subject: [PATCH 6/8] Fix for columns becoming a property --- src/dodal/devices/detector/det_dist_to_beam_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dodal/devices/detector/det_dist_to_beam_converter.py b/src/dodal/devices/detector/det_dist_to_beam_converter.py index e30462c64a..4d3f453e09 100644 --- a/src/dodal/devices/detector/det_dist_to_beam_converter.py +++ b/src/dodal/devices/detector/det_dist_to_beam_converter.py @@ -20,7 +20,7 @@ def __init__(self, lookup_file: str): lookup_table_columns: list = config_server.get_file_contents( lookup_file, DetectorXYLookupTable - ).columns() + ).columns self._d_to_x = linear_extrapolation_lut( lookup_table_columns[0], lookup_table_columns[1] ) From 77be37ff11e41b92d8ed313a13e07256c3ad9baa Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Mon, 12 Jan 2026 16:52:18 +0000 Subject: [PATCH 7/8] Require latest daq-config-server --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6560daf5ae..159f8c2ee7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "scanspec>=0.7.3", "pyzmq==26.3.0", # Until we can move to RHEL 8 https://github.com/DiamondLightSource/mx-bluesky/issues/1139 "deepdiff", - "daq-config-server>=v1.0.0-rc.2", # For getting Configuration settings. + "daq-config-server>=v1.1.2", # For getting Configuration settings. ] dynamic = ["version"] From 1e9a569c5340ff6629e7b139ac4a1563aba1a1a7 Mon Sep 17 00:00:00 2001 From: Jacob Williamson Date: Wed, 4 Feb 2026 17:01:29 +0000 Subject: [PATCH 8/8] Fix lint --- uv.lock | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index eca15cb4b9..297e2bf774 100644 --- a/uv.lock +++ b/uv.lock @@ -644,7 +644,7 @@ wheels = [ [[package]] name = "daq-config-server" -version = "1.0.0" +version = "1.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cachetools" }, @@ -657,10 +657,11 @@ dependencies = [ { name = "requests" }, { name = "urllib3" }, { name = "uvicorn" }, + { name = "xmltodict" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/84/649f039a658994fdbe6ecf53e92ba01b65c53635965fe2f4c0c64fbb21a4/daq_config_server-1.0.0.tar.gz", hash = "sha256:63b4989c563520683fbda12aaa42ffeab5fcccc9cc2b25953fd6bc673ab91afd", size = 113002, upload-time = "2025-12-04T17:01:19.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/1e/37373402723769ced976215ffb1716353d89c0772d5bec8116d9f981d095/daq_config_server-1.1.2.tar.gz", hash = "sha256:2f8c9e43a41534d90512be7ecab37de0fdf33d4b00a6b61bd04e3c82d886062f", size = 126067, upload-time = "2026-01-12T11:28:21.034Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/05/5edbc534abf7edbd9cdd971f25809f7ae916e0d417756cb96fb11c49252c/daq_config_server-1.0.0-py3-none-any.whl", hash = "sha256:cfea960c4b6652784f598ce784fcd236f5fd1234f483c2c287b3a4ab96efb3c8", size = 19808, upload-time = "2025-12-04T17:01:18.392Z" }, + { url = "https://files.pythonhosted.org/packages/13/9f/b8df9fad0b1005e95460ee92092a1da33c282c158336bc23bd7635981f29/daq_config_server-1.1.2-py3-none-any.whl", hash = "sha256:c2bca297feb01a51883e3df7d6ade11af9a3f311602e5584320b1e12881cf636", size = 29273, upload-time = "2026-01-12T11:28:19.635Z" }, ] [[package]] @@ -763,7 +764,7 @@ requires-dist = [ { name = "aiohttp" }, { name = "bluesky", specifier = ">=1.14.5" }, { name = "click" }, - { name = "daq-config-server", specifier = ">=1.0.0" }, + { name = "daq-config-server", specifier = ">=1.1.2" }, { name = "deepdiff" }, { name = "graypy" }, { name = "numpy" }, @@ -3831,6 +3832,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/47/7902c3cea79f6a1964fac59b97fb9f11e5ea85e0c0582cc89b2c3193ea48/workflows-3.2-py3-none-any.whl", hash = "sha256:38eed7d209d626b371277bcbcd9c3d476bce9945467d1341c578b1c21ff4eec3", size = 66736, upload-time = "2025-02-27T17:37:40.441Z" }, ] +[[package]] +name = "xmltodict" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/aa/917ceeed4dbb80d2f04dbd0c784b7ee7bba8ae5a54837ef0e5e062cd3cfb/xmltodict-1.0.2.tar.gz", hash = "sha256:54306780b7c2175a3967cad1db92f218207e5bc1aba697d887807c0fb68b7649", size = 25725, upload-time = "2025-09-17T21:59:26.459Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/20/69a0e6058bc5ea74892d089d64dfc3a62ba78917ec5e2cfa70f7c92ba3a5/xmltodict-1.0.2-py3-none-any.whl", hash = "sha256:62d0fddb0dcbc9f642745d8bbf4d81fd17d6dfaec5a15b5c1876300aad92af0d", size = 13893, upload-time = "2025-09-17T21:59:24.859Z" }, +] + [[package]] name = "yarl" version = "1.22.0"