33from collections .abc import Iterator
44from copy import copy
55from dataclasses import dataclass
6+ from typing import get_type_hints
67
78from .attributes import Attribute
89from .cs_methods import Command , Put , Scan
@@ -19,9 +20,14 @@ class SingleMapping:
1920
2021
2122class BaseController :
23+ #: Attributes passed from the device at runtime.
24+ attributes : dict [str , Attribute ]
25+
2226 def __init__ (self , path : list [str ] | None = None ) -> None :
27+ if not hasattr (self , "attributes" ):
28+ self .attributes = {}
2329 self ._path : list [str ] = path or []
24- self .__sub_controller_tree : dict [str , BaseController ] = {}
30+ self .__sub_controller_tree : dict [str , SubController ] = {}
2531
2632 self ._bind_attrs ()
2733
@@ -37,11 +43,27 @@ def set_path(self, path: list[str]):
3743 self ._path = path
3844
3945 def _bind_attrs (self ) -> None :
40- for attr_name in dir (self ):
41- attr = getattr (self , attr_name )
46+ # Using a dictionary instead of a set to maintain order.
47+ class_dir = {key : None for key in dir (type (self ))}
48+ class_type_hints = get_type_hints (type (self ))
49+
50+ for attr_name in {** class_dir , ** class_type_hints }:
51+ if attr_name == "root_attribute" :
52+ continue
53+
54+ attr = getattr (self , attr_name , None )
4255 if isinstance (attr , Attribute ):
56+ if (
57+ attr_name in self .attributes
58+ and self .attributes [attr_name ] is not attr
59+ ):
60+ raise ValueError (
61+ f"`{ type (self ).__name__ } ` has conflicting attribute "
62+ f"`{ attr_name } ` already present in the attributes dict."
63+ )
4364 new_attribute = copy (attr )
4465 setattr (self , attr_name , new_attribute )
66+ self .attributes [attr_name ] = new_attribute
4567
4668 def register_sub_controller (self , name : str , sub_controller : SubController ):
4769 if name in self .__sub_controller_tree .keys ():
@@ -52,7 +74,16 @@ def register_sub_controller(self, name: str, sub_controller: SubController):
5274 self .__sub_controller_tree [name ] = sub_controller
5375 sub_controller .set_path (self .path + [name ])
5476
55- 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 ]:
5687 return self .__sub_controller_tree
5788
5889 def get_controller_mappings (self ) -> list [SingleMapping ]:
@@ -69,7 +100,6 @@ def _get_single_mapping(controller: BaseController) -> SingleMapping:
69100 scan_methods : dict [str , Scan ] = {}
70101 put_methods : dict [str , Put ] = {}
71102 command_methods : dict [str , Command ] = {}
72- attributes : dict [str , Attribute ] = {}
73103 for attr_name in dir (controller ):
74104 attr = getattr (controller , attr_name )
75105 match attr :
@@ -79,11 +109,15 @@ def _get_single_mapping(controller: BaseController) -> SingleMapping:
79109 scan_methods [attr_name ] = scan_method
80110 case WrappedMethod (fastcs_method = Command (enabled = True ) as command_method ):
81111 command_methods [attr_name ] = command_method
82- case Attribute (enabled = True ):
83- attributes [attr_name ] = attr
112+
113+ enabled_attributes = {
114+ name : attribute
115+ for name , attribute in controller .attributes .items ()
116+ if attribute .enabled
117+ }
84118
85119 return SingleMapping (
86- controller , scan_methods , put_methods , command_methods , attributes
120+ controller , scan_methods , put_methods , command_methods , enabled_attributes
87121 )
88122
89123
@@ -113,5 +147,7 @@ class SubController(BaseController):
113147 it as part of a larger device.
114148 """
115149
150+ root_attribute : Attribute | None = None
151+
116152 def __init__ (self ) -> None :
117153 super ().__init__ ()
0 commit comments