Skip to content

Commit

Permalink
Attribute parsing with Controller class definitions
Browse files Browse the repository at this point in the history
A
  • Loading branch information
evalott100 committed Nov 26, 2024
1 parent cec9fda commit 93af3e4
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 32 deletions.
4 changes: 4 additions & 0 deletions src/fastcs/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ def register_sub_controller(self, name: str, sub_controller: SubController):
def get_sub_controllers(self) -> dict[str, BaseController]:
return self.__sub_controller_tree

def get_attributes(self) -> dict[str, Attribute]:
"""For getting any attributes which aren't defined on the class itself."""
return {}


class Controller(BaseController):
"""Top-level controller for a device.
Expand Down
33 changes: 29 additions & 4 deletions src/fastcs/mapping.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from collections.abc import Iterator
from dataclasses import dataclass
from typing import get_type_hints

from .attributes import Attribute
from .attributes import Attribute, AttrR, AttrW, AttrRW
from .controller import BaseController, Controller
from .cs_methods import Command, Put, Scan
from .wrappers import WrappedMethod
Expand Down Expand Up @@ -41,7 +42,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:
Expand All @@ -51,8 +51,33 @@ 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

attributes: dict[str, Attribute] = {}
for name in list(get_type_hints(type(controller))) + dir(type(controller)):
if (
isinstance(
(attr := getattr(controller, name, None)), AttrRW | AttrR | AttrW
)
and attr.enabled
):
attributes[name] = attr

object_defined_attributes = {
name: attr for name, attr in controller.get_attributes().items() if attr.enabled
}

if conflicting_keys := {
key
for key in object_defined_attributes.keys() & attributes.keys()
if object_defined_attributes[key] is not attributes[key]
}:
raise TypeError(
f"{controller} has conflicting attributes between those passed in"
"`get_attributes` and those obtained from the class definition: "
f"{conflicting_keys}"
)

attributes.update(object_defined_attributes)

return SingleMapping(
controller, scan_methods, put_methods, command_methods, attributes
Expand Down
28 changes: 14 additions & 14 deletions tests/backends/epics/test_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,44 +49,44 @@ def test_get_components(mapping):
children=[
SignalR(
name="ReadInt",
read_pv="DEVICE:SubController01:ReadInt",
read_pv="DEVICE:SubController02:ReadInt",
read_widget=TextRead(),
)
],
),
SignalR(name="BigEnum", read_pv="DEVICE:BigEnum", read_widget=TextRead()),
SignalR(name="ReadBool", read_pv="DEVICE:ReadBool", read_widget=LED()),
SignalR(
name="ReadInt",
read_pv="DEVICE:ReadInt",
read_widget=TextRead(),
),
SignalRW(
name="ReadWriteFloat",
write_pv="DEVICE:ReadWriteFloat",
name="ReadWriteInt",
write_pv="DEVICE:ReadWriteInt",
write_widget=TextWrite(),
read_pv="DEVICE:ReadWriteFloat_RBV",
read_pv="DEVICE:ReadWriteInt_RBV",
read_widget=TextRead(),
),
SignalRW(
name="ReadWriteInt",
write_pv="DEVICE:ReadWriteInt",
name="ReadWriteFloat",
write_pv="DEVICE:ReadWriteFloat",
write_widget=TextWrite(),
read_pv="DEVICE:ReadWriteInt_RBV",
read_pv="DEVICE:ReadWriteFloat_RBV",
read_widget=TextRead(),
),
SignalR(name="ReadBool", read_pv="DEVICE:ReadBool", read_widget=LED()),
SignalW(
name="WriteBool",
write_pv="DEVICE:WriteBool",
write_widget=ToggleButton(),
),
SignalRW(
name="StringEnum",
read_pv="DEVICE:StringEnum_RBV",
read_widget=TextRead(format=TextFormat.string),
write_pv="DEVICE:StringEnum",
write_widget=ComboBox(choices=["red", "green", "blue"]),
),
SignalW(
name="WriteBool",
write_pv="DEVICE:WriteBool",
write_widget=ToggleButton(),
),
SignalR(name="BigEnum", read_pv="DEVICE:BigEnum", read_widget=TextRead()),
SignalX(
name="Go",
write_pv="DEVICE:Go",
Expand Down
3 changes: 2 additions & 1 deletion tests/backends/epics/test_ioc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any
from typing import Any, get_type_hints

import pytest
from pytest_mock import MockerFixture
Expand Down Expand Up @@ -401,6 +401,7 @@ def test_long_pv_names_discarded(mocker: MockerFixture):
long_rw_name = "attr_rw_with_a_reallyreally_long_name_that_is_too_long_for_RBV"
assert long_name_controller.attr_rw_short_name.enabled
assert getattr(long_name_controller, long_attr_name).enabled

EpicsIOC(DEVICE, long_name_mapping)
assert long_name_controller.attr_rw_short_name.enabled
assert not getattr(long_name_controller, long_attr_name).enabled
Expand Down
8 changes: 4 additions & 4 deletions tests/backends/tango/test_dsr.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ def tango_context(self, assertable_controller):

def test_list_attributes(self, tango_context):
assert list(tango_context.get_attribute_list()) == [
"BigEnum",
"ReadBool",
"ReadInt",
"ReadWriteFloat",
"ReadWriteInt",
"StringEnum",
"ReadWriteFloat",
"ReadBool",
"WriteBool",
"StringEnum",
"BigEnum",
"SubController01_ReadInt",
"SubController02_ReadInt",
"State",
Expand Down
18 changes: 9 additions & 9 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,6 @@ class TestSubController(SubController):


class TestController(Controller):
def __init__(self) -> None:
super().__init__()

self._sub_controllers: list[TestSubController] = []
for index in range(1, 3):
controller = TestSubController()
self._sub_controllers.append(controller)
self.register_sub_controller(f"SubController{index:02d}", controller)

read_int: AttrR = AttrR(Int(), handler=TestUpdater())
read_write_int: AttrRW = AttrRW(Int(), handler=TestHandler())
read_write_float: AttrRW = AttrRW(Float())
Expand All @@ -77,6 +68,15 @@ def __init__(self) -> None:
allowed_values=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
)

def __init__(self) -> None:
super().__init__()

self._sub_controllers: list[TestSubController] = []
for index in range(1, 3):
controller = TestSubController()
self._sub_controllers.append(controller)
self.register_sub_controller(f"SubController{index:02d}", controller)

initialised = False
connected = False
count = 0
Expand Down
34 changes: 34 additions & 0 deletions tests/test_controller.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import pytest

from fastcs.attributes import Attribute, AttrR
from fastcs.controller import Controller, SubController
from fastcs.datatypes import Int
from fastcs.mapping import _get_single_mapping, _walk_mappings


Expand Down Expand Up @@ -29,3 +31,35 @@ def test_controller_nesting():
ValueError, match=r"SubController is already registered under .*"
):
controller.register_sub_controller("c", sub_controller)


def test_attribute_parsing():
runtime_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 get_attributes(self) -> dict[str, Attribute]:
return {"get_attributes_attr": runtime_attribute}

def __init__(self):
self.annotated_attr = AttrR(Int())
super().__init__()

controller = SomeController()
mapping = next(_walk_mappings(controller))
assert mapping.attributes == {
"get_attributes_attr": runtime_attribute,
"annotated_attr": controller.annotated_attr,
"equal_attr": controller.equal_attr,
"annotated_and_equal_attr": controller.annotated_and_equal_attr,
}

assert SomeController.equal_attr is not controller.equal_attr
assert (
SomeController.annotated_and_equal_attr
is not controller.annotated_and_equal_attr
)

0 comments on commit 93af3e4

Please sign in to comment.