@@ -227,6 +227,102 @@ def update_profile(self, new_preferences: dict, message: Message = None):
227227 except Exception as x :
228228 LOG .error (x )
229229
230+ # Duplicated from OVOSSkill for backwards-compat with skills using ovos-workshop 0.X
231+ def _register_public_api (self ):
232+ """
233+ Find and register API methods decorated with `@api_method` and create a
234+ messagebus handler for fetching the api info if any handlers exist.
235+ """
236+
237+ def wrap_method (fn , arg_model = None ):
238+ """Boilerplate for returning the response to the sender."""
239+
240+ def wrapper (message ):
241+ result = None
242+ error = None
243+ try :
244+ if arg_model :
245+ result = fn (arg_model (* message .data ['args' ],
246+ ** message .data ['kwargs' ]))
247+ else :
248+ result = fn (* message .data .get ('args' , []),
249+ ** message .data .get ('kwargs' , {}))
250+ try :
251+ result = result .model_dump ()
252+ except AttributeError :
253+ # Response is not a Pydantic model
254+ pass
255+ except Exception as e :
256+ error = str (e )
257+ message .context ["skill_id" ] = self .skill_id
258+ self .bus .emit (message .response (data = {'result' : result ,
259+ 'error' : error }))
260+ return wrapper
261+
262+ from ovos_utils .skills import get_non_properties
263+ methods = [attr_name for attr_name in get_non_properties (self )
264+ if hasattr (getattr (self , attr_name ), '__name__' )]
265+
266+ for attr_name in methods :
267+ method = getattr (self , attr_name )
268+
269+ if hasattr (method , 'api_method' ):
270+ doc = method .__doc__ or ''
271+ name = method .__name__
272+
273+ # Extract method signature and return type
274+ import inspect
275+ signature = inspect .signature (method )
276+ schema = None
277+ return_schema = None
278+ request_class = None
279+ try :
280+ from pydantic import BaseModel
281+ parameters = signature .parameters
282+
283+ for arg_name , param in parameters .items ():
284+ if arg_name == 'self' :
285+ continue
286+ if issubclass (param .annotation , BaseModel ):
287+ # Get the JSON schema for the BaseModel
288+ schema = param .annotation .model_json_schema ()
289+ request_class = param .annotation
290+ break
291+ if signature .return_annotation and issubclass (signature .return_annotation , BaseModel ):
292+ # Get the JSON schema for the return type
293+ return_schema = signature .return_annotation .model_json_schema ()
294+ except ImportError :
295+ # If pydantic is not installed, there is no schema to extract
296+ pass
297+
298+ self .public_api [name ] = {
299+ 'help' : doc ,
300+ 'type' : f'{ self .skill_id } .{ name } ' ,
301+ 'func' : method ,
302+ 'signature' : str (signature ),
303+ 'request_schema' : schema ,
304+ 'response_schema' : return_schema ,
305+ 'request_class' : request_class
306+ }
307+ for key in self .public_api :
308+ if ('type' in self .public_api [key ] and
309+ 'func' in self .public_api [key ]):
310+ self .log .debug (f"Adding api method: "
311+ f"{ self .public_api [key ]['type' ]} " )
312+
313+ # remove the function member since it shouldn't be
314+ # reused and can't be sent over the messagebus
315+ func = self .public_api [key ].pop ('func' )
316+ req_class = self .public_api [key ].pop ('request_class' , None )
317+ self .add_event (self .public_api [key ]['type' ],
318+ wrap_method (func , req_class ), speak_errors = False )
319+
320+ if self .public_api :
321+ # TODO: Think about always registering this, so queries get an
322+ # empty response, rather than waiting for a timeout
323+ self .add_event (f'{ self .skill_id } .public_api' ,
324+ self ._send_public_api , speak_errors = False )
325+
230326 @resolve_message
231327 def update_skill_settings (self , new_preferences : dict ,
232328 message : Message = None , skill_global = True ):
0 commit comments