diff --git a/pytest.ini b/pytest.ini index 472578a0f6..77a47127da 100644 --- a/pytest.ini +++ b/pytest.ini @@ -52,4 +52,6 @@ markers = rmq_reconnect: rabbitmq reconnect tests rmq_shutdown: rabbitmq shutdown tests secure: Test platform and agents with secure platform options + rpc: Tests for RPC mysqlfuncts: level one integration tests for mysqlfuncts + \ No newline at end of file diff --git a/volttron/platform/vip/agent/subsystems/rpc.py b/volttron/platform/vip/agent/subsystems/rpc.py index 7945f2dc4c..57c6e5d7b7 100644 --- a/volttron/platform/vip/agent/subsystems/rpc.py +++ b/volttron/platform/vip/agent/subsystems/rpc.py @@ -168,11 +168,19 @@ def method(self, request, ident, name, args, kwargs, del local.request del local.batch - def _inspect(self, method): - params = inspect.getargspec(method) - if hasattr(method, 'im_self'): - params.args.pop(0) - response = {'params': params} + @staticmethod + def _inspect(method): + response = {'params': {}} + signature = inspect.signature(method) + for p in signature.parameters.values(): + response['params'][p.name] = { + 'kind': p.kind.name + } + if p.default is not inspect.Parameter.empty: + response['params'][p.name]['default'] = p.default + if p.annotation is not inspect.Parameter.empty: + annotation = p.annotation.__name__ if type(p.annotation) is type else str(p.annotation) + response['params'][p.name]['annotation'] = annotation doc = inspect.getdoc(method) if doc: response['doc'] = doc @@ -181,15 +189,16 @@ def _inspect(self, method): cut = len(os.path.commonprefix([_ROOT_PACKAGE_PATH, source])) source = source[cut:] lineno = inspect.getsourcelines(method)[1] - except IOError: + except Exception: pass else: - response['source'] = source, lineno - try: - # pylint: disable=protected-access - response['return'] = method._returns - except AttributeError: - pass + response['source'] = { + 'file': source, + 'line_number': lineno + } + ret = signature.return_annotation + if ret is not inspect.Signature.empty: + response['return'] = ret.__name__ if type(ret) is type else str(ret) return response diff --git a/volttrontesting/subsystems/test_rpc_subsystem.py b/volttrontesting/subsystems/test_rpc_subsystem.py new file mode 100644 index 0000000000..4ef775ce92 --- /dev/null +++ b/volttrontesting/subsystems/test_rpc_subsystem.py @@ -0,0 +1,51 @@ +import os +import pytest +import inspect +from volttron.platform.vip.agent import RPC +from volttron.platform.vip.agent import Agent +from typing import Optional, Union, List + +class _ExporterTestAgent(Agent): + def __init__(self, **kwargs): + super(_ExporterTestAgent, self).__init__(**kwargs) + + @RPC.export('test_method') + def test_method(self, param1: int, param2: Union[str, List[str]], *, param3: bool = True, + param4: Optional[Union[float, List[float]]] = None) -> dict: + """Doc String""" + return {'param1': param1, 'param2': param2, param3: param3, 'param4': param4} + + +@pytest.mark.rpc +def test_method_inspection(volttron_instance): + """ Tests RPC Method Inspection + + :param volttron_instance: + :return: + """ + + lineno = inspect.getsourcelines(_ExporterTestAgent.test_method)[1] + test_output = { + 'doc': 'Doc String', + 'params': {'param1': {'annotation': 'int', + 'kind': 'POSITIONAL_OR_KEYWORD'}, + 'param2': {'annotation': 'typing.Union[str, typing.List[str]]', + 'kind': 'POSITIONAL_OR_KEYWORD'}, + 'param3': {'annotation': 'bool', + 'default': True, + 'kind': 'KEYWORD_ONLY'}, + 'param4': {'annotation': 'typing.Union[float, typing.List[float], ' + 'NoneType]', + 'default': None, + 'kind': 'KEYWORD_ONLY'}}, + 'return': 'dict', + 'source': {'file': 'volttrontesting/subsystems/test_rpc_subsystem.py', # Must change if this file moves! + 'line_number': lineno}, + } + + new_agent1 = volttron_instance.build_agent(identity='test_inspect1', agent_class=_ExporterTestAgent) + new_agent2 = volttron_instance.build_agent(identity='test_inspect2') + + result = new_agent2.vip.rpc.call('test_inspect1', 'test_method.inspect').get() + + assert result == test_output