diff --git a/ovos_workshop/skills/ovos.py b/ovos_workshop/skills/ovos.py index a232439a..c819cb9c 100644 --- a/ovos_workshop/skills/ovos.py +++ b/ovos_workshop/skills/ovos.py @@ -1023,16 +1023,32 @@ def _register_public_api(self): messagebus handler for fetching the api info if any handlers exist. """ - def wrap_method(fn): + def wrap_method(fn, arg_model=None): """Boilerplate for returning the response to the sender.""" def wrapper(message): - result = fn(*message.data['args'], **message.data['kwargs']) + result = None + error = None + try: + if arg_model: + result = fn(arg_model(*message.data['args'], + **message.data['kwargs'])) + else: + result = fn(*message.data.get('args', []), + **message.data.get('kwargs', {})) + try: + result = result.model_dump() + except AttributeError: + # Response is not a Pydantic model + pass + except Exception as e: + error = str(e) message.context["skill_id"] = self.skill_id - self.bus.emit(message.response(data={'result': result})) - + self.bus.emit(message.response(data={'result': result, + 'error': error})) return wrapper + from ovos_utils.skills import get_non_properties methods = [attr_name for attr_name in get_non_properties(self) if hasattr(getattr(self, attr_name), '__name__')] @@ -1042,10 +1058,48 @@ def wrapper(message): if hasattr(method, 'api_method'): doc = method.__doc__ or '' name = method.__name__ + + # Extract method signature and return type + import inspect + sig = inspect.signature(method) + schema = None + return_schema = None + request_class = None + try: + from pydantic import BaseModel + parameters = sig.parameters + + for arg_name, param in parameters.items(): + if arg_name == 'self': + continue + ann = param.annotation + if isinstance(ann, type) and issubclass(ann, BaseModel): + # Get the JSON schema for the BaseModel + try: + schema = ann.model_json_schema() + except AttributeError: + schema = ann.schema() + request_class = ann + break + ra = sig.return_annotation + if isinstance(ra, type) and issubclass(ra, BaseModel): + # Get the JSON schema for the return type + try: + return_schema = ra.model_json_schema() + except AttributeError: + return_schema = ra.schema() + except ImportError: + # If pydantic is not installed, there is no schema to extract + pass + self.public_api[name] = { 'help': doc, 'type': f'{self.skill_id}.{name}', - 'func': method + 'func': method, + 'signature': str(signature), + 'request_schema': schema, + 'response_schema': return_schema, + 'request_class': request_class } for key in self.public_api: if ('type' in self.public_api[key] and @@ -1056,8 +1110,9 @@ def wrapper(message): # remove the function member since it shouldn't be # reused and can't be sent over the messagebus func = self.public_api[key].pop('func') + req_class = self.public_api[key].pop('request_class', None) self.add_event(self.public_api[key]['type'], - wrap_method(func), speak_errors=False) + wrap_method(func, req_class), speak_errors=False) if self.public_api: self.add_event(f'{self.skill_id}.public_api',