diff --git a/pyproject.toml b/pyproject.toml index 545b739944..15f0a84182 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/beamlines/i09_1_shared.py b/src/dodal/beamlines/i09_1_shared.py index ad0fae279b..783781916f 100644 --- a/src/dodal/beamlines/i09_1_shared.py +++ b/src/dodal/beamlines/i09_1_shared.py @@ -1,3 +1,5 @@ +from daq_config_server.client import ConfigServer + from dodal.device_manager import DeviceManager from dodal.devices.common_dcm import ( DoubleCrystalMonochromatorWithDSpacing, @@ -6,6 +8,7 @@ ) from dodal.devices.i09_1_shared.hard_energy import HardEnergy, HardInsertionDeviceEnergy from dodal.devices.i09_1_shared.hard_undulator_functions import ( + I09HardLutProvider, calculate_energy_i09_hu, calculate_gap_i09_hu, ) @@ -17,6 +20,9 @@ devices = DeviceManager() +I09_1_CONF_CLIENT = ConfigServer() +LOOK_UPTABLE_FILE = "/dls_sw/i09-1/software/gda/workspace_git/gda-diamond.git/configurations/i09-1-shared/lookupTables/IIDCalibrationTable.txt" + @devices.factory() def dcm() -> DoubleCrystalMonochromatorWithDSpacing[ @@ -44,7 +50,7 @@ def hu_id_energy( return HardInsertionDeviceEnergy( undulator_order=harmonics, undulator=undulator, - lut={}, # ToDo https://github.com/DiamondLightSource/sm-bluesky/issues/239 + lut_provider=I09HardLutProvider(I09_1_CONF_CLIENT, LOOK_UPTABLE_FILE), gap_to_energy_func=calculate_energy_i09_hu, energy_to_gap_func=calculate_gap_i09_hu, ) diff --git a/src/dodal/devices/i09_1_shared/__init__.py b/src/dodal/devices/i09_1_shared/__init__.py index 174e219ecb..064c2f5c3e 100644 --- a/src/dodal/devices/i09_1_shared/__init__.py +++ b/src/dodal/devices/i09_1_shared/__init__.py @@ -2,12 +2,10 @@ from .hard_undulator_functions import ( calculate_energy_i09_hu, calculate_gap_i09_hu, - get_hu_lut_as_dict, ) __all__ = [ "calculate_gap_i09_hu", - "get_hu_lut_as_dict", "calculate_energy_i09_hu", "HardInsertionDeviceEnergy", "HardEnergy", diff --git a/src/dodal/devices/i09_1_shared/hard_energy.py b/src/dodal/devices/i09_1_shared/hard_energy.py index 1156cad93f..913b690a93 100644 --- a/src/dodal/devices/i09_1_shared/hard_energy.py +++ b/src/dodal/devices/i09_1_shared/hard_energy.py @@ -1,8 +1,7 @@ from asyncio import gather -from collections.abc import Callable +from typing import Any, Protocol from bluesky.protocols import Locatable, Location, Movable -from numpy import ndarray from ophyd_async.core import ( AsyncStatus, Reference, @@ -13,13 +12,21 @@ ) from dodal.devices.common_dcm import DoubleCrystalMonochromatorBase -from dodal.devices.i09_1_shared.hard_undulator_functions import ( - MAX_ENERGY_COLUMN, - MIN_ENERGY_COLUMN, -) from dodal.devices.undulator import UndulatorInMm, UndulatorOrder +class LookUpTableProvider(Protocol): + def get_look_up_table(self, *args, **kwargs) -> Any: + """Protocol to provide lookup table data.""" + ... + + +class EnergyGapConvertor(Protocol): + def __call__(self, lut: LookUpTableProvider, value: float, order: int) -> float: + """Protocol to provide value conversion using lookup table provider.""" + ... + + class HardInsertionDeviceEnergy(StandardReadable, Movable[float]): """ Compound device to link hard x-ray undulator gap and order to photon energy. @@ -30,16 +37,16 @@ def __init__( self, undulator_order: UndulatorOrder, undulator: UndulatorInMm, - lut: dict[int, ndarray], - gap_to_energy_func: Callable[..., float], - energy_to_gap_func: Callable[..., float], + lut_provider: LookUpTableProvider, + gap_to_energy_func: EnergyGapConvertor, + energy_to_gap_func: EnergyGapConvertor, name: str = "", ) -> None: - self._lut = lut - self.gap_to_energy_func = gap_to_energy_func - self.energy_to_gap_func = energy_to_gap_func self._undulator_order_ref = Reference(undulator_order) self._undulator_ref = Reference(undulator) + self._lut_provider = lut_provider + self.gap_to_energy_func = gap_to_energy_func + self.energy_to_gap_func = energy_to_gap_func self.add_readables([undulator_order, undulator.current_gap]) with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL): @@ -54,26 +61,11 @@ def __init__( super().__init__(name=name) def _read_energy(self, current_gap: float, current_order: int) -> float: - return self.gap_to_energy_func( - gap=current_gap, - look_up_table=self._lut, - order=current_order, - ) + return self.gap_to_energy_func(self._lut_provider, current_gap, current_order) async def _set_energy(self, energy: float) -> None: current_order = await self._undulator_order_ref().value.get_value() - min_energy, max_energy = self._lut[current_order][ - MIN_ENERGY_COLUMN : MAX_ENERGY_COLUMN + 1 - ] - if not (min_energy <= energy <= max_energy): - raise ValueError( - f"Requested energy {energy} keV is out of range for harmonic {current_order}: " - f"[{min_energy}, {max_energy}] keV" - ) - - target_gap = self.energy_to_gap_func( - photon_energy_kev=energy, look_up_table=self._lut, order=current_order - ) + target_gap = self.energy_to_gap_func(self._lut_provider, energy, current_order) await self._undulator_ref().set(target_gap) @AsyncStatus.wrap diff --git a/src/dodal/devices/i09_1_shared/hard_undulator_functions.py b/src/dodal/devices/i09_1_shared/hard_undulator_functions.py index ac44da367b..20c76d001b 100644 --- a/src/dodal/devices/i09_1_shared/hard_undulator_functions.py +++ b/src/dodal/devices/i09_1_shared/hard_undulator_functions.py @@ -1,49 +1,82 @@ import numpy as np +from daq_config_server.client import ConfigServer +from daq_config_server.models.converters.lookup_tables import GenericLookupTable -from dodal.devices.util.lookup_tables import energy_distance_table +from dodal.devices.i09_1_shared.hard_energy import LookUpTableProvider from dodal.log import LOGGER -LUT_COMMENTS = ["#"] -HU_SKIP_ROWS = 3 - # Physics constants ELECTRON_REST_ENERGY_MEV = 0.510999 -# Columns in the lookup table -RING_ENERGY_COLUMN = 1 -MAGNET_FIELD_COLUMN = 2 -MIN_ENERGY_COLUMN = 3 -MAX_ENERGY_COLUMN = 4 -MIN_GAP_COLUMN = 5 -MAX_GAP_COLUMN = 6 -GAP_OFFSET_COLUMN = 7 - +# Column names in the lookup table +HARMONICS_COLUMN_NAME = "order" +RING_ENERGY_COLUMN_NAME = "ring_energy_gev" +MAGNET_FIELD_COLUMN_NAME = "magnetic_field_t" +MIN_ENERGY_COLUMN_NAME = "energy_min_ev" +MAX_ENERGY_COLUMN_NAME = "energy_max_ev" +MIN_GAP_COLUMN_NAME = "gap_min_mm" +MAX_GAP_COLUMN_NAME = "gap_max_mm" +GAP_OFFSET_COLUMN_NAME = "gap_offset_mm" +I09_HU_UNDULATOR_LUT_COLUMN_NAMES = [ + HARMONICS_COLUMN_NAME, + RING_ENERGY_COLUMN_NAME, + MAGNET_FIELD_COLUMN_NAME, + MIN_ENERGY_COLUMN_NAME, + MAX_ENERGY_COLUMN_NAME, + MIN_GAP_COLUMN_NAME, + MAX_GAP_COLUMN_NAME, + GAP_OFFSET_COLUMN_NAME, +] MAGNET_BLOCKS_PER_PERIOD = 4 -MAGNTE_BLOCK_HEIGHT_MM = 16 +MAGNET_BLOCK_HEIGHT_MM = 16 -async def get_hu_lut_as_dict(lut_path: str) -> dict[int, np.ndarray]: - lut_dict: dict[int, np.ndarray] = {} - _lookup_table: np.ndarray = await energy_distance_table( - lut_path, - comments=LUT_COMMENTS, - skiprows=HU_SKIP_ROWS, - ) - for i in range(_lookup_table.shape[0]): - lut_dict[_lookup_table[i][0]] = _lookup_table[i] - LOGGER.debug(f"Loaded lookup table: {lut_dict}") - return lut_dict +class I09HardLutProvider(LookUpTableProvider): + def __init__(self, config_server: ConfigServer, filepath: str) -> None: + self.config_server = config_server + self.filepath = filepath + + def get_look_up_table(self) -> GenericLookupTable: + self._lut = self.config_server.get_file_contents( + self.filepath, + desired_return_type=GenericLookupTable, + reset_cached_result=True, + ) + return self._lut -def _validate_order(order: int, look_up_table: dict[int, "np.ndarray"]) -> None: +def _validate_order(look_up_table: GenericLookupTable, order: int) -> None: """Validate that the harmonic order exists in the lookup table.""" - if order not in look_up_table.keys(): + order_column_index = look_up_table.get_column_names().index(HARMONICS_COLUMN_NAME) + if order not in look_up_table.columns[order_column_index]: raise ValueError(f"Order parameter {order} not found in lookup table") -def _calculate_gamma(look_up_table: dict[int, "np.ndarray"], order: int) -> float: +def _validate_energy_in_range( + look_up_table: GenericLookupTable, + energy: float, + order: int, +) -> None: + """Check if the requested energy is within the allowed range for the current harmonic order.""" + min_energy = look_up_table.get_value( + HARMONICS_COLUMN_NAME, order, MIN_ENERGY_COLUMN_NAME + ) + max_energy = look_up_table.get_value( + HARMONICS_COLUMN_NAME, order, MAX_ENERGY_COLUMN_NAME + ) + if not (min_energy <= energy <= max_energy): + raise ValueError( + f"Requested energy {energy} keV is out of range for harmonic {order}: " + f"[{min_energy}, {max_energy}] keV" + ) + + +def _calculate_gamma(look_up_table: GenericLookupTable, order: int) -> float: """Calculate the Lorentz factor gamma from the lookup table.""" - return 1000 * look_up_table[order][RING_ENERGY_COLUMN] / ELECTRON_REST_ENERGY_MEV + ring_energy_gev = look_up_table.get_value( + HARMONICS_COLUMN_NAME, order, "ring_energy_gev" + ) + return 1000 * ring_energy_gev / ELECTRON_REST_ENERGY_MEV def _calculate_undulator_parameter_max( @@ -62,16 +95,14 @@ def _calculate_undulator_parameter_max( / np.pi ) * np.sin(np.pi / MAGNET_BLOCKS_PER_PERIOD) - * (1 - np.exp(-2 * np.pi * MAGNTE_BLOCK_HEIGHT_MM / undulator_period_mm)) + * (1 - np.exp(-2 * np.pi * MAGNET_BLOCK_HEIGHT_MM / undulator_period_mm)) ) def calculate_gap_i09_hu( - photon_energy_kev: float, - look_up_table: dict[int, np.ndarray], + lut: LookUpTableProvider, + value: float, order: int = 1, - gap_offset: float = 0.0, - undulator_period_mm: int = 27, ) -> float: """ Calculate the undulator gap required to produce a given energy at a given harmonic order. @@ -82,14 +113,18 @@ def calculate_gap_i09_hu( photon_energy_kev (float): Requested photon energy in keV. look_up_table (dict[int, np.ndarray]): Lookup table containing undulator and beamline parameters for each harmonic order. order (int, optional): Harmonic order for which to calculate the gap. Defaults to 1. - gap_offset (float, optional): Additional gap offset to apply (in mm). Defaults to 0.0. - undulator_period_mm (int, optional): Undulator period in mm. Defaults to 27. Returns: float: Calculated undulator gap in millimeters. """ + gap_offset: float = 0.0 + undulator_period_mm: int = 27 + look_up_table: GenericLookupTable = lut.get_look_up_table() + + # Validate inputs + _validate_order(look_up_table, order) + _validate_energy_in_range(look_up_table, value, order) - _validate_order(order, look_up_table) gamma = _calculate_gamma(look_up_table, order) # Constructive interference of radiation emitted at different poles @@ -99,9 +134,7 @@ def calculate_gap_i09_hu( # gives K^2 = 2*((2*n*gamma^2*lamda/lambda_u)-1) undulator_parameter_sqr = ( - 4.959368e-6 - * (order * gamma * gamma / (undulator_period_mm * photon_energy_kev)) - - 2 + 4.959368e-6 * (order * gamma * gamma / (undulator_period_mm * value)) - 2 ) if undulator_parameter_sqr < 0: raise ValueError( @@ -116,7 +149,8 @@ def calculate_gap_i09_hu( # K = undulator_parameter_max*exp(-pi*gap/lambda_u) # Calculating undulator_parameter_max gives: undulator_parameter_max = _calculate_undulator_parameter_max( - look_up_table[order][MAGNET_FIELD_COLUMN], undulator_period_mm + look_up_table.get_value(HARMONICS_COLUMN_NAME, order, MAGNET_FIELD_COLUMN_NAME), + undulator_period_mm, ) # Finnaly, rearranging the equation: @@ -124,22 +158,18 @@ def calculate_gap_i09_hu( gap = ( (undulator_period_mm / np.pi) * np.log(undulator_parameter_max / undulator_parameter) - + look_up_table[order][GAP_OFFSET_COLUMN] + + look_up_table.get_value(HARMONICS_COLUMN_NAME, order, GAP_OFFSET_COLUMN_NAME) + gap_offset ) - LOGGER.debug( - f"Calculated gap is {gap}mm for energy {photon_energy_kev}keV at order {order}" - ) + LOGGER.debug(f"Calculated gap is {gap}mm for energy {value}keV at order {order}") return gap def calculate_energy_i09_hu( - gap: float, - look_up_table: dict[int, "np.ndarray"], + lut: LookUpTableProvider, + value: float, order: int = 1, - gap_offset: float = 0.0, - undulator_period_mm: int = 27, ) -> float: """ Calculate the photon energy produced by the undulator at a given gap and harmonic order. @@ -149,21 +179,30 @@ def calculate_energy_i09_hu( gap (float): Undulator gap in millimeters. look_up_table (dict[int, np.ndarray]): Lookup table containing undulator and beamline parameters for each harmonic order. order (int, optional): Harmonic order for which to calculate the energy. Defaults to 1. - gap_offset (float, optional): Additional gap offset to apply (in mm). Defaults to 0.0. - undulator_period_mm (int, optional): Undulator period in mm. Defaults to 27. Returns: float: Calculated photon energy in keV. """ - _validate_order(order, look_up_table) + gap_offset: float = 0.0 + undulator_period_mm: int = 27 + + look_up_table: GenericLookupTable = lut.get_look_up_table() + _validate_order(look_up_table, order) gamma = _calculate_gamma(look_up_table, order) undulator_parameter_max = _calculate_undulator_parameter_max( - look_up_table[order][MAGNET_FIELD_COLUMN], undulator_period_mm + look_up_table.get_value(HARMONICS_COLUMN_NAME, order, MAGNET_FIELD_COLUMN_NAME), + undulator_period_mm, ) undulator_parameter = undulator_parameter_max / np.exp( - (gap - look_up_table[order][GAP_OFFSET_COLUMN] - gap_offset) + ( + value + - look_up_table.get_value( + HARMONICS_COLUMN_NAME, order, GAP_OFFSET_COLUMN_NAME + ) + - gap_offset + ) / (undulator_period_mm / np.pi) ) energy_kev = ( diff --git a/src/dodal/testing/fixtures/devices/hard_undulator.py b/src/dodal/testing/fixtures/devices/hard_undulator.py new file mode 100644 index 0000000000..f74ce3cd77 --- /dev/null +++ b/src/dodal/testing/fixtures/devices/hard_undulator.py @@ -0,0 +1,43 @@ +from unittest.mock import MagicMock + +import pytest +from daq_config_server.client import ConfigServer +from daq_config_server.models.converters.lookup_tables import GenericLookupTable + +from dodal.devices.i09_1_shared.hard_undulator_functions import ( + I09_HU_UNDULATOR_LUT_COLUMN_NAMES, +) + +lut = GenericLookupTable( + column_names=I09_HU_UNDULATOR_LUT_COLUMN_NAMES, + rows=[ + [1, 3.00089, 0.98928, 2.12, 3.05, 14.265, 23.72, 0.0], + [2, 3.04129, 1.02504, 2.5, 2.8, 5.05165, 8.88007, 0.0], + [3, 3.05798, 1.03065, 2.4, 4.3, 5.2, 8.99036, 0.0], + [4, 3.03635, 1.02332, 3.2, 5.7, 5.26183, 8.9964, 0.0], + [5, 3.06334, 1.03294, 4.0, 7.2, 5.22735, 9.02065, 0.0], + [6, 3.04963, 1.02913, 4.7, 8.6, 5.13939, 9.02527, 0.0], + [7, 3.06515, 1.03339, 5.5, 10.1, 5.12684, 9.02602, 0.0], + [8, 3.05775, 1.03223, 6.3, 11.5, 5.16289, 9.02873, 0.0], + [9, 3.06829, 1.03468, 7.1, 13.0, 5.16357, 9.03049, 0.0], + [10, 3.06164, 1.03328, 7.9, 14.4, 5.17205, 9.02845, 0.0], + [11, 3.07056, 1.03557, 8.6, 15.9, 5.1135, 9.0475, 0.0], + [12, 3.06627, 1.03482, 9.4, 17.3, 5.12051, 9.02826, 0.0], + [13, 3.07176, 1.03623, 10.2, 18.3, 5.13027, 8.8494, 0.0], + [14, 3.06964, 1.03587, 11.0, 18.3, 5.13985, 8.30146, 0.0], + [15, 3.06515, 1.03391, 11.8, 18.3, 5.14643, 7.8238, 0.0], + ], +) + + +@pytest.fixture +def mock_config_client() -> ConfigServer: + mock_config_client = ConfigServer() + mock_config_client.get_file_contents = MagicMock(spec=["get_file_contents"]) + + def my_side_effect(file_path, desired_return_type, reset_cached_result): + assert reset_cached_result is True + return lut + + mock_config_client.get_file_contents.side_effect = my_side_effect + return mock_config_client diff --git a/tests/devices/i09_1_shared/test_hard_energy.py b/tests/devices/i09_1_shared/test_hard_energy.py index 210ef3434a..3b0f2143f9 100644 --- a/tests/devices/i09_1_shared/test_hard_energy.py +++ b/tests/devices/i09_1_shared/test_hard_energy.py @@ -3,6 +3,7 @@ import pytest from bluesky.plan_stubs import mv from bluesky.run_engine import RunEngine +from daq_config_server.client import ConfigServer from ophyd_async.core import init_devices from ophyd_async.testing import ( assert_reading, @@ -19,15 +20,11 @@ HardInsertionDeviceEnergy, calculate_energy_i09_hu, calculate_gap_i09_hu, - get_hu_lut_as_dict, ) +from dodal.devices.i09_1_shared.hard_undulator_functions import I09HardLutProvider from dodal.devices.undulator import UndulatorInMm, UndulatorOrder -from tests.devices.i09_1_shared.test_data import TEST_HARD_UNDULATOR_LUT - -@pytest.fixture -async def lut_dictionary() -> dict: - return await get_hu_lut_as_dict(TEST_HARD_UNDULATOR_LUT) +pytest_plugins = ["dodal.testing.fixtures.devices.hard_undulator"] @pytest.fixture @@ -55,15 +52,15 @@ async def undulator_in_mm() -> UndulatorInMm: @pytest.fixture async def hu_id_energy( + mock_config_client: ConfigServer, undulator_order: UndulatorOrder, undulator_in_mm: UndulatorInMm, - lut_dictionary: dict, ) -> HardInsertionDeviceEnergy: async with init_devices(): hu_id_energy = HardInsertionDeviceEnergy( undulator_order=undulator_order, undulator=undulator_in_mm, - lut=lut_dictionary, + lut_provider=I09HardLutProvider(mock_config_client, "path/to/lut"), gap_to_energy_func=calculate_energy_i09_hu, energy_to_gap_func=calculate_gap_i09_hu, ) diff --git a/tests/devices/i09_1_shared/test_undulator_functions.py b/tests/devices/i09_1_shared/test_undulator_functions.py index 6fccd72e6a..d14f52562e 100644 --- a/tests/devices/i09_1_shared/test_undulator_functions.py +++ b/tests/devices/i09_1_shared/test_undulator_functions.py @@ -1,15 +1,23 @@ import re +from unittest.mock import patch import pytest +from daq_config_server.client import ConfigServer -from dodal.devices.i09_1_shared import calculate_gap_i09_hu, get_hu_lut_as_dict -from dodal.devices.i09_1_shared.hard_undulator_functions import calculate_energy_i09_hu -from tests.devices.i09_1_shared.test_data import TEST_HARD_UNDULATOR_LUT +from dodal.devices.i09_1_shared import calculate_gap_i09_hu +from dodal.devices.i09_1_shared.hard_undulator_functions import ( + I09HardLutProvider, + calculate_energy_i09_hu, +) + +pytest_plugins = ["dodal.testing.fixtures.devices.hard_undulator"] -@pytest.fixture -async def lut_dictionary() -> dict: - return await get_hu_lut_as_dict(TEST_HARD_UNDULATOR_LUT) +@pytest.fixture() +def lut_provider( + mock_config_client: ConfigServer, +) -> I09HardLutProvider: + return I09HardLutProvider(mock_config_client, "path/to/lut") @pytest.mark.parametrize( @@ -24,9 +32,9 @@ async def test_calculate_gap_from_energy( energy: float, order: int, expected_gap: float, - lut_dictionary: dict, + lut_provider: I09HardLutProvider, ): - assert calculate_gap_i09_hu(energy, lut_dictionary, order) == pytest.approx( + assert calculate_gap_i09_hu(lut_provider, energy, order) == pytest.approx( expected_gap, abs=0.0001 ) @@ -43,26 +51,43 @@ async def test_calculate_energy_from_gap( energy: float, order: int, gap: float, - lut_dictionary: dict, + lut_provider: I09HardLutProvider, ): - assert calculate_energy_i09_hu(gap, lut_dictionary, order) == pytest.approx( + assert calculate_energy_i09_hu(lut_provider, gap, order) == pytest.approx( energy, abs=0.0001 ) async def test_calculate_gap_from_energy_wrong_order( - lut_dictionary: dict, + lut_provider: I09HardLutProvider, ): wrong_order = 100 with pytest.raises( ValueError, match=re.escape(f"Order parameter {wrong_order} not found in lookup table"), ): - calculate_gap_i09_hu(30, lut_dictionary, wrong_order) + calculate_gap_i09_hu(lut_provider, 30, wrong_order) +async def test_calculate_gap_from_energy_wrong_energy( + lut_provider: I09HardLutProvider, +): + with pytest.raises( + ValueError, + match=re.escape( + "Requested energy 30 keV is out of range for harmonic 1: [2.12, 3.05] keV" + ), + ): + calculate_gap_i09_hu(lut_provider, 30, 1) + + +@patch( + "dodal.devices.i09_1_shared.hard_undulator_functions._validate_energy_in_range", + autospec=True, +) async def test_calculate_gap_from_energy_wrong_k( - lut_dictionary: dict, + validate_energy_in_range_mock, + lut_provider: I09HardLutProvider, ): with pytest.raises( ValueError, @@ -70,4 +95,4 @@ async def test_calculate_gap_from_energy_wrong_k( "Diffraction parameter squared must be positive! Calculated value -1.78" ), ): - calculate_gap_i09_hu(30, lut_dictionary, 1) + calculate_gap_i09_hu(lut_provider, 30, 1) diff --git a/uv.lock b/uv.lock index eca15cb4b9..b1b0eac365 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" }, @@ -1036,6 +1037,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" }, { url = "https://files.pythonhosted.org/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" }, { url = "https://files.pythonhosted.org/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" }, + { url = "https://files.pythonhosted.org/packages/80/d7/db0a5085035d05134f8c089643da2b44cc9b80647c39e93129c5ef170d8f/greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45", size = 601098, upload-time = "2025-12-04T15:07:11.898Z" }, { url = "https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" }, { url = "https://files.pythonhosted.org/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" }, { url = "https://files.pythonhosted.org/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" }, @@ -1043,6 +1045,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" }, { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" }, { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" }, { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" }, { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" }, { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" }, @@ -1050,6 +1053,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" }, { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" }, { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" }, + { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" }, { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" }, { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" }, { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" }, @@ -1057,6 +1061,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" }, { url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" }, { url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" }, + { url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload-time = "2025-12-04T15:07:15.789Z" }, { url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" }, { url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" }, { url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" }, @@ -1064,6 +1069,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" }, { url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" }, { url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" }, + { url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload-time = "2025-12-04T15:07:16.906Z" }, { url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" }, { url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" }, { url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" }, @@ -3831,6 +3837,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"