22import types
33from typing import Any , Callable
44
5- from chartlets .channel import (
5+ from .channel import (
66 Input ,
77 Output ,
88 State ,
99)
10+ from .util .logger import LOGGER
1011
1112
1213class Callback :
@@ -20,7 +21,7 @@ class Callback:
2021 def from_decorator (
2122 cls ,
2223 decorator_name : str ,
23- decorator_args : tuple [ Any , ...] ,
24+ decorator_args : tuple | list ,
2425 function : Any ,
2526 states_only : bool = False ,
2627 ) -> "Callback" :
@@ -126,11 +127,10 @@ def make_function_args(
126127 f" expected { num_inputs } ,"
127128 f" but got { num_values } "
128129 )
129- if delta > 0 :
130- values = (* values , * (delta * (None ,)))
131- print (f"WARNING: { message } " ) # TODO use logging
132- else :
130+ if delta < 0 :
133131 raise TypeError (message )
132+ LOGGER .warning (message )
133+ values = (* values , * (delta * (None ,)))
134134
135135 param_names = self .param_names [1 :]
136136 args = [context ]
@@ -150,15 +150,14 @@ def _parameter_to_dict(parameter: inspect.Parameter) -> dict[str, Any]:
150150 empty = inspect .Parameter .empty
151151 d = {"name" : parameter .name }
152152 if parameter .annotation is not empty :
153- d |= {"schema" : _annotation_to_json_schema (parameter .annotation )}
153+ d |= {"schema" : annotation_to_json_schema (parameter .annotation )}
154154 if parameter .default is not empty :
155155 d |= {"default" : parameter .default }
156156 return d
157157
158+
158159def _return_to_dict (return_annotation : Any ) -> dict [str , Any ]:
159- return {
160- "schema" : _annotation_to_json_schema (return_annotation )
161- }
160+ return {"schema" : annotation_to_json_schema (return_annotation )}
162161
163162
164163_basic_types = {
@@ -173,68 +172,55 @@ def _return_to_dict(return_annotation: Any) -> dict[str, Any]:
173172 dict : "object" ,
174173}
175174
176- _object_types = {"Component" : "Component" , "Chart" : "Chart" }
177175
176+ def annotation_to_json_schema (annotation : Any ) -> dict :
177+ from chartlets import Component
178178
179- def _annotation_to_json_schema (annotation : Any ) -> dict :
180179 if annotation is Any :
181180 return {}
182-
183- if annotation in _basic_types :
181+ elif annotation in _basic_types :
184182 return {"type" : _basic_types [annotation ]}
185-
186- if isinstance ( annotation , types . UnionType ):
187- type_list = list (map (_annotation_to_json_schema , annotation .__args__ ))
183+ elif isinstance ( annotation , types . UnionType ):
184+ assert annotation . __args__ and len ( annotation . __args__ ) > 1
185+ type_list = list (map (annotation_to_json_schema , annotation .__args__ ))
188186 type_name_list = [
189- t ["type" ] for t in type_list if isinstance (t .get ("type" ), str )
187+ t ["type" ]
188+ for t in type_list
189+ if isinstance (t .get ("type" ), str ) and len (t ) == 1
190190 ]
191- if len (type_name_list ) == 1 :
192- return {"type" : type_name_list [0 ]}
193- elif len (type_name_list ) > 1 :
191+ if len (type_list ) == len (type_name_list ):
194192 return {"type" : type_name_list }
195- elif len (type_list ) == 1 :
196- return type_list [0 ]
197- elif len (type_list ) > 1 :
198- return {"oneOf" : type_list }
199193 else :
200- return {}
201-
202- if isinstance ( annotation , types . GenericAlias ):
194+ return {"oneOf" : type_list }
195+ elif isinstance ( annotation , types . GenericAlias ):
196+ assert annotation . __args__
203197 if annotation .__origin__ is tuple :
204198 return {
205199 "type" : "array" ,
206- "items" : list (map (_annotation_to_json_schema , annotation .__args__ )),
200+ "items" : list (map (annotation_to_json_schema , annotation .__args__ )),
207201 }
208202 elif annotation .__origin__ is list :
209- if annotation .__args__ :
210- return {
211- "type" : "array" ,
212- "items" : _annotation_to_json_schema (annotation .__args__ [0 ]),
213- }
203+ assert annotation .__args__ and len (annotation .__args__ ) == 1
204+ items_schema = annotation_to_json_schema (annotation .__args__ [0 ])
205+ if items_schema == {}:
206+ return {"type" : "array" }
214207 else :
215- return {
216- "type" : "array" ,
217- }
208+ return {"type" : "array" , "items" : items_schema }
218209 elif annotation .__origin__ is dict :
219- if annotation .__args__ :
220- if len (annotation .__args__ ) == 2 and annotation .__args__ [0 ] is str :
221- return {
222- "type" : "object" ,
223- "additionalProperties" : _annotation_to_json_schema (
224- annotation .__args__ [1 ]
225- ),
226- }
227- else :
228- return {
229- "type" : "object" ,
230- }
231- else :
232- type_name = (
233- annotation .__name__ if hasattr (annotation , "__name__" ) else str (annotation )
234- )
235- try :
236- return {"type" : "object" , "class" : _object_types [type_name ]}
237- except KeyError :
238- pass
210+ assert annotation .__args__
211+ assert len (annotation .__args__ ) == 2
212+ if annotation .__args__ [0 ] is str :
213+ value_schema = annotation_to_json_schema (annotation .__args__ [1 ])
214+ if value_schema == {}:
215+ return {"type" : "object" }
216+ else :
217+ return {"type" : "object" , "additionalProperties" : value_schema }
218+ elif (
219+ inspect .isclass (annotation )
220+ and "." not in annotation .__qualname__
221+ and callable (getattr (annotation , "to_dict" , None ))
222+ ):
223+ # Note, for Component classes it is actually possible to generate the object schema
224+ return {"type" : "object" , "class" : annotation .__qualname__ }
239225
240226 raise TypeError (f"unsupported type annotation: { annotation } " )
0 commit comments