Skip to content

Commit da21c22

Browse files
committed
Attribute parsing with Controller.attributes_
1 parent 7d602ce commit da21c22

File tree

2 files changed

+95
-4
lines changed

2 files changed

+95
-4
lines changed

src/fastcs/controller.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ class SingleMapping:
1919

2020

2121
class BaseController:
22+
#! Attributes passed from the device at runtime.
23+
attributes: dict[str, Attribute]
24+
2225
def __init__(self, path: list[str] | None = None) -> None:
26+
if not hasattr(self, "attributes"):
27+
self.attributes = {}
2328
self._path: list[str] = path or []
2429
self.__sub_controller_tree: dict[str, BaseController] = {}
2530

@@ -40,9 +45,19 @@ def _bind_attrs(self) -> None:
4045
for attr_name in dir(self):
4146
attr = getattr(self, attr_name)
4247
if isinstance(attr, Attribute):
48+
if (
49+
attr_name in self.attributes
50+
and self.attributes[attr_name] is not attr
51+
):
52+
raise ValueError(
53+
f"`{type(self).__name__}` has conflicting attribute "
54+
f"`{attr_name}` already present in the attributes dict."
55+
)
4356
new_attribute = copy(attr)
4457
setattr(self, attr_name, new_attribute)
4558

59+
self.attributes[attr_name] = new_attribute
60+
4661
def register_sub_controller(self, name: str, sub_controller: SubController):
4762
if name in self.__sub_controller_tree.keys():
4863
raise ValueError(
@@ -69,7 +84,6 @@ def _get_single_mapping(controller: BaseController) -> SingleMapping:
6984
scan_methods: dict[str, Scan] = {}
7085
put_methods: dict[str, Put] = {}
7186
command_methods: dict[str, Command] = {}
72-
attributes: dict[str, Attribute] = {}
7387
for attr_name in dir(controller):
7488
attr = getattr(controller, attr_name)
7589
match attr:
@@ -79,11 +93,14 @@ def _get_single_mapping(controller: BaseController) -> SingleMapping:
7993
scan_methods[attr_name] = scan_method
8094
case WrappedMethod(fastcs_method=Command(enabled=True) as command_method):
8195
command_methods[attr_name] = command_method
82-
case Attribute(enabled=True):
83-
attributes[attr_name] = attr
8496

97+
enabled_attributes = {
98+
name: attribute
99+
for name, attribute in controller.attributes.items()
100+
if attribute.enabled
101+
}
85102
return SingleMapping(
86-
controller, scan_methods, put_methods, command_methods, attributes
103+
controller, scan_methods, put_methods, command_methods, enabled_attributes
87104
)
88105

89106

tests/test_controller.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import pytest
22

3+
from fastcs.attributes import AttrR
34
from fastcs.controller import (
45
Controller,
56
SubController,
67
_get_single_mapping,
78
_walk_mappings,
89
)
10+
from fastcs.datatypes import Int
911

1012

1113
def test_controller_nesting():
@@ -33,3 +35,75 @@ def test_controller_nesting():
3335
ValueError, match=r"SubController is already registered under .*"
3436
):
3537
controller.register_sub_controller("c", sub_controller)
38+
39+
40+
class SomeSubController(SubController):
41+
def __init__(self):
42+
super().__init__()
43+
44+
sub_attribute = AttrR(Int())
45+
46+
47+
class SomeController(Controller):
48+
annotated_attr = AttrR(Int())
49+
annotated_attr_not_defined_in_init: AttrR[int]
50+
equal_attr = AttrR(Int())
51+
annotated_and_equal_attr: AttrR[int] = AttrR(Int())
52+
53+
def __init__(self, sub_controller: SubController):
54+
self.attributes = {}
55+
56+
self.annotated_attr = AttrR(Int())
57+
self.attr_on_object = AttrR(Int())
58+
59+
self.attributes["_attributes_attr"] = AttrR(Int())
60+
self.attributes["_attributes_attr_equal"] = self.equal_attr
61+
62+
super().__init__()
63+
self.register_sub_controller("sub_controller", sub_controller)
64+
65+
66+
def test_attribute_parsing():
67+
sub_controller = SomeSubController()
68+
controller = SomeController(sub_controller)
69+
70+
mapping_walk = _walk_mappings(controller)
71+
72+
controller_mapping = next(mapping_walk)
73+
assert set(controller_mapping.attributes.keys()) == {
74+
"_attributes_attr",
75+
"annotated_attr",
76+
"attr_on_object",
77+
"_attributes_attr_equal",
78+
"annotated_and_equal_attr",
79+
"equal_attr",
80+
}
81+
82+
assert SomeController.equal_attr is not controller.equal_attr
83+
assert (
84+
SomeController.annotated_and_equal_attr
85+
is not controller.annotated_and_equal_attr
86+
)
87+
88+
sub_controller_mapping = next(mapping_walk)
89+
assert sub_controller_mapping.attributes == {
90+
"sub_attribute": sub_controller.sub_attribute,
91+
}
92+
93+
94+
def test_attribute_in_both_class_and_get_attributes():
95+
class FailingController(Controller):
96+
duplicate_attribute = AttrR(Int())
97+
98+
def __init__(self):
99+
self.attributes = {"duplicate_attribute": AttrR(Int())}
100+
super().__init__()
101+
102+
with pytest.raises(
103+
ValueError,
104+
match=(
105+
"`FailingController` has conflicting attribute `duplicate_attribute` "
106+
"already present in the attributes dict."
107+
),
108+
):
109+
next(_walk_mappings(FailingController()))

0 commit comments

Comments
 (0)