-
Notifications
You must be signed in to change notification settings - Fork 3
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
IWF-357: Add internal channel TypeStore #70
Changes from all commits
7c94d6b
3520331
20e0e22
ef9d285
d9f7d7c
fac26e7
0dceb5c
4a3895b
e6c4916
546730d
c5df9ca
1f3c0dc
02ae853
3a40930
3fd2401
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
) |
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 | ||
Comment on lines
+9
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JavaSDK allows prefixing SignalChannels and DataAttributes. Leaving this for future use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
|
||
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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this used anywhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed it to be used now. It was not used before, good catch |
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test added to make sure the currently existing issue is fixed. Issue description by @longquanzheng