diff --git a/pyproject.toml b/pyproject.toml index 1d850c89834..dc9740f29f4 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", # For getting Configuration settings. + "daq-config-server>=v1.1.2", # For getting Configuration settings. ] dynamic = ["version"] 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 be169f543b2..4d3f453e095 100644 --- a/src/dodal/devices/detector/det_dist_to_beam_converter.py +++ b/src/dodal/devices/detector/det_dist_to_beam_converter.py @@ -1,8 +1,10 @@ from enum import Enum +from daq_config_server.client import ConfigServer +from daq_config_server.models import DetectorXYLookupTable + from dodal.devices.util.lookup_tables import ( linear_extrapolation_lut, - parse_lookup_table, ) @@ -14,7 +16,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, 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 3e15d7bee29..0e35786925a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import importlib +import json import logging import os import sys @@ -8,9 +9,8 @@ from unittest.mock import MagicMock, patch import pytest -from ophyd_async.core import ( - PathProvider, -) +from daq_config_server.models import ConfigModel +from ophyd_async.core import PathProvider from dodal.common.beamlines import beamline_parameters, beamline_utils from dodal.common.beamlines.beamline_utils import clear_path_provider @@ -167,3 +167,31 @@ def eiger_params(tmp_path: Path) -> DetectorParams: det_dist_to_beam_converter_path=TEST_LUT_TXT, detector_size_constants=EIGER2_X_16M_SIZE.det_type_string, # type: ignore ) + + +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() + if desired_return_type is str: + return contents + elif desired_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 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 084853e4329..4ba3260e3d5 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/detector/test_detector.py b/tests/devices/detector/test_detector.py index c27bfae9a85..5694d686653 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, @@ -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 0d26cf7e283..0b3261f5060 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.models import DetectorXYLookupTable 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 = DetectorXYLookupTable(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/test_data/test_lookup_table.txt b/tests/devices/test_data/test_lookup_table.txt index 16fa297a058..00f6357bfdc 100644 --- a/tests/devices/test_data/test_lookup_table.txt +++ b/tests/devices/test_data/test_lookup_table.txt @@ -1,5 +1,6 @@ -# 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 +{ + "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 35a78a0f036..02fd3b90909 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, ) @@ -27,18 +24,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( diff --git a/uv.lock b/uv.lock index eca15cb4b9a..297e2bf7747 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"