From 5659823aa4338c501b18776d3cb7027a9c734f9f Mon Sep 17 00:00:00 2001 From: "David M. Raker" Date: Tue, 23 Jun 2020 14:34:06 -0400 Subject: [PATCH 1/3] Updated _inspect() method in platform/vip/agent/subsystems/rpc.py for changes to inspect module in Python 3 plus some enhancements to output. --- volttron/platform/vip/agent/subsystems/rpc.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/volttron/platform/vip/agent/subsystems/rpc.py b/volttron/platform/vip/agent/subsystems/rpc.py index 7945f2dc4c..441b59b745 100644 --- a/volttron/platform/vip/agent/subsystems/rpc.py +++ b/volttron/platform/vip/agent/subsystems/rpc.py @@ -168,11 +168,18 @@ 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.description + } + if p.default is not inspect.Parameter.empty: + response['params'][p.name]['default'] = p.default + if p.annotation is not inspect.Parameter.empty: + response['params'][p.name]['annotation'] = p.annotation doc = inspect.getdoc(method) if doc: response['doc'] = doc @@ -181,15 +188,15 @@ 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 + } + if signature.return_annotation is not inspect.Signature.empty: + response['return'] = signature.return_annotation return response From aee049601445a328baf29f9801dda8c3820aa4f5 Mon Sep 17 00:00:00 2001 From: "David M. Raker" Date: Wed, 24 Jun 2020 00:38:23 -0400 Subject: [PATCH 2/3] Added test for _inspect() method in platform/vip/agent/subsystems/rpc.py and fixed _inspect() output to pass. --- pytest.ini | 1 + volttron/platform/vip/agent/subsystems/rpc.py | 8 +-- .../subsystems/test_rpc_subsystem.py | 51 +++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 volttrontesting/subsystems/test_rpc_subsystem.py diff --git a/pytest.ini b/pytest.ini index d930b2104e..8adc48ee4c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -51,3 +51,4 @@ markers = rmq_reconnect: rabbitmq reconnect tests rmq_shutdown: rabbitmq shutdown tests secure: Test platform and agents with secure platform options + rpc: Tests for RPC diff --git a/volttron/platform/vip/agent/subsystems/rpc.py b/volttron/platform/vip/agent/subsystems/rpc.py index 441b59b745..309844edc9 100644 --- a/volttron/platform/vip/agent/subsystems/rpc.py +++ b/volttron/platform/vip/agent/subsystems/rpc.py @@ -179,7 +179,8 @@ def _inspect(method): if p.default is not inspect.Parameter.empty: response['params'][p.name]['default'] = p.default if p.annotation is not inspect.Parameter.empty: - response['params'][p.name]['annotation'] = p.annotation + 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 @@ -195,8 +196,9 @@ def _inspect(method): 'file': source, 'line_number': lineno } - if signature.return_annotation is not inspect.Signature.empty: - response['return'] = signature.return_annotation + 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..5066cfc50f --- /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 From 1e84e569bb212ed2ac0acf18608783e3a3dc9984 Mon Sep 17 00:00:00 2001 From: "David M. Raker" Date: Fri, 7 Aug 2020 17:18:28 -0400 Subject: [PATCH 3/3] Updated platform.vip.agent.rpc._inspect() and volttrontesting.subsystems.test_rpc_subsystem.test_method_inspection() to remove functionality not added until 3.8. --- volttron/platform/vip/agent/subsystems/rpc.py | 2 +- volttrontesting/subsystems/test_rpc_subsystem.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/volttron/platform/vip/agent/subsystems/rpc.py b/volttron/platform/vip/agent/subsystems/rpc.py index 309844edc9..57c6e5d7b7 100644 --- a/volttron/platform/vip/agent/subsystems/rpc.py +++ b/volttron/platform/vip/agent/subsystems/rpc.py @@ -174,7 +174,7 @@ def _inspect(method): signature = inspect.signature(method) for p in signature.parameters.values(): response['params'][p.name] = { - 'kind': p.kind.description + 'kind': p.kind.name } if p.default is not inspect.Parameter.empty: response['params'][p.name]['default'] = p.default diff --git a/volttrontesting/subsystems/test_rpc_subsystem.py b/volttrontesting/subsystems/test_rpc_subsystem.py index 5066cfc50f..4ef775ce92 100644 --- a/volttrontesting/subsystems/test_rpc_subsystem.py +++ b/volttrontesting/subsystems/test_rpc_subsystem.py @@ -28,16 +28,16 @@ def test_method_inspection(volttron_instance): test_output = { 'doc': 'Doc String', 'params': {'param1': {'annotation': 'int', - 'kind': 'positional or keyword'}, + 'kind': 'POSITIONAL_OR_KEYWORD'}, 'param2': {'annotation': 'typing.Union[str, typing.List[str]]', - 'kind': 'positional or keyword'}, + 'kind': 'POSITIONAL_OR_KEYWORD'}, 'param3': {'annotation': 'bool', 'default': True, - 'kind': 'keyword-only'}, + 'kind': 'KEYWORD_ONLY'}, 'param4': {'annotation': 'typing.Union[float, typing.List[float], ' 'NoneType]', 'default': None, - 'kind': 'keyword-only'}}, + 'kind': 'KEYWORD_ONLY'}}, 'return': 'dict', 'source': {'file': 'volttrontesting/subsystems/test_rpc_subsystem.py', # Must change if this file moves! 'line_number': lineno},