diff --git a/pyproject.toml b/pyproject.toml index af11721..7bf37c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,9 @@ python = "^3.9" Flask = "^2.2" Flask-OAuthlib = "^0.9.6" +# spiffworkflow-connector-command = {git = "https://github.com/sartography/spiffworkflow-connector-command.git", rev = "main"} +spiffworkflow-connector-command = {develop = true, path = "../spiffworkflow-connector-command"} + [tool.poetry.group.dev.dependencies] pytest = "^7.2.0" connector-example = {path = "tests/mock_connectors/connector-example", develop = true} diff --git a/src/spiffworkflow_proxy/blueprint.py b/src/spiffworkflow_proxy/blueprint.py index 7b78fd0..56e1818 100644 --- a/src/spiffworkflow_proxy/blueprint.py +++ b/src/spiffworkflow_proxy/blueprint.py @@ -11,6 +11,7 @@ from flask import session from flask import url_for from flask_oauthlib.contrib.client import OAuth # type: ignore +from spiffworkflow_connector_command.command_interface import CommandResponseDict from spiffworkflow_proxy.plugin_service import PluginService @@ -35,10 +36,15 @@ def list_commands() -> Response: @proxy_blueprint.route("/v1/do//", methods=["GET", "POST"]) def do_command(plugin_display_name: str, command_name: str) -> Response: + operator_id = f"{plugin_display_name}/{command_name}" + # import pdb; pdb.set_trace() command = PluginService.command_named(plugin_display_name, command_name) if command is None: return json_error_response( - f"Command not found: {plugin_display_name}:{command_name}", status=404 + message="It either does not exist or does not inherit from spiffworkflow_connector_command.", + error_name="command_not_found", + operator_id=operator_id, + status=404 ) params = typing.cast(dict, request.json) @@ -48,17 +54,19 @@ def do_command(plugin_display_name: str, command_name: str) -> Response: result = command(**params).execute(current_app.config, task_data) except Exception as e: return json_error_response( - f"Error encountered when executing {plugin_display_name}:{command_name} {str(e)}", - status=404, + message=str(e), + error_name=e.__class__.__name__, + operator_id=operator_id, + status=500 ) - if 'status' in result: - status_code = int(result['status']) - else: - status_code = 200 - if isinstance(result["response"], dict): - response = json.dumps(result["response"]) - else: - response = result["response"] + + status_code = int(result['status']) + return_response = result["response"] + if "operator_id" not in return_response or return_response["operator_id"] is None: + return_response["operator_id"] = operator_id + print(f"return_response: {return_response}") + response = json.dumps(return_response) + print(f"result: {result}") return Response(response, mimetype=result["mimetype"], status=status_code) @@ -142,7 +150,14 @@ def tokensaver(token: str) -> None: return handler -def json_error_response(message: str, status: int) -> Response: - resp = {"error": message, "status": status} - return Response(json.dumps(resp), status=status) +def json_error_response(message: str, operator_id: str, error_name: str, status: int) -> Response: + response: CommandResponseDict = { + "api_response": {}, + "error": { + "message": message, + "error_name": error_name, + }, + "operator_id": operator_id, + } + return Response(json.dumps(response), status=status) diff --git a/src/spiffworkflow_proxy/plugin_service.py b/src/spiffworkflow_proxy/plugin_service.py index 0d8d0a4..3efe0fa 100644 --- a/src/spiffworkflow_proxy/plugin_service.py +++ b/src/spiffworkflow_proxy/plugin_service.py @@ -7,6 +7,8 @@ from inspect import Parameter from typing import Any +from spiffworkflow_connector_command.command_interface import ConnectorCommand + class PluginService: """ @@ -33,7 +35,7 @@ def available_plugins() -> dict[str, types.ModuleType]: } @staticmethod - def available_auths_by_plugin() -> dict[str, dict[str, type]]: + def available_auths_by_plugin() -> dict[str, dict[str, type[ConnectorCommand]]]: return { plugin_name: dict(PluginService.auths_for_plugin( plugin_name, plugin @@ -42,7 +44,7 @@ def available_auths_by_plugin() -> dict[str, dict[str, type]]: } @staticmethod - def available_commands_by_plugin() -> dict[str, dict[str, type]]: + def available_commands_by_plugin() -> dict[str, dict[str, type[ConnectorCommand]]]: return { plugin_name: dict(PluginService.commands_for_plugin( plugin_name, plugin @@ -56,7 +58,7 @@ def target_id(plugin_name: str, target_name: str) -> str: return f"{plugin_display_name}/{target_name}" @staticmethod - def auth_named(plugin_display_name: str, auth_name: str) -> type | None: + def auth_named(plugin_display_name: str, auth_name: str) -> type[ConnectorCommand] | None: plugin_name = PluginService.plugin_name_from_display_name(plugin_display_name) available_auths_by_plugin = PluginService.available_auths_by_plugin() @@ -66,7 +68,7 @@ def auth_named(plugin_display_name: str, auth_name: str) -> type | None: return None @staticmethod - def command_named(plugin_display_name: str, command_name: str) -> type | None: + def command_named(plugin_display_name: str, command_name: str) -> type[ConnectorCommand] | None: plugin_name = PluginService.plugin_name_from_display_name(plugin_display_name) available_commands_by_plugin = PluginService.available_commands_by_plugin() @@ -95,21 +97,23 @@ def modules_for_plugin_in_package( @staticmethod def targets_for_plugin( plugin_name: str, plugin: types.ModuleType, target_package_name: str - ) -> Generator[tuple[str, type], None, None]: + ) -> Generator[tuple[str, type[ConnectorCommand]], None, None]: for module_name, module in PluginService.modules_for_plugin_in_package( plugin, target_package_name ): - for member_name, member in inspect.getmembers(module, inspect.isclass): + # for member_name, member in inspect.getmembers(module, inspect.isclass): + for member_name, member in inspect.getmembers(module, PluginService.is_connector_command): if member.__module__ == module_name: yield member_name, member @staticmethod - def auths_for_plugin(plugin_name: str, plugin: types.ModuleType) -> Generator[tuple[str, type], None, None]: + def auths_for_plugin(plugin_name: str, plugin: types.ModuleType) -> Generator[tuple[str, type[ConnectorCommand]], None, None]: yield from PluginService.targets_for_plugin(plugin_name, plugin, "auths") @staticmethod - def commands_for_plugin(plugin_name: str, plugin: types.ModuleType) -> Generator[tuple[str, type], None, None]: - # TODO check if class has an execute method before yielding + def commands_for_plugin( + plugin_name: str, plugin: types.ModuleType + ) -> Generator[tuple[str, type[ConnectorCommand]], None, None]: yield from PluginService.targets_for_plugin(plugin_name, plugin, "commands") @staticmethod @@ -165,3 +169,7 @@ 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} + + @staticmethod + def is_connector_command(module: Any) -> bool: + return inspect.isclass(module) and issubclass(module, ConnectorCommand) 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 fb85b95..8400419 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 @@ -1,8 +1,12 @@ """Simple Example Command.""" from typing import Any +from spiffworkflow_connector_command.command_interface import CommandResponseDict +from spiffworkflow_connector_command.command_interface import CommandResultDict +from spiffworkflow_connector_command.command_interface import ConnectorCommand -class CombineStrings: + +class CombineStrings(ConnectorCommand): """Takes two strings, combines them together, and returns a single string! AMAZIN!.""" def __init__( @@ -16,7 +20,19 @@ def __init__( self.arg1 = arg1 self.arg2 = arg2 - def execute(self, config: Any, task_data: Any) -> Any: + def execute(self, config: Any, task_data: Any) -> CommandResultDict: """Execute.""" - # Get the service resource. - return self.arg1 + self.arg2 + + return_response: CommandResponseDict = { + "api_response": {"example_response": "whatever you want", "arg1": self.arg1, "arg2": self.arg2}, + "spiff__logs": [], + "error": None, + } + result: CommandResultDict = { + "response": return_response, + "status": 200, + "mimetype": "application/json", + } + + return result +