diff --git a/src/fastcs/controller.py b/src/fastcs/controller.py index 0928ba0b..1a165539 100755 --- a/src/fastcs/controller.py +++ b/src/fastcs/controller.py @@ -19,7 +19,12 @@ class SingleMapping: class BaseController: + #! Attributes passed from the device at runtime. + attributes: dict[str, Attribute] + def __init__(self, path: list[str] | None = None) -> None: + if not hasattr(self, "attributes"): + self.attributes = {} self._path: list[str] = path or [] self.__sub_controller_tree: dict[str, BaseController] = {} @@ -40,9 +45,19 @@ def _bind_attrs(self) -> None: for attr_name in dir(self): attr = getattr(self, attr_name) if isinstance(attr, Attribute): + if ( + attr_name in self.attributes + and self.attributes[attr_name] is not attr + ): + raise ValueError( + f"`{type(self).__name__}` has conflicting attribute " + f"`{attr_name}` already present in the attributes dict." + ) new_attribute = copy(attr) setattr(self, attr_name, new_attribute) + self.attributes[attr_name] = new_attribute + def register_sub_controller(self, name: str, sub_controller: SubController): if name in self.__sub_controller_tree.keys(): raise ValueError( @@ -69,7 +84,6 @@ def _get_single_mapping(controller: BaseController) -> SingleMapping: scan_methods: dict[str, Scan] = {} put_methods: dict[str, Put] = {} command_methods: dict[str, Command] = {} - attributes: dict[str, Attribute] = {} for attr_name in dir(controller): attr = getattr(controller, attr_name) match attr: @@ -79,11 +93,14 @@ def _get_single_mapping(controller: BaseController) -> SingleMapping: scan_methods[attr_name] = scan_method case WrappedMethod(fastcs_method=Command(enabled=True) as command_method): command_methods[attr_name] = command_method - case Attribute(enabled=True): - attributes[attr_name] = attr + enabled_attributes = { + name: attribute + for name, attribute in controller.attributes.items() + if attribute.enabled + } return SingleMapping( - controller, scan_methods, put_methods, command_methods, attributes + controller, scan_methods, put_methods, command_methods, enabled_attributes ) diff --git a/tests/test_controller.py b/tests/test_controller.py index 44b3f0fa..7eb84eb7 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -1,11 +1,13 @@ import pytest +from fastcs.attributes import AttrR from fastcs.controller import ( Controller, SubController, _get_single_mapping, _walk_mappings, ) +from fastcs.datatypes import Int def test_controller_nesting(): @@ -33,3 +35,75 @@ def test_controller_nesting(): ValueError, match=r"SubController is already registered under .*" ): controller.register_sub_controller("c", sub_controller) + + +class SomeSubController(SubController): + def __init__(self): + super().__init__() + + sub_attribute = AttrR(Int()) + + +class SomeController(Controller): + annotated_attr = AttrR(Int()) + annotated_attr_not_defined_in_init: AttrR[int] + equal_attr = AttrR(Int()) + annotated_and_equal_attr: AttrR[int] = AttrR(Int()) + + def __init__(self, sub_controller: SubController): + self.attributes = {} + + self.annotated_attr = AttrR(Int()) + self.attr_on_object = AttrR(Int()) + + self.attributes["_attributes_attr"] = AttrR(Int()) + self.attributes["_attributes_attr_equal"] = self.equal_attr + + super().__init__() + self.register_sub_controller("sub_controller", sub_controller) + + +def test_attribute_parsing(): + sub_controller = SomeSubController() + controller = SomeController(sub_controller) + + mapping_walk = _walk_mappings(controller) + + controller_mapping = next(mapping_walk) + assert set(controller_mapping.attributes.keys()) == { + "_attributes_attr", + "annotated_attr", + "attr_on_object", + "_attributes_attr_equal", + "annotated_and_equal_attr", + "equal_attr", + } + + assert SomeController.equal_attr is not controller.equal_attr + assert ( + SomeController.annotated_and_equal_attr + is not controller.annotated_and_equal_attr + ) + + sub_controller_mapping = next(mapping_walk) + assert sub_controller_mapping.attributes == { + "sub_attribute": sub_controller.sub_attribute, + } + + +def test_attribute_in_both_class_and_get_attributes(): + class FailingController(Controller): + duplicate_attribute = AttrR(Int()) + + def __init__(self): + self.attributes = {"duplicate_attribute": AttrR(Int())} + super().__init__() + + with pytest.raises( + ValueError, + match=( + "`FailingController` has conflicting attribute `duplicate_attribute` " + "already present in the attributes dict." + ), + ): + next(_walk_mappings(FailingController()))