Skip to content

Commit f45ac5c

Browse files
committed
Added root_attribute
`root_attribute` is parsed on `register_sub_controller`
1 parent d842d40 commit f45ac5c

File tree

2 files changed

+51
-4
lines changed

2 files changed

+51
-4
lines changed

src/fastcs/controller.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ class SingleMapping:
2020

2121

2222
class BaseController:
23-
#! Attributes passed from the device at runtime.
23+
#: Attributes passed from the device at runtime.
2424
attributes: dict[str, Attribute]
2525

2626
def __init__(self, path: list[str] | None = None) -> None:
2727
if not hasattr(self, "attributes"):
2828
self.attributes = {}
2929
self._path: list[str] = path or []
30-
self.__sub_controller_tree: dict[str, BaseController] = {}
30+
self.__sub_controller_tree: dict[str, SubController] = {}
3131

3232
self._bind_attrs()
3333

@@ -48,6 +48,9 @@ def _bind_attrs(self) -> None:
4848
class_type_hints = get_type_hints(type(self))
4949

5050
for attr_name in {**class_dir, **class_type_hints}:
51+
if attr_name == "root_attribute":
52+
continue
53+
5154
attr = getattr(self, attr_name, None)
5255
if isinstance(attr, Attribute):
5356
if (
@@ -60,7 +63,6 @@ def _bind_attrs(self) -> None:
6063
)
6164
new_attribute = copy(attr)
6265
setattr(self, attr_name, new_attribute)
63-
6466
self.attributes[attr_name] = new_attribute
6567

6668
def register_sub_controller(self, name: str, sub_controller: SubController):
@@ -72,7 +74,16 @@ def register_sub_controller(self, name: str, sub_controller: SubController):
7274
self.__sub_controller_tree[name] = sub_controller
7375
sub_controller.set_path(self.path + [name])
7476

75-
def get_sub_controllers(self) -> dict[str, BaseController]:
77+
if isinstance(sub_controller.root_attribute, Attribute):
78+
if name in self.attributes:
79+
raise TypeError(
80+
f"Cannot set SubController `{name}` root attribute "
81+
f"on the parent controller `{type(self).__name__}` "
82+
f"as it already has an attribute of that name."
83+
)
84+
self.attributes[name] = sub_controller.root_attribute
85+
86+
def get_sub_controllers(self) -> dict[str, SubController]:
7687
return self.__sub_controller_tree
7788

7889
def get_controller_mappings(self) -> list[SingleMapping]:
@@ -104,6 +115,7 @@ def _get_single_mapping(controller: BaseController) -> SingleMapping:
104115
for name, attribute in controller.attributes.items()
105116
if attribute.enabled
106117
}
118+
107119
return SingleMapping(
108120
controller, scan_methods, put_methods, command_methods, enabled_attributes
109121
)
@@ -135,5 +147,7 @@ class SubController(BaseController):
135147
it as part of a larger device.
136148
"""
137149

150+
root_attribute: Attribute | None = None
151+
138152
def __init__(self) -> None:
139153
super().__init__()

tests/test_controller.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,36 @@ def test_attribute_parsing():
9191
assert sub_controller_mapping.attributes == {
9292
"sub_attribute": sub_controller.sub_attribute,
9393
}
94+
95+
96+
def test_attribute_in_both_class_and_get_attributes():
97+
class FailingController(Controller):
98+
duplicate_attribute = AttrR(Int())
99+
100+
def __init__(self):
101+
self.attributes = {"duplicate_attribute": AttrR(Int())}
102+
super().__init__()
103+
104+
with pytest.raises(
105+
ValueError,
106+
match=(
107+
"`FailingController` has conflicting attribute `duplicate_attribute` "
108+
"already present in the attributes dict."
109+
),
110+
):
111+
FailingController()
112+
113+
114+
def test_root_attribute():
115+
class FailingController(SomeController):
116+
sub_controller = AttrR(Int())
117+
118+
with pytest.raises(
119+
TypeError,
120+
match=(
121+
"Cannot set SubController `sub_controller` root attribute "
122+
"on the parent controller `FailingController` as it already "
123+
"has an attribute of that name."
124+
),
125+
):
126+
next(_walk_mappings(FailingController(SomeSubController())))

0 commit comments

Comments
 (0)