Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/error boundary #3

Merged
merged 4 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

Loading