diff --git a/src/fastcs/backend.py b/src/fastcs/backend.py index 2330dd26c..30d6e8354 100644 --- a/src/fastcs/backend.py +++ b/src/fastcs/backend.py @@ -30,7 +30,10 @@ def __init__( def _link_process_tasks(self): for controller_api in self.controller_api.walk_api(): _link_put_tasks(controller_api) - _link_attribute_sender_class(controller_api, self._controller) + _link_attribute_sender_class( + controller_api, + self._controller.get_controller_by_path(controller_api.path), + ) def __del__(self): self._stop_scan_tasks() @@ -74,7 +77,7 @@ def _link_put_tasks(controller_api: ControllerAPI) -> None: def _link_attribute_sender_class( - controller_api: ControllerAPI, controller: Controller + controller_api: ControllerAPI, controller: BaseController ) -> None: for attr_name, attribute in controller_api.attributes.items(): match attribute: diff --git a/src/fastcs/controller.py b/src/fastcs/controller.py index c7fbc45fa..7067489f5 100755 --- a/src/fastcs/controller.py +++ b/src/fastcs/controller.py @@ -4,6 +4,7 @@ from typing import get_type_hints from fastcs.attributes import Attribute +from fastcs.exceptions import FastCSException class BaseController: @@ -101,6 +102,19 @@ def register_sub_controller(self, name: str, sub_controller: SubController): def get_sub_controllers(self) -> dict[str, SubController]: return self.__sub_controller_tree + def __getitem__(self, key: str) -> SubController: + if key not in self.__sub_controller_tree: + raise FastCSException(f"Controller {self} has no sub controller '{key}'") + + return self.__sub_controller_tree[key] + + def get_controller_by_path(self, path: list[str]) -> BaseController: + if path: + sub_controller, remaining_path = self[path[0]], path[1:] + return sub_controller.get_controller_by_path(remaining_path) + + return self + class Controller(BaseController): """Top-level controller for a device. diff --git a/tests/test_backend.py b/tests/test_backend.py index 2d578c547..417de5fc7 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -1,10 +1,13 @@ import asyncio +from unittest.mock import call + +from pytest_mock import MockerFixture from fastcs.attributes import AttrRW from fastcs.backend import Backend, build_controller_api -from fastcs.controller import Controller +from fastcs.controller import Controller, SubController from fastcs.cs_methods import Command -from fastcs.datatypes import Int +from fastcs.datatypes import Float, Int from fastcs.wrappers import command, scan @@ -36,6 +39,29 @@ async def test_wrapper(): loop.run_until_complete(test_wrapper()) +def test_backend_link_attribute_sender(mocker: MockerFixture): + class MyController(Controller): + attr = AttrRW(Float()) + + class MySubController(SubController): + sub_attr = AttrRW(Int()) + + controller = MyController() + sub_controller = MySubController() + controller.register_sub_controller("sub", sub_controller) + + loop = asyncio.get_event_loop() + create_sender_mock = mocker.patch("fastcs.backend._create_sender_callback") + Backend(controller, loop) + + create_sender_mock.assert_has_calls( + [ + call(controller.attr, controller), + call(sub_controller.sub_attr, sub_controller), + ] + ) + + def test_controller_api(): class MyTestController(Controller): attr1: AttrRW[int] = AttrRW(Int()) diff --git a/tests/test_controller.py b/tests/test_controller.py index b0e87fd07..eebd76a04 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -3,6 +3,7 @@ from fastcs.attributes import AttrR from fastcs.controller import Controller, SubController from fastcs.datatypes import Int +from fastcs.exceptions import FastCSException def test_controller_nesting(): @@ -16,7 +17,9 @@ def test_controller_nesting(): assert sub_controller.path == ["a"] assert sub_sub_controller.path == ["a", "b"] assert controller.get_sub_controllers() == {"a": sub_controller} + assert controller["a"] == sub_controller assert sub_controller.get_sub_controllers() == {"b": sub_sub_controller} + assert controller["a"]["b"] == sub_sub_controller with pytest.raises( ValueError, match=r"Controller .* already has a SubController registered as .*" @@ -112,3 +115,12 @@ class FailingController(SomeController): ), ): FailingController(SomeSubController()) + + +def test_controller_getitem(controller): + assert controller["SubController01"] == controller.get_sub_controllers().get( + "SubController01" + ) + + with pytest.raises(FastCSException, match=r"Controller .* has no sub controller.*"): + controller["DoesNotExist"]