33import inspect
44import typing
55import warnings
6- from typing import Callable , Generic , Optional , TypeVar , overload
6+ from typing import Callable , Generic , Optional , Sequence , TypeVar , Union , overload
77
88import ntcore
9- from ntcore import NetworkTableInstance , Value
9+ from ntcore import NetworkTableInstance
1010from ntcore .types import ValueT
1111
12+
13+ class StructSerializable (typing .Protocol ):
14+ """Any type that is a wpiutil.wpistruct."""
15+
16+ WPIStruct : typing .ClassVar
17+
18+
1219T = TypeVar ("T" )
13- V = TypeVar ("V" , bound = ValueT )
20+ V = TypeVar ("V" , bound = Union [ ValueT , StructSerializable , Sequence [ StructSerializable ]] )
1421
1522
1623class tunable (Generic [V ]):
@@ -69,7 +76,7 @@ def execute(self):
6976 "_ntsubtable" ,
7077 "_ntwritedefault" ,
7178 # "__doc__",
72- "_mkv " ,
79+ "_topic_type " ,
7380 "_nt" ,
7481 )
7582
@@ -87,10 +94,15 @@ def __init__(
8794 self ._ntdefault = default
8895 self ._ntsubtable = subtable
8996 self ._ntwritedefault = writeDefault
90- d = Value .makeValue (default )
91- self ._mkv = Value .getFactoryByType (d .type ())
9297 # self.__doc__ = doc
9398
99+ self ._topic_type = _get_topic_type_for_value (self ._ntdefault )
100+ if self ._topic_type is None :
101+ checked_type : type = type (self ._ntdefault )
102+ raise TypeError (
103+ f"tunable is not publishable to NetworkTables, type: { checked_type .__name__ } "
104+ )
105+
94106 @overload
95107 def __get__ (self , instance : None , owner = None ) -> "tunable[V]" : ...
96108
@@ -99,11 +111,23 @@ def __get__(self, instance, owner=None) -> V: ...
99111
100112 def __get__ (self , instance , owner = None ):
101113 if instance is not None :
102- return instance ._tunables [self ].value
114+ return instance ._tunables [self ].get ()
103115 return self
104116
105117 def __set__ (self , instance , value : V ) -> None :
106- instance ._tunables [self ].setValue (self ._mkv (value ))
118+ instance ._tunables [self ].set (value )
119+
120+
121+ def _get_topic_type_for_value (value ) -> Optional [Callable [[ntcore .Topic ], typing .Any ]]:
122+ topic_type = _get_topic_type (type (value ))
123+ # bytes and str are Sequences. They must be checked before Sequence.
124+ if topic_type is None and isinstance (value , collections .abc .Sequence ):
125+ if not value :
126+ raise ValueError (
127+ f"tunable default cannot be an empty sequence, got { value } "
128+ )
129+ topic_type = _get_topic_type (Sequence [type (value [0 ])]) # type: ignore [misc]
130+ return topic_type
107131
108132
109133def setup_tunables (component , cname : str , prefix : Optional [str ] = "components" ) -> None :
@@ -127,7 +151,7 @@ def setup_tunables(component, cname: str, prefix: Optional[str] = "components")
127151
128152 NetworkTables = NetworkTableInstance .getDefault ()
129153
130- tunables = {}
154+ tunables : dict [ tunable , ntcore . Topic ] = {}
131155
132156 for n in dir (cls ):
133157 if n .startswith ("_" ):
@@ -142,11 +166,12 @@ def setup_tunables(component, cname: str, prefix: Optional[str] = "components")
142166 else :
143167 key = "%s/%s" % (prefix , n )
144168
145- ntvalue = NetworkTables .getEntry (key )
169+ topic = prop ._topic_type (NetworkTables .getTopic (key ))
170+ ntvalue = topic .getEntry (prop ._ntdefault )
146171 if prop ._ntwritedefault :
147- ntvalue .setValue (prop ._ntdefault )
172+ ntvalue .set (prop ._ntdefault )
148173 else :
149- ntvalue .setDefaultValue (prop ._ntdefault )
174+ ntvalue .setDefault (prop ._ntdefault )
150175 tunables [prop ] = ntvalue
151176
152177 component ._tunables = tunables
0 commit comments