Skip to content

Commit

Permalink
Merge pull request #3 from sartography/feature/error-boundary
Browse files Browse the repository at this point in the history
Feature/error boundary
  • Loading branch information
jasquat authored Oct 13, 2023
2 parents c7c5c6d + 9579ac8 commit 53fcd3e
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 29 deletions.
21 changes: 20 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "spiffworkflow-proxy"
version = "0.1.0"
version = "1.0.0"
description = "A blueprint that can allow (and limit) SpiffWorkflow's Service Tasks access to an organizations API's, such as connections to AWS Services and existing applications."
authors = ["Dan <[email protected]>"]
readme = "README.md"
Expand All @@ -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}
Expand Down
34 changes: 20 additions & 14 deletions src/spiffworkflow_proxy/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ConnectorProxyResponseDict

from spiffworkflow_proxy.plugin_service import PluginService

Expand Down Expand Up @@ -38,7 +39,9 @@ def do_command(plugin_display_name: str, command_name: str) -> Response:
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_code="command_not_found",
status=404
)

params = typing.cast(dict, request.json)
Expand All @@ -48,17 +51,14 @@ 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_code=e.__class__.__name__,
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"]
response = json.dumps(return_response)
return Response(response, mimetype=result["mimetype"], status=status_code)


Expand Down Expand Up @@ -142,7 +142,13 @@ 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, error_code: str, status: int) -> Response:
response: ConnectorProxyResponseDict = {
"command_response": {},
"error": {
"message": message,
"error_code": error_code,
},
}
return Response(json.dumps(response), status=status)

26 changes: 17 additions & 9 deletions src/spiffworkflow_proxy/plugin_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from inspect import Parameter
from typing import Any

from spiffworkflow_connector_command.command_interface import ConnectorCommand


class PluginService:
"""
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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()

Expand All @@ -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()

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
"""Simple Example Command."""
from typing import Any

from spiffworkflow_connector_command.command_interface import CommandResultDictV2
from spiffworkflow_connector_command.command_interface import ConnectorCommand
from spiffworkflow_connector_command.command_interface import ConnectorProxyResponseDict

class CombineStrings:

class CombineStrings(ConnectorCommand):
"""Takes two strings, combines them together, and returns a single string! AMAZIN!."""

def __init__(
Expand All @@ -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) -> CommandResultDictV2:
"""Execute."""
# Get the service resource.
return self.arg1 + self.arg2

return_response: ConnectorProxyResponseDict = {
"command_response": {"example_response": "whatever you want", "arg1": self.arg1, "arg2": self.arg2},
"spiff__logs": [],
"error": None,
}
result: CommandResultDictV2 = {
"response": return_response,
"status": 200,
"mimetype": "application/json",
}

return result

0 comments on commit 53fcd3e

Please sign in to comment.