-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
IWF-357: Add internal channel TypeStore (#70)
* IWF-357: Add internal channel TypeStore * IWF-357: Lint * IWF-357: Fix * IWF-357: Fix * IWF-357: Fix * IWF-357: Fix * IWF-357: Fix * IWF-357: Lint * IWF-357: Add test * IWF-357: Lint * IWF-357: Change class name * IWF-357: Address MR comments * IWF-357: Lint * IWF-357: Refactor * IWF-357: Fix test
- Loading branch information
1 parent
b810adb
commit a85eb32
Showing
8 changed files
with
236 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
iwf/tests/test_internal_channel_with_no_prefix_channel.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import inspect | ||
import time | ||
import unittest | ||
|
||
from iwf.client import Client | ||
from iwf.command_request import CommandRequest, InternalChannelCommand | ||
from iwf.command_results import CommandResults | ||
from iwf.communication import Communication | ||
from iwf.communication_schema import CommunicationMethod, CommunicationSchema | ||
from iwf.persistence import Persistence | ||
from iwf.state_decision import StateDecision | ||
from iwf.state_schema import StateSchema | ||
from iwf.tests.worker_server import registry | ||
from iwf.workflow import ObjectWorkflow | ||
from iwf.workflow_context import WorkflowContext | ||
from iwf.workflow_state import T, WorkflowState | ||
|
||
internal_channel_name = "internal-channel-1" | ||
|
||
test_non_prefix_channel_name = "test-channel-" | ||
test_non_prefix_channel_name_with_suffix = test_non_prefix_channel_name + "abc" | ||
|
||
|
||
class InitState(WorkflowState[None]): | ||
def execute( | ||
self, | ||
ctx: WorkflowContext, | ||
input: T, | ||
command_results: CommandResults, | ||
persistence: Persistence, | ||
communication: Communication, | ||
) -> StateDecision: | ||
return StateDecision.multi_next_states( | ||
WaitAnyWithPublishState, WaitAllThenPublishState | ||
) | ||
|
||
|
||
class WaitAnyWithPublishState(WorkflowState[None]): | ||
def wait_until( | ||
self, | ||
ctx: WorkflowContext, | ||
input: T, | ||
persistence: Persistence, | ||
communication: Communication, | ||
) -> CommandRequest: | ||
# Trying to publish to a non-existing channel; this would only work if test_channel_name_non_prefix was defined as a prefix channel | ||
communication.publish_to_internal_channel( | ||
test_non_prefix_channel_name_with_suffix, "str-value-for-prefix" | ||
) | ||
return CommandRequest.for_any_command_completed( | ||
InternalChannelCommand.by_name(internal_channel_name), | ||
) | ||
|
||
def execute( | ||
self, | ||
ctx: WorkflowContext, | ||
input: T, | ||
command_results: CommandResults, | ||
persistence: Persistence, | ||
communication: Communication, | ||
) -> StateDecision: | ||
return StateDecision.graceful_complete_workflow() | ||
|
||
|
||
class WaitAllThenPublishState(WorkflowState[None]): | ||
def wait_until( | ||
self, | ||
ctx: WorkflowContext, | ||
input: T, | ||
persistence: Persistence, | ||
communication: Communication, | ||
) -> CommandRequest: | ||
return CommandRequest.for_all_command_completed( | ||
InternalChannelCommand.by_name(test_non_prefix_channel_name), | ||
) | ||
|
||
def execute( | ||
self, | ||
ctx: WorkflowContext, | ||
input: T, | ||
command_results: CommandResults, | ||
persistence: Persistence, | ||
communication: Communication, | ||
) -> StateDecision: | ||
communication.publish_to_internal_channel(internal_channel_name, None) | ||
return StateDecision.dead_end | ||
|
||
|
||
class InternalChannelWorkflowWithNoPrefixChannel(ObjectWorkflow): | ||
def get_workflow_states(self) -> StateSchema: | ||
return StateSchema.with_starting_state( | ||
InitState(), WaitAnyWithPublishState(), WaitAllThenPublishState() | ||
) | ||
|
||
def get_communication_schema(self) -> CommunicationSchema: | ||
return CommunicationSchema.create( | ||
CommunicationMethod.internal_channel_def(internal_channel_name, type(None)), | ||
# Defining a standard channel (non-prefix) to make sure messages to the channel with a suffix added will not be accepted | ||
CommunicationMethod.internal_channel_def(test_non_prefix_channel_name, str), | ||
) | ||
|
||
|
||
wf = InternalChannelWorkflowWithNoPrefixChannel() | ||
registry.add_workflow(wf) | ||
client = Client(registry) | ||
|
||
|
||
class TestInternalChannelWithNoPrefix(unittest.TestCase): | ||
def test_internal_channel_workflow_with_no_prefix_channel(self): | ||
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}" | ||
|
||
client.start_workflow( | ||
InternalChannelWorkflowWithNoPrefixChannel, wf_id, 5, None | ||
) | ||
|
||
with self.assertRaises(Exception) as context: | ||
client.wait_for_workflow_completion(wf_id, None) | ||
|
||
self.assertIn("FAILED", context.exception.workflow_status) | ||
self.assertIn( | ||
f"WorkerExecutionError: InternalChannel channel_name is not defined {test_non_prefix_channel_name_with_suffix}", | ||
context.exception.error_message, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
from typing import Optional | ||
from enum import Enum | ||
|
||
from iwf.communication_schema import CommunicationMethod | ||
from iwf.errors import WorkflowDefinitionError, NotRegisteredError | ||
|
||
|
||
class Type(Enum): | ||
INTERNAL_CHANNEL = 1 | ||
# TODO: extend to other types | ||
# DATA_ATTRIBUTE = 2 | ||
# SIGNAL_CHANNEL = 3 | ||
|
||
|
||
class TypeStore: | ||
_class_type: Type | ||
_name_to_type_store: dict[str, Optional[type]] | ||
_prefix_to_type_store: dict[str, Optional[type]] | ||
|
||
def __init__(self, class_type: Type): | ||
self._class_type = class_type | ||
self._name_to_type_store = dict() | ||
self._prefix_to_type_store = dict() | ||
|
||
def is_valid_name_or_prefix(self, name: str) -> bool: | ||
t = self._do_get_type(name) | ||
return t is not None | ||
|
||
def get_type(self, name: str) -> type: | ||
t = self._do_get_type(name) | ||
|
||
if t is None: | ||
raise NotRegisteredError(f"{self._class_type} not registered: {name}") | ||
|
||
return t | ||
|
||
def add_internal_channel_def(self, obj: CommunicationMethod): | ||
if self._class_type != Type.INTERNAL_CHANNEL: | ||
raise ValueError( | ||
f"Cannot add internal channel definition to {self._class_type}" | ||
) | ||
self._do_add_to_store(obj.is_prefix, obj.name, obj.value_type) | ||
|
||
def _do_get_type(self, name: str) -> Optional[type]: | ||
if name in self._name_to_type_store: | ||
return self._name_to_type_store[name] | ||
|
||
prefixes = self._prefix_to_type_store.keys() | ||
|
||
first = next((prefix for prefix in prefixes if name.startswith(prefix)), None) | ||
|
||
if first is None: | ||
return None | ||
|
||
return self._prefix_to_type_store.get(first, None) | ||
|
||
def _do_add_to_store(self, is_prefix: bool, name: str, t: Optional[type]): | ||
if is_prefix: | ||
store = self._prefix_to_type_store | ||
else: | ||
store = self._name_to_type_store | ||
|
||
if name in store: | ||
raise WorkflowDefinitionError( | ||
f"{self._class_type} name/prefix {name} already exists" | ||
) | ||
|
||
store[name] = t |
Oops, something went wrong.