From 0acc02b13b27d6c3b2e5d55280df29fdafc0b68d Mon Sep 17 00:00:00 2001 From: To Finke Date: Thu, 22 May 2025 20:33:00 +0200 Subject: [PATCH 1/3] test: unit test for PluginService.describe_target --- .../unit/plugin_service_test.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/spiffworkflow_proxy/unit/plugin_service_test.py b/tests/spiffworkflow_proxy/unit/plugin_service_test.py index bc7af4f..796f529 100644 --- a/tests/spiffworkflow_proxy/unit/plugin_service_test.py +++ b/tests/spiffworkflow_proxy/unit/plugin_service_test.py @@ -20,3 +20,25 @@ def test_plugin_for_display_name() -> None: def test_available_commands_by_plugin() -> None: commands = PluginService.available_commands_by_plugin() assert(list(commands['connector_example'].keys()) == ['CombineStrings']) + +def test_describe_target() -> None: + commands = PluginService.available_commands_by_plugin() + combine_strings = commands["connector_example"]["CombineStrings"] + description = PluginService.describe_target( + "connector_example", "CombineStrings", combine_strings + ) + assert description == { + "id": "example/CombineStrings", + "parameters": [ + { + "id": "arg1", + "required": True, + "type": "str", + }, + { + "id": "arg2", + "required": True, + "type": "str", + } + ] + } \ No newline at end of file From 4899cb623d9d08cd75c6ad7d9301dd293fa00521 Mon Sep 17 00:00:00 2001 From: To Finke Date: Thu, 22 May 2025 20:41:48 +0200 Subject: [PATCH 2/3] feat: added default value to description of command parameters --- src/spiffworkflow_proxy/plugin_service.py | 7 ++++++- .../src/connector_example/commands/combine_strings.py | 2 +- tests/spiffworkflow_proxy/unit/plugin_service_test.py | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/spiffworkflow_proxy/plugin_service.py b/src/spiffworkflow_proxy/plugin_service.py index f39fcac..773b17f 100644 --- a/src/spiffworkflow_proxy/plugin_service.py +++ b/src/spiffworkflow_proxy/plugin_service.py @@ -149,7 +149,12 @@ def param_annotation_desc(param: Parameter) -> dict: if annotation_type in supported_types: param_type_desc = annotation_type.__name__ - return {"id": param_id, "type": param_type_desc, "required": param_req} + description = {"id": param_id, "type": param_type_desc, "required": param_req} + + if param.default is not param.empty: + description["default"] = param.default + + return description @staticmethod def callable_params_desc(kallable: Any) -> list[dict]: diff --git a/tests/mock_connectors/connector-example/src/connector_example/commands/combine_strings.py b/tests/mock_connectors/connector-example/src/connector_example/commands/combine_strings.py index df93265..7509442 100644 --- a/tests/mock_connectors/connector-example/src/connector_example/commands/combine_strings.py +++ b/tests/mock_connectors/connector-example/src/connector_example/commands/combine_strings.py @@ -9,7 +9,7 @@ class CombineStrings(ConnectorCommand): """Takes two strings, combines them together, and returns a single string! AMAZIN!.""" def __init__( - self, arg1: str, arg2: str + self, arg1: str, arg2: str = "foo" ): """ :param arg1: The First Argument diff --git a/tests/spiffworkflow_proxy/unit/plugin_service_test.py b/tests/spiffworkflow_proxy/unit/plugin_service_test.py index 796f529..16317cb 100644 --- a/tests/spiffworkflow_proxy/unit/plugin_service_test.py +++ b/tests/spiffworkflow_proxy/unit/plugin_service_test.py @@ -37,8 +37,9 @@ def test_describe_target() -> None: }, { "id": "arg2", - "required": True, + "required": False, "type": "str", + "default": "foo" } ] } \ No newline at end of file From 8b68bc548308dac3cf15edb26e3904bf1ca14ac5 Mon Sep 17 00:00:00 2001 From: To Finke Date: Sun, 25 May 2025 17:55:26 +0200 Subject: [PATCH 3/3] feat: spiff-arena #2342 parse parameters into separat react-jsonschema-form compatible json schema --- src/spiffworkflow_proxy/plugin_service.py | 55 +++++++++++++++++-- .../unit/plugin_service_test.py | 23 +++++++- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/src/spiffworkflow_proxy/plugin_service.py b/src/spiffworkflow_proxy/plugin_service.py index 773b17f..6f435f1 100644 --- a/src/spiffworkflow_proxy/plugin_service.py +++ b/src/spiffworkflow_proxy/plugin_service.py @@ -149,12 +149,43 @@ def param_annotation_desc(param: Parameter) -> dict: if annotation_type in supported_types: param_type_desc = annotation_type.__name__ - description = {"id": param_id, "type": param_type_desc, "required": param_req} + return {"id": param_id, "type": param_type_desc, "required": param_req} + + @staticmethod + def param_schema(param: Parameter) -> dict: + """Parses a callable parameter's type annotation, if any, to form a ParameterSchema.""" + schema = { + "title": param.name, + } + + none_type = type(None) + supported_types_map = { + str: "string", + int: "integer", + float: "number", + dict: "object", + list: "array", + bool: "boolean", + none_type: "null", + } # https://json-schema.org/understanding-json-schema/reference/type + + annotation = param.annotation + + if annotation in supported_types_map.keys(): + supported_annotation_types = {annotation} + else: + supported_annotation_types = {t for t in typing.get_args(annotation) if t in supported_types_map.keys()} + + # only add the type keyword if at least one type is supported + if(len(supported_annotation_types)): + schema["type"] = [supported_types_map[annotation_type] for annotation_type in supported_annotation_types] + + param_req = param.default is param.empty and none_type not in supported_annotation_types if param.default is not param.empty: - description["default"] = param.default + schema["default"] = param.default - return description + return {"schema": schema, "required": param_req} @staticmethod def callable_params_desc(kallable: Any) -> list[dict]: @@ -167,11 +198,27 @@ def callable_params_desc(kallable: Any) -> list[dict]: return params + @staticmethod + def callable_params_schema(kallable: Any) -> list[dict]: + sig = inspect.signature(kallable) + params_to_skip = ["self", "kwargs"] + sig_params = filter(lambda param: param.name not in params_to_skip, sig.parameters.values()) + params = [PluginService.param_schema(param) for param in sig_params] + schema = { + "title": "Parameters", + "type": "object", + "required": [p["schema"]["title"] for p in params if p["required"]], + "properties": {p["schema"]["title"]: p["schema"] for p in params} + } + + return schema + @staticmethod def describe_target(plugin_name: str, target_name: str, target: type) -> dict: parameters = PluginService.callable_params_desc(target.__init__) # type: ignore target_id = PluginService.target_id(plugin_name, target_name) - return {"id": target_id, "parameters": parameters} + schema = PluginService.callable_params_schema(target.__init__) + return {"id": target_id, "parameters": parameters, "schema": schema} @staticmethod def is_connector_command(module: Any) -> bool: diff --git a/tests/spiffworkflow_proxy/unit/plugin_service_test.py b/tests/spiffworkflow_proxy/unit/plugin_service_test.py index 16317cb..2421def 100644 --- a/tests/spiffworkflow_proxy/unit/plugin_service_test.py +++ b/tests/spiffworkflow_proxy/unit/plugin_service_test.py @@ -39,7 +39,24 @@ def test_describe_target() -> None: "id": "arg2", "required": False, "type": "str", - "default": "foo" } - ] - } \ No newline at end of file + ], + "schema": { + "title": "Parameters", + "type": "object", + "required": [ + "arg1" + ], + "properties": { + "arg1": { + "title": "arg1", + "type": ["string"] + }, + "arg2": { + "title": "arg2", + "type": ["string"], + "default": "foo" + } + } + } + }