@@ -80,6 +80,7 @@ def execute(self):
8080 "_ntsubtable" ,
8181 "_ntwritedefault" ,
8282 # "__doc__",
83+ "__orig_class__" ,
8384 "_topic_type" ,
8485 "_nt" ,
8586 )
@@ -100,13 +101,47 @@ def __init__(
100101 self ._ntwritedefault = writeDefault
101102 # self.__doc__ = doc
102103
103- self ._topic_type = _get_topic_type_for_value (self ._ntdefault )
104- if self ._topic_type is None :
105- checked_type : type = type (self ._ntdefault )
104+ # Defer checks for empty sequences to check type hints.
105+ # Report errors here when we can so the error points to the tunable line.
106+ if default or not isinstance (default , collections .abc .Sequence ):
107+ self ._topic_type = _get_topic_type_for_value (default )
108+ if self ._topic_type is None :
109+ checked_type : type = type (default )
110+ raise TypeError (
111+ f"tunable is not publishable to NetworkTables, type: { checked_type .__name__ } "
112+ )
113+
114+ def __set_name__ (self , owner : type , name : str ) -> None :
115+ type_hint : Optional [type ] = None
116+ # __orig_class__ is set after __init__, check it here.
117+ orig_class = getattr (self , "__orig_class__" , None )
118+ if orig_class is not None :
119+ # Accept field = tunable[Sequence[int]]([])
120+ type_hint = typing .get_args (orig_class )[0 ]
121+ else :
122+ type_hint = typing .get_type_hints (owner ).get (name )
123+ origin = typing .get_origin (type_hint )
124+ if origin is typing .ClassVar :
125+ # Accept field: ClassVar[tunable[Sequence[int]]] = tunable([])
126+ type_hint = typing .get_args (type_hint )[0 ]
127+ origin = typing .get_origin (type_hint )
128+ if origin is tunable :
129+ # Accept field: tunable[Sequence[int]] = tunable([])
130+ type_hint = typing .get_args (type_hint )[0 ]
131+
132+ if type_hint is not None :
133+ topic_type = _get_topic_type (type_hint )
134+ else :
135+ topic_type = _get_topic_type_for_value (self ._ntdefault )
136+
137+ if topic_type is None :
138+ checked_type : type = type_hint or type (self ._ntdefault )
106139 raise TypeError (
107140 f"tunable is not publishable to NetworkTables, type: { checked_type .__name__ } "
108141 )
109142
143+ self ._topic_type = topic_type
144+
110145 @overload
111146 def __get__ (self , instance : None , owner = None ) -> "tunable[V]" : ...
112147
@@ -218,7 +253,7 @@ class MyComponent:
218253 navx: ...
219254
220255 @feedback
221- def get_angle(self):
256+ def get_angle(self) -> float :
222257 return self.navx.getYaw()
223258
224259 class MyRobot(magicbot.MagicRobot):
@@ -297,6 +332,8 @@ def _get_topic_type(
297332 if hasattr (inner_type , "WPIStruct" ):
298333 return lambda topic : ntcore .StructArrayTopic (topic , inner_type )
299334
335+ return None
336+
300337
301338def collect_feedbacks (component , cname : str , prefix : Optional [str ] = "components" ):
302339 """
0 commit comments