From ddd9e7ef3259c7c5bbe95f2a389b9a5f6ef94472 Mon Sep 17 00:00:00 2001 From: Gary Yendell Date: Tue, 6 Aug 2024 16:09:27 +0000 Subject: [PATCH 1/2] Update to new Backend API Remove asyncio.run in EigerController __init__ --- pyproject.toml | 2 +- src/eiger_fastcs/__main__.py | 17 +++++------------ src/eiger_fastcs/eiger_controller.py | 19 ++----------------- tests/system/test_introspection.py | 4 ++-- 4 files changed, 10 insertions(+), 32 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d2539f0..667279b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ classifiers = [ description = "Eiger control system integration with FastCS" dependencies = [ "aiohttp", - "fastcs~=0.5.0", + "fastcs~=0.6.0", "numpy", "pillow", "typer", diff --git a/src/eiger_fastcs/__main__.py b/src/eiger_fastcs/__main__.py index 623ec6e..8ad6b43 100644 --- a/src/eiger_fastcs/__main__.py +++ b/src/eiger_fastcs/__main__.py @@ -5,7 +5,6 @@ from fastcs.backends.asyncio_backend import AsyncioBackend from fastcs.backends.epics.backend import EpicsBackend from fastcs.backends.epics.gui import EpicsGUIOptions -from fastcs.mapping import Mapping from eiger_fastcs import __version__ from eiger_fastcs.eiger_controller import EigerController @@ -51,27 +50,21 @@ def ioc( ): ui_path = OPI_PATH if OPI_PATH.is_dir() else Path.cwd() - mapping = get_controller_mapping(ip, port) + controller = EigerController(ip, port) - backend = EpicsBackend(mapping, pv_prefix) + backend = EpicsBackend(controller, pv_prefix) backend.create_gui( EpicsGUIOptions(output_path=ui_path / "eiger.bob", title=f"Eiger - {pv_prefix}") ) - backend.get_ioc().run() + backend.run() @app.command() def asyncio(ip: str = EigerIp, port: int = EigerPort): - mapping = get_controller_mapping(ip, port) - - backend = AsyncioBackend(mapping) - backend.run_interactive_session() - - -def get_controller_mapping(ip: str, port: int) -> Mapping: controller = EigerController(ip, port) - return Mapping(controller) + backend = AsyncioBackend(controller) + backend.run_interactive_session() # test with: python -m eiger_fastcs diff --git a/src/eiger_fastcs/eiger_controller.py b/src/eiger_fastcs/eiger_controller.py index 46c5ecb..76c1390 100644 --- a/src/eiger_fastcs/eiger_controller.py +++ b/src/eiger_fastcs/eiger_controller.py @@ -6,8 +6,7 @@ from typing import Any, Literal import numpy as np -from attr import Attribute -from fastcs.attributes import AttrR, AttrRW, AttrW +from fastcs.attributes import Attribute, AttrR, AttrRW, AttrW from fastcs.controller import Controller from fastcs.datatypes import Bool, Float, Int, String from fastcs.wrappers import command, scan @@ -177,14 +176,6 @@ def __init__(self, ip: str, port: int) -> None: self._parameter_updates: set[str] = set() self._parameter_update_lock = asyncio.Lock() - # Initialize parameters from hardware - run on ephemeral asyncio loop - # TODO: Make the backend asyncio loop available earlier - asyncio.run(self.initialise()) - - async def connect(self) -> None: - """Reopen connection on backend asyncio loop""" - self.connection.open() - async def initialise(self) -> None: """Create attributes by introspecting detector. @@ -197,7 +188,7 @@ async def initialise(self) -> None: state_val = await self.connection.get("detector/api/1.8.0/status/state") if state_val["value"] == "na": print("Initializing Detector") - await self.connection.put("detector/api/1.8.0/command/initialize") + await self.initialize() try: parameters = await self._introspect_detector() @@ -210,8 +201,6 @@ async def initialise(self) -> None: for name, attribute in attributes.items(): setattr(self, name, attribute) - await self.connection.close() - async def _introspect_detector(self) -> list[EigerParameter]: parameters = [] for subsystem, mode in product( @@ -302,10 +291,6 @@ def _tag_key_clashes(parameters: list[EigerParameter]): other.has_unique_key = False break - async def close(self) -> None: - """Closing HTTP connection with device""" - await self.connection.close() - @detector_command async def initialize(self): await self.connection.put(command_uri("initialize")) diff --git a/tests/system/test_introspection.py b/tests/system/test_introspection.py index dbd2300..d40f7c9 100644 --- a/tests/system/test_introspection.py +++ b/tests/system/test_introspection.py @@ -65,7 +65,7 @@ async def test_introspection(sim_eiger_controller: EigerController): controller = sim_eiger_controller # controller = eiger_controller - await controller.connect() + controller.connection.open() _parameters = await controller._introspect_detector() controller._tag_key_clashes(_parameters) parameters = {p.name: _serialise_parameter(p) for p in _parameters} @@ -78,4 +78,4 @@ async def test_introspection(sim_eiger_controller: EigerController): assert parameters == expected_parameters, "Detector API does not match" - await controller.close() + await controller.connection.close() From eda516cc58e63e08d7e3197785619d9404ea4610 Mon Sep 17 00:00:00 2001 From: Gary Yendell Date: Mon, 19 Aug 2024 13:22:31 +0000 Subject: [PATCH 2/2] Add some tests --- pyproject.toml | 1 + tests/system/test_introspection.py | 9 ++++++ tests/test_controller.py | 45 ++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 tests/test_controller.py diff --git a/pyproject.toml b/pyproject.toml index 667279b..2104f71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dev = [ "pytest", "pytest-asyncio", "pytest-cov", + "pytest-mock", "ruff", "sphinx-autobuild", "sphinx-copybutton", diff --git a/tests/system/test_introspection.py b/tests/system/test_introspection.py index d40f7c9..bb88406 100644 --- a/tests/system/test_introspection.py +++ b/tests/system/test_introspection.py @@ -6,6 +6,8 @@ from time import sleep import pytest +from fastcs.attributes import AttrR +from fastcs.datatypes import Float from eiger_fastcs.eiger_controller import EigerController, EigerParameter @@ -78,4 +80,11 @@ async def test_introspection(sim_eiger_controller: EigerController): assert parameters == expected_parameters, "Detector API does not match" + attributes = controller._create_attributes(_parameters) + + assert len(attributes) == 91 + assert isinstance(attributes["humidity"], AttrR) + assert isinstance(attributes["humidity"].datatype, Float) + assert attributes["humidity"]._group == "DetectorStatus" + await controller.connection.close() diff --git a/tests/test_controller.py b/tests/test_controller.py new file mode 100644 index 0000000..dea76ae --- /dev/null +++ b/tests/test_controller.py @@ -0,0 +1,45 @@ +import pytest +from pytest_mock import MockerFixture + +from eiger_fastcs.eiger_controller import EigerController + + +@pytest.mark.asyncio +async def test_initialise(mocker: MockerFixture): + controller = EigerController("127.0.0.1", 80) + + connection = mocker.patch.object(controller, "connection") + connection.get = mocker.AsyncMock() + connection.get.return_value = {"value": "idle"} + initialize = mocker.patch.object(controller, "initialize") + introspect = mocker.patch.object(controller, "_introspect_detector") + create_attributes = mocker.patch.object(controller, "_create_attributes") + attr = mocker.MagicMock() + create_attributes.return_value = {"attr_name": attr} + + await controller.initialise() + + connection.get.assert_called_once_with("detector/api/1.8.0/status/state") + initialize.assert_not_called() + introspect.assert_awaited_once_with() + create_attributes.assert_called_once_with(introspect.return_value) + assert controller.attr_name == attr, "Attribute not added to controller" + + +@pytest.mark.asyncio +async def test_initialise_state_na(mocker: MockerFixture): + controller = EigerController("127.0.0.1", 80) + + connection = mocker.patch.object(controller, "connection") + connection.get = mocker.AsyncMock() + connection.get.return_value = {"value": "na"} + initialize = mocker.patch.object(controller, "initialize") + introspect = mocker.patch.object(controller, "_introspect_detector") + create_attributes = mocker.patch.object(controller, "_create_attributes") + + await controller.initialise() + + connection.get.assert_called_once_with("detector/api/1.8.0/status/state") + initialize.assert_awaited_once_with() + introspect.assert_awaited_once_with() + create_attributes.assert_called_once_with(introspect.return_value)