iwf-sdk 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iwf/.openapi-python-client-config.yaml +1 -0
- iwf/__init__.py +0 -0
- iwf/client.py +554 -0
- iwf/client_options.py +20 -0
- iwf/command_request.py +116 -0
- iwf/command_results.py +102 -0
- iwf/communication.py +127 -0
- iwf/communication_schema.py +54 -0
- iwf/data_attributes.py +61 -0
- iwf/errors.py +118 -0
- iwf/iwf_api/__init__.py +8 -0
- iwf/iwf_api/api/__init__.py +1 -0
- iwf/iwf_api/api/default/__init__.py +1 -0
- iwf/iwf_api/api/default/get_info_healthcheck.py +122 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_config_update.py +163 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_dataobjects_get.py +165 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_dataobjects_set.py +163 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_get.py +165 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_internal_dump.py +165 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_reset.py +165 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_rpc.py +165 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_search.py +165 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_searchattributes_get.py +165 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_searchattributes_set.py +163 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_signal.py +163 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_start.py +165 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_stop.py +163 -0
- iwf/iwf_api/api/default/post_api_v1_workflow_timer_skip.py +163 -0
- iwf/iwf_api/api/default/post_api_v_1_workflow_get_with_wait.py +165 -0
- iwf/iwf_api/api/default/post_api_v_1_workflow_state_decide.py +165 -0
- iwf/iwf_api/api/default/post_api_v_1_workflow_state_start.py +165 -0
- iwf/iwf_api/api/default/post_api_v_1_workflow_trigger_continue_as_new.py +163 -0
- iwf/iwf_api/api/default/post_api_v_1_workflow_wait_for_state_completion.py +161 -0
- iwf/iwf_api/api/default/post_api_v_1_workflow_worker_rpc.py +165 -0
- iwf/iwf_api/client.py +268 -0
- iwf/iwf_api/errors.py +16 -0
- iwf/iwf_api/models/__init__.py +163 -0
- iwf/iwf_api/models/channel_info.py +59 -0
- iwf/iwf_api/models/channel_request_status.py +9 -0
- iwf/iwf_api/models/command_combination.py +61 -0
- iwf/iwf_api/models/command_request.py +152 -0
- iwf/iwf_api/models/command_results.py +135 -0
- iwf/iwf_api/models/command_waiting_type.py +10 -0
- iwf/iwf_api/models/context.py +104 -0
- iwf/iwf_api/models/encoded_object.py +68 -0
- iwf/iwf_api/models/error_response.py +103 -0
- iwf/iwf_api/models/error_sub_status.py +12 -0
- iwf/iwf_api/models/execute_api_failure_policy.py +9 -0
- iwf/iwf_api/models/executing_state_id_mode.py +10 -0
- iwf/iwf_api/models/health_info.py +77 -0
- iwf/iwf_api/models/id_reuse_policy.py +11 -0
- iwf/iwf_api/models/inter_state_channel_command.py +88 -0
- iwf/iwf_api/models/inter_state_channel_publishing.py +83 -0
- iwf/iwf_api/models/inter_state_channel_result.py +100 -0
- iwf/iwf_api/models/key_value.py +81 -0
- iwf/iwf_api/models/persistence_loading_policy.py +98 -0
- iwf/iwf_api/models/persistence_loading_type.py +12 -0
- iwf/iwf_api/models/retry_policy.py +95 -0
- iwf/iwf_api/models/search_attribute.py +123 -0
- iwf/iwf_api/models/search_attribute_key_and_type.py +76 -0
- iwf/iwf_api/models/search_attribute_value_type.py +14 -0
- iwf/iwf_api/models/signal_command.py +88 -0
- iwf/iwf_api/models/signal_result.py +100 -0
- iwf/iwf_api/models/state_completion_output.py +91 -0
- iwf/iwf_api/models/state_decision.py +93 -0
- iwf/iwf_api/models/state_movement.py +110 -0
- iwf/iwf_api/models/timer_command.py +70 -0
- iwf/iwf_api/models/timer_result.py +69 -0
- iwf/iwf_api/models/timer_status.py +9 -0
- iwf/iwf_api/models/trigger_continue_as_new_request.py +70 -0
- iwf/iwf_api/models/wait_until_api_failure_policy.py +9 -0
- iwf/iwf_api/models/worker_error_response.py +68 -0
- iwf/iwf_api/models/workflow_already_started_options.py +70 -0
- iwf/iwf_api/models/workflow_conditional_close.py +98 -0
- iwf/iwf_api/models/workflow_conditional_close_type.py +10 -0
- iwf/iwf_api/models/workflow_config.py +103 -0
- iwf/iwf_api/models/workflow_config_executing_state_id_mode.py +10 -0
- iwf/iwf_api/models/workflow_config_update_request.py +84 -0
- iwf/iwf_api/models/workflow_dump_request.py +83 -0
- iwf/iwf_api/models/workflow_dump_response.py +75 -0
- iwf/iwf_api/models/workflow_error_type.py +13 -0
- iwf/iwf_api/models/workflow_get_data_objects_request.py +90 -0
- iwf/iwf_api/models/workflow_get_data_objects_response.py +75 -0
- iwf/iwf_api/models/workflow_get_request.py +88 -0
- iwf/iwf_api/models/workflow_get_response.py +121 -0
- iwf/iwf_api/models/workflow_get_search_attributes_request.py +95 -0
- iwf/iwf_api/models/workflow_get_search_attributes_response.py +75 -0
- iwf/iwf_api/models/workflow_reset_request.py +142 -0
- iwf/iwf_api/models/workflow_reset_response.py +59 -0
- iwf/iwf_api/models/workflow_reset_type.py +12 -0
- iwf/iwf_api/models/workflow_retry_policy.py +86 -0
- iwf/iwf_api/models/workflow_rpc_request.py +173 -0
- iwf/iwf_api/models/workflow_rpc_response.py +72 -0
- iwf/iwf_api/models/workflow_search_request.py +79 -0
- iwf/iwf_api/models/workflow_search_response.py +84 -0
- iwf/iwf_api/models/workflow_search_response_entry.py +67 -0
- iwf/iwf_api/models/workflow_set_data_objects_request.py +95 -0
- iwf/iwf_api/models/workflow_set_search_attributes_request.py +95 -0
- iwf/iwf_api/models/workflow_signal_request.py +100 -0
- iwf/iwf_api/models/workflow_skip_timer_request.py +96 -0
- iwf/iwf_api/models/workflow_start_options.py +196 -0
- iwf/iwf_api/models/workflow_start_request.py +174 -0
- iwf/iwf_api/models/workflow_start_response.py +59 -0
- iwf/iwf_api/models/workflow_state_execute_request.py +180 -0
- iwf/iwf_api/models/workflow_state_execute_response.py +184 -0
- iwf/iwf_api/models/workflow_state_options.py +284 -0
- iwf/iwf_api/models/workflow_state_wait_until_request.py +143 -0
- iwf/iwf_api/models/workflow_state_wait_until_response.py +184 -0
- iwf/iwf_api/models/workflow_status.py +14 -0
- iwf/iwf_api/models/workflow_stop_request.py +96 -0
- iwf/iwf_api/models/workflow_stop_type.py +10 -0
- iwf/iwf_api/models/workflow_wait_for_state_completion_request.py +97 -0
- iwf/iwf_api/models/workflow_wait_for_state_completion_response.py +72 -0
- iwf/iwf_api/models/workflow_worker_rpc_request.py +181 -0
- iwf/iwf_api/models/workflow_worker_rpc_request_internal_channel_infos.py +57 -0
- iwf/iwf_api/models/workflow_worker_rpc_request_signal_channel_infos.py +57 -0
- iwf/iwf_api/models/workflow_worker_rpc_response.py +193 -0
- iwf/iwf_api/py.typed +1 -0
- iwf/iwf_api/types.py +46 -0
- iwf/object_encoder.py +804 -0
- iwf/persistence.py +80 -0
- iwf/persistence_options.py +10 -0
- iwf/persistence_schema.py +44 -0
- iwf/py.typed +1 -0
- iwf/registry.py +195 -0
- iwf/reset_workflow_type_and_options.py +63 -0
- iwf/rpc.py +86 -0
- iwf/search_attributes.py +175 -0
- iwf/state_decision.py +147 -0
- iwf/state_execution_locals.py +57 -0
- iwf/state_movement.py +109 -0
- iwf/state_schema.py +40 -0
- iwf/stop_workflow_options.py +9 -0
- iwf/tests/__init__.py +70 -0
- iwf/tests/iwf-service-env/.env +7 -0
- iwf/tests/iwf-service-env/docker-compose-init.sh +44 -0
- iwf/tests/iwf-service-env/docker-compose.yml +85 -0
- iwf/tests/iwf-service-env/dynamicconfig/README.md +39 -0
- iwf/tests/iwf-service-env/dynamicconfig/development-sql.yaml +6 -0
- iwf/tests/iwf-service-env/dynamicconfig/docker.yaml +0 -0
- iwf/tests/test_abnormal_exit_workflow.py +34 -0
- iwf/tests/test_basic_workflow.py +61 -0
- iwf/tests/test_conditional_complete.py +42 -0
- iwf/tests/test_describe_workflow.py +31 -0
- iwf/tests/test_empty_data_decodes_properly.py +65 -0
- iwf/tests/test_internal_channel.py +19 -0
- iwf/tests/test_internal_channel_with_no_prefix_channel.py +32 -0
- iwf/tests/test_persistence_data_attributes.py +53 -0
- iwf/tests/test_persistence_search_attributes.py +118 -0
- iwf/tests/test_persistence_state_execution_locals.py +29 -0
- iwf/tests/test_rpc.py +55 -0
- iwf/tests/test_rpc_with_memo.py +186 -0
- iwf/tests/test_rpc_with_memo_duplicate_java_tests.py +108 -0
- iwf/tests/test_signal.py +42 -0
- iwf/tests/test_skip_wait_until.py +71 -0
- iwf/tests/test_state_failure_recovery.py +19 -0
- iwf/tests/test_timer.py +26 -0
- iwf/tests/test_wait_for_state_execution_completion.py +44 -0
- iwf/tests/test_workflow_errors.py +78 -0
- iwf/tests/test_workflow_state_options.py +109 -0
- iwf/tests/test_workflow_state_options_override.py +35 -0
- iwf/tests/worker_server.py +60 -0
- iwf/tests/workflows/abnormal_exit_workflow.py +34 -0
- iwf/tests/workflows/basic_workflow.py +55 -0
- iwf/tests/workflows/conditional_complete_workflow.py +87 -0
- iwf/tests/workflows/describe_workflow.py +38 -0
- iwf/tests/workflows/empty_data_workflow.py +37 -0
- iwf/tests/workflows/internal_channel_workflow.py +123 -0
- iwf/tests/workflows/internal_channel_workflow_with_no_prefix_channel.py +94 -0
- iwf/tests/workflows/java_duplicate_rpc_memo_workflow.py +271 -0
- iwf/tests/workflows/persistence_data_attributes_workflow.py +90 -0
- iwf/tests/workflows/persistence_search_attributes_workflow.py +153 -0
- iwf/tests/workflows/persistence_state_execution_local_workflow.py +55 -0
- iwf/tests/workflows/recovery_workflow.py +76 -0
- iwf/tests/workflows/rpc_memo_workflow.py +224 -0
- iwf/tests/workflows/rpc_workflow.py +111 -0
- iwf/tests/workflows/state_options_override_workflow.py +86 -0
- iwf/tests/workflows/state_options_workflow.py +78 -0
- iwf/tests/workflows/timer_workflow.py +38 -0
- iwf/tests/workflows/wait_for_state_with_state_execution_id_workflow.py +63 -0
- iwf/tests/workflows/wait_for_state_with_wait_for_key_workflow.py +64 -0
- iwf/tests/workflows/wait_internal_channel_workflow.py +39 -0
- iwf/tests/workflows/wait_signal_workflow.py +140 -0
- iwf/type_store.py +91 -0
- iwf/unregistered_client.py +581 -0
- iwf/utils/__init__.py +0 -0
- iwf/utils/iwf_typing.py +17 -0
- iwf/utils/persistence_utils.py +23 -0
- iwf/worker_service.py +427 -0
- iwf/workflow.py +72 -0
- iwf/workflow_context.py +36 -0
- iwf/workflow_info.py +7 -0
- iwf/workflow_options.py +65 -0
- iwf/workflow_state.py +120 -0
- iwf/workflow_state_options.py +146 -0
- iwf_sdk-0.0.1.dist-info/METADATA +143 -0
- iwf_sdk-0.0.1.dist-info/RECORD +199 -0
- iwf_sdk-0.0.1.dist-info/WHEEL +4 -0
- iwf_sdk-0.0.1.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
project_name_override: iwf_api
|
iwf/__init__.py
ADDED
|
File without changes
|
iwf/client.py
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from typing import Any, Callable, List, Optional, Type, TypeVar, Union
|
|
3
|
+
|
|
4
|
+
from typing_extensions import deprecated
|
|
5
|
+
|
|
6
|
+
from iwf.client_options import ClientOptions
|
|
7
|
+
from iwf.errors import InvalidArgumentError, NotRegisteredError, WorkflowDefinitionError
|
|
8
|
+
from iwf.iwf_api.models import (
|
|
9
|
+
SearchAttribute,
|
|
10
|
+
SearchAttributeKeyAndType,
|
|
11
|
+
SearchAttributeValueType,
|
|
12
|
+
)
|
|
13
|
+
from iwf.iwf_api.types import Unset
|
|
14
|
+
from iwf.registry import Registry
|
|
15
|
+
from iwf.reset_workflow_type_and_options import ResetWorkflowTypeAndOptions
|
|
16
|
+
from iwf.stop_workflow_options import StopWorkflowOptions
|
|
17
|
+
from iwf.unregistered_client import UnregisteredClient, UnregisteredWorkflowOptions
|
|
18
|
+
from iwf.utils.iwf_typing import unset_to_none
|
|
19
|
+
from iwf.utils.persistence_utils import get_search_attribute_value
|
|
20
|
+
from iwf.workflow import ObjectWorkflow, get_workflow_type_by_class
|
|
21
|
+
from iwf.workflow_info import WorkflowInfo
|
|
22
|
+
from iwf.workflow_options import WorkflowOptions
|
|
23
|
+
from iwf.workflow_state import (
|
|
24
|
+
WorkflowState,
|
|
25
|
+
get_state_id,
|
|
26
|
+
get_state_id_by_class,
|
|
27
|
+
get_state_execution_id,
|
|
28
|
+
should_skip_wait_until,
|
|
29
|
+
)
|
|
30
|
+
from iwf.workflow_state_options import _to_idl_state_options
|
|
31
|
+
|
|
32
|
+
T = TypeVar("T")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_workflow_type_by_rpc_method(meth) -> str:
|
|
36
|
+
if inspect.ismethod(meth):
|
|
37
|
+
return inspect.getmro(meth.__self__.__class__)[0].__name__
|
|
38
|
+
if inspect.isfunction(meth):
|
|
39
|
+
return meth.__qualname__.split(".<locals>", 1)[0].rsplit(".", 1)[0]
|
|
40
|
+
raise InvalidArgumentError(f"method {meth} is not a RPC method")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Client:
|
|
44
|
+
def __init__(self, registry: Registry, options: Optional[ClientOptions] = None):
|
|
45
|
+
self._registry = registry
|
|
46
|
+
if options is None:
|
|
47
|
+
options = ClientOptions.local_default()
|
|
48
|
+
self._options = options
|
|
49
|
+
self._unregistered_client = UnregisteredClient(options)
|
|
50
|
+
|
|
51
|
+
def start_workflow(
|
|
52
|
+
self,
|
|
53
|
+
wf_class: type[ObjectWorkflow],
|
|
54
|
+
wf_id: str,
|
|
55
|
+
timeout_seconds: int,
|
|
56
|
+
input: Any = None,
|
|
57
|
+
options: Optional[WorkflowOptions] = None,
|
|
58
|
+
) -> str:
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
wf_class: the workflow definition class
|
|
63
|
+
wf_id: workflowId
|
|
64
|
+
timeout_seconds: the timeout. Use zero for infinite timeout(only works for Temporal as backend)
|
|
65
|
+
input: input of the workflow, aka, the input of the starting state of the workflow
|
|
66
|
+
options: advanced options
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
workflow_run_id: the run id of the started workflow
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ClientSideError for non-retryable error
|
|
73
|
+
ServerSideError for server error
|
|
74
|
+
"""
|
|
75
|
+
wf_type = get_workflow_type_by_class(wf_class)
|
|
76
|
+
self._registry.get_workflow_with_check(wf_type)
|
|
77
|
+
|
|
78
|
+
starting_state = self._registry.get_workflow_starting_state(wf_type)
|
|
79
|
+
unreg_opts = UnregisteredWorkflowOptions()
|
|
80
|
+
|
|
81
|
+
if options is not None:
|
|
82
|
+
unreg_opts.workflow_id_reuse_policy = options.workflow_id_reuse_policy
|
|
83
|
+
unreg_opts.workflow_retry_policy = options.workflow_retry_policy
|
|
84
|
+
unreg_opts.cron_schedule = options.workflow_cron_schedule
|
|
85
|
+
unreg_opts.workflow_start_delay_seconds = (
|
|
86
|
+
options.workflow_start_delay_seconds
|
|
87
|
+
)
|
|
88
|
+
unreg_opts.workflow_already_started_options = (
|
|
89
|
+
options.workflow_already_started_options
|
|
90
|
+
)
|
|
91
|
+
unreg_opts.initial_data_attributes = options.initial_data_attributes
|
|
92
|
+
|
|
93
|
+
unreg_opts.workflow_config_override = options.workflow_config_override
|
|
94
|
+
|
|
95
|
+
unreg_opts.wait_for_completion_state_ids = (
|
|
96
|
+
options.wait_for_completion_state_ids
|
|
97
|
+
)
|
|
98
|
+
unreg_opts.wait_for_completion_state_execution_ids = (
|
|
99
|
+
options.wait_for_completion_state_execution_ids
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if options.initial_search_attributes:
|
|
103
|
+
sa_types = self._registry.get_search_attribute_types(wf_type)
|
|
104
|
+
converted_sas = convert_to_sa_list(
|
|
105
|
+
sa_types, options.initial_search_attributes
|
|
106
|
+
)
|
|
107
|
+
unreg_opts.initial_search_attributes = converted_sas
|
|
108
|
+
|
|
109
|
+
schema_options = self._registry.get_persistence_options(wf_type)
|
|
110
|
+
if schema_options.enable_caching:
|
|
111
|
+
unreg_opts.using_memo_for_data_attributes = schema_options.enable_caching
|
|
112
|
+
|
|
113
|
+
starting_state_id = None
|
|
114
|
+
|
|
115
|
+
if starting_state is not None:
|
|
116
|
+
starting_state_id = get_state_id(starting_state)
|
|
117
|
+
starting_state_opts = _to_idl_state_options(
|
|
118
|
+
should_skip_wait_until(starting_state),
|
|
119
|
+
starting_state.get_state_options(),
|
|
120
|
+
self._registry.get_state_store(wf_type),
|
|
121
|
+
)
|
|
122
|
+
unreg_opts.start_state_options = starting_state_opts
|
|
123
|
+
|
|
124
|
+
return self._unregistered_client.start_workflow(
|
|
125
|
+
wf_type, wf_id, starting_state_id, timeout_seconds, input, unreg_opts
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
@deprecated("use wait_for_workflow_completion instead")
|
|
129
|
+
def get_simple_workflow_result_with_wait(
|
|
130
|
+
self,
|
|
131
|
+
workflow_id: str,
|
|
132
|
+
type_hint: Optional[Type[T]] = None,
|
|
133
|
+
) -> Optional[T]:
|
|
134
|
+
return self._unregistered_client.get_simple_workflow_result_with_wait(
|
|
135
|
+
workflow_id, "", type_hint
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def wait_for_workflow_completion(
|
|
139
|
+
self,
|
|
140
|
+
workflow_id: str,
|
|
141
|
+
type_hint: Optional[Type[T]] = None,
|
|
142
|
+
) -> Optional[T]:
|
|
143
|
+
"""
|
|
144
|
+
This will be waiting up to 5~60 seconds (configurable in HTTP client and capped by server) for workflow to
|
|
145
|
+
complete, and return the workflow completion result.
|
|
146
|
+
Args:
|
|
147
|
+
workflow_id: workflowId
|
|
148
|
+
type_hint: the type of workflow result
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
the completion result if there is one
|
|
152
|
+
Raises
|
|
153
|
+
WorkflowAbnormalExitError if workflow failed/timeout/canceled/terminated
|
|
154
|
+
WorkflowStillRunningError if workflow is still running after exceeding the waiting timeout(HTTP timeout)
|
|
155
|
+
ClientSideError for non-retryable error
|
|
156
|
+
ServerSideError for server error
|
|
157
|
+
"""
|
|
158
|
+
return self._unregistered_client.get_simple_workflow_result_with_wait(
|
|
159
|
+
workflow_id, "", type_hint
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def stop_workflow(
|
|
163
|
+
self,
|
|
164
|
+
workflow_id: str,
|
|
165
|
+
options: Optional[StopWorkflowOptions] = None,
|
|
166
|
+
):
|
|
167
|
+
return self._unregistered_client.stop_workflow(workflow_id, "", options)
|
|
168
|
+
|
|
169
|
+
def get_all_workflow_data_attributes(
|
|
170
|
+
self,
|
|
171
|
+
workflow_class: type[ObjectWorkflow],
|
|
172
|
+
workflow_id: str,
|
|
173
|
+
workflow_run_id: str = "",
|
|
174
|
+
):
|
|
175
|
+
return self.get_workflow_data_attributes(
|
|
176
|
+
workflow_class, workflow_id, workflow_run_id, None
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def get_workflow_data_attributes(
|
|
180
|
+
self,
|
|
181
|
+
workflow_class: type[ObjectWorkflow],
|
|
182
|
+
workflow_id: str,
|
|
183
|
+
workflow_run_id: str = "",
|
|
184
|
+
keys: Optional[List[str]] = None,
|
|
185
|
+
):
|
|
186
|
+
wf_type = get_workflow_type_by_class(workflow_class)
|
|
187
|
+
data_attr_type_store = self._registry.get_data_attribute_types(wf_type)
|
|
188
|
+
if keys:
|
|
189
|
+
for key in keys:
|
|
190
|
+
if not data_attr_type_store.is_valid_name_or_prefix(key):
|
|
191
|
+
raise NotRegisteredError(
|
|
192
|
+
f"key {key} is not registered in workflow {wf_type}"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
schema_options = self._registry.get_persistence_options(wf_type)
|
|
196
|
+
|
|
197
|
+
response = self._unregistered_client.get_workflow_data_attributes(
|
|
198
|
+
workflow_id, workflow_run_id, keys, schema_options.enable_caching
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if not response.objects:
|
|
202
|
+
raise RuntimeError("data attributes not returned")
|
|
203
|
+
|
|
204
|
+
res = {}
|
|
205
|
+
for kv in response.objects:
|
|
206
|
+
k = unset_to_none(kv.key)
|
|
207
|
+
if k and kv.value:
|
|
208
|
+
res[kv.key] = self._options.object_encoder.decode(
|
|
209
|
+
kv.value, data_attr_type_store.get_type(k)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return res
|
|
213
|
+
|
|
214
|
+
def set_workflow_data_attributes(
|
|
215
|
+
self,
|
|
216
|
+
workflow_class: type[ObjectWorkflow],
|
|
217
|
+
workflow_id: str,
|
|
218
|
+
workflow_run_id: str = "",
|
|
219
|
+
data_attributes: dict[str, Any] = dict(),
|
|
220
|
+
):
|
|
221
|
+
wf_type = get_workflow_type_by_class(workflow_class)
|
|
222
|
+
data_attr_type_store = self._registry.get_data_attribute_types(wf_type)
|
|
223
|
+
for key, value in data_attributes.items():
|
|
224
|
+
if not data_attr_type_store.is_valid_name_or_prefix(key):
|
|
225
|
+
raise NotRegisteredError(f"data attribute {key} is not registered")
|
|
226
|
+
|
|
227
|
+
data_attr_type = data_attr_type_store.get_type(key)
|
|
228
|
+
if not isinstance(value, data_attr_type):
|
|
229
|
+
raise NotRegisteredError(
|
|
230
|
+
f"data attribute {key} is not registered as {type(value)}"
|
|
231
|
+
)
|
|
232
|
+
return self._unregistered_client.set_workflow_data_attributes(
|
|
233
|
+
workflow_id, workflow_run_id, data_attributes
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def invoke_rpc(
|
|
237
|
+
self,
|
|
238
|
+
workflow_id: str,
|
|
239
|
+
rpc: Callable, # this can be a function: RPCWorkflow.rpc_method or a method: workflow_instance.rpc_method
|
|
240
|
+
input: Any = None,
|
|
241
|
+
return_type_hint: Optional[Type[T]] = None,
|
|
242
|
+
) -> Optional[T]:
|
|
243
|
+
wf_type = get_workflow_type_by_rpc_method(rpc)
|
|
244
|
+
rpc_name = rpc.__name__
|
|
245
|
+
rpc_info = self._registry.get_rpc_infos(wf_type)[rpc_name]
|
|
246
|
+
schema_options = self._registry.get_persistence_options(wf_type)
|
|
247
|
+
use_memo = schema_options.enable_caching
|
|
248
|
+
if rpc_info.bypass_caching_for_strong_consistency:
|
|
249
|
+
use_memo = False
|
|
250
|
+
|
|
251
|
+
return self._unregistered_client.invoke_rpc(
|
|
252
|
+
input=input,
|
|
253
|
+
workflow_id=workflow_id,
|
|
254
|
+
workflow_run_id="",
|
|
255
|
+
rpc_name=rpc_name,
|
|
256
|
+
timeout_seconds=rpc_info.timeout_seconds,
|
|
257
|
+
data_attribute_policy=rpc_info.data_attribute_loading_policy,
|
|
258
|
+
use_memo_for_data_attributes=use_memo,
|
|
259
|
+
all_defined_search_attribute_types=[],
|
|
260
|
+
return_type_hint=return_type_hint,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def signal_workflow(
|
|
264
|
+
self,
|
|
265
|
+
workflow_id: str,
|
|
266
|
+
signal_channel_name: str,
|
|
267
|
+
signal_value: Optional[Any] = None,
|
|
268
|
+
):
|
|
269
|
+
return self._unregistered_client.signal_workflow(
|
|
270
|
+
workflow_id, "", signal_channel_name, signal_value
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def reset_workflow(
|
|
274
|
+
self,
|
|
275
|
+
workflow_id: str,
|
|
276
|
+
reset_workflow_type_and_options: ResetWorkflowTypeAndOptions,
|
|
277
|
+
):
|
|
278
|
+
return self._unregistered_client.reset_workflow(
|
|
279
|
+
workflow_id, "", reset_workflow_type_and_options
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
def describe_workflow(
|
|
283
|
+
self,
|
|
284
|
+
workflow_id: str,
|
|
285
|
+
workflow_run_id: Optional[str] = None,
|
|
286
|
+
):
|
|
287
|
+
run_id = workflow_run_id if workflow_run_id is not None else ""
|
|
288
|
+
|
|
289
|
+
response = self._unregistered_client.get_workflow(workflow_id, run_id)
|
|
290
|
+
return WorkflowInfo(workflow_status=response.workflow_status)
|
|
291
|
+
|
|
292
|
+
def skip_timer_by_command_id(
|
|
293
|
+
self,
|
|
294
|
+
workflow_id: str,
|
|
295
|
+
workflow_state_id: str,
|
|
296
|
+
timer_command_id: str,
|
|
297
|
+
state_execution_number: int = 1,
|
|
298
|
+
):
|
|
299
|
+
return self._unregistered_client.skip_timer_by_command_id(
|
|
300
|
+
workflow_id,
|
|
301
|
+
"",
|
|
302
|
+
workflow_state_id,
|
|
303
|
+
timer_command_id=timer_command_id,
|
|
304
|
+
state_execution_number=state_execution_number,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def skip_timer_at_command_index(
|
|
308
|
+
self,
|
|
309
|
+
workflow_id: str,
|
|
310
|
+
workflow_state_id: Union[str, type[WorkflowState]],
|
|
311
|
+
state_execution_number: int = 1,
|
|
312
|
+
timer_command_index: int = 0,
|
|
313
|
+
):
|
|
314
|
+
if isinstance(workflow_state_id, type):
|
|
315
|
+
state_id = get_state_id_by_class(workflow_state_id)
|
|
316
|
+
else:
|
|
317
|
+
state_id = workflow_state_id
|
|
318
|
+
return self._unregistered_client.skip_timer_at_command_index(
|
|
319
|
+
workflow_id,
|
|
320
|
+
"",
|
|
321
|
+
state_id,
|
|
322
|
+
state_execution_number,
|
|
323
|
+
timer_command_index,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def get_all_search_attributes(
|
|
327
|
+
self,
|
|
328
|
+
workflow_class: type[ObjectWorkflow],
|
|
329
|
+
workflow_id: str,
|
|
330
|
+
workflow_run_id: Optional[str] = None,
|
|
331
|
+
):
|
|
332
|
+
run_id = workflow_run_id if workflow_run_id is not None else ""
|
|
333
|
+
|
|
334
|
+
return self._do_get_workflow_search_attributes(
|
|
335
|
+
workflow_class, workflow_id, run_id
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def get_workflow_search_attributes(
|
|
339
|
+
self,
|
|
340
|
+
workflow_class: type[ObjectWorkflow],
|
|
341
|
+
workflow_id: str,
|
|
342
|
+
attribute_keys: list[str],
|
|
343
|
+
workflow_run_id: Optional[str] = None,
|
|
344
|
+
):
|
|
345
|
+
if not attribute_keys:
|
|
346
|
+
raise ValueError(
|
|
347
|
+
"attribute_keys must contain at least one entry, or use get_all_search_attributes API to get all"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
run_id = workflow_run_id if workflow_run_id is not None else ""
|
|
351
|
+
|
|
352
|
+
return self._do_get_workflow_search_attributes(
|
|
353
|
+
workflow_class, workflow_id, run_id, attribute_keys
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
def _do_get_workflow_search_attributes(
|
|
357
|
+
self,
|
|
358
|
+
workflow_class: type[ObjectWorkflow],
|
|
359
|
+
workflow_id: str,
|
|
360
|
+
workflow_run_id: str,
|
|
361
|
+
attribute_keys: Optional[list[str]] = None,
|
|
362
|
+
):
|
|
363
|
+
wf_type = get_workflow_type_by_class(workflow_class)
|
|
364
|
+
self._registry.get_workflow_with_check(wf_type)
|
|
365
|
+
|
|
366
|
+
search_attribute_types = self._registry.get_search_attribute_types(wf_type)
|
|
367
|
+
|
|
368
|
+
# if attribute keys is None, will fetch all registered search attributes from the server
|
|
369
|
+
if attribute_keys:
|
|
370
|
+
non_existing_search_attribute_list: list[str] = []
|
|
371
|
+
for attribute_key in attribute_keys:
|
|
372
|
+
if attribute_key not in search_attribute_types:
|
|
373
|
+
non_existing_search_attribute_list.append(attribute_key)
|
|
374
|
+
|
|
375
|
+
if non_existing_search_attribute_list:
|
|
376
|
+
raise InvalidArgumentError(
|
|
377
|
+
f"Search attributes not registered: {','.join(non_existing_search_attribute_list)}"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
key_and_types: list[SearchAttributeKeyAndType] = []
|
|
381
|
+
if attribute_keys is None:
|
|
382
|
+
for attribute_key, sa_type in search_attribute_types.items():
|
|
383
|
+
key_and_types.append(SearchAttributeKeyAndType(attribute_key, sa_type))
|
|
384
|
+
else:
|
|
385
|
+
for attribute_key in attribute_keys:
|
|
386
|
+
sa_type = search_attribute_types[attribute_key]
|
|
387
|
+
key_and_types.append(SearchAttributeKeyAndType(attribute_key, sa_type))
|
|
388
|
+
|
|
389
|
+
response = self._unregistered_client.get_workflow_search_attributes(
|
|
390
|
+
workflow_id, workflow_run_id, key_and_types
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
response_sas = response.search_attributes
|
|
394
|
+
|
|
395
|
+
# TODO: troubleshoot why unset_to_none doesn't work as expected with lists
|
|
396
|
+
if isinstance(response_sas, Unset) or response_sas is None:
|
|
397
|
+
raise RuntimeError("search attributes not returned")
|
|
398
|
+
|
|
399
|
+
result: dict[str, Any] = {}
|
|
400
|
+
|
|
401
|
+
for response_sa in response_sas:
|
|
402
|
+
response_sa_key = unset_to_none(response_sa.key)
|
|
403
|
+
if response_sa_key is None:
|
|
404
|
+
raise RuntimeError("search attribute key is None")
|
|
405
|
+
response_sa_type = search_attribute_types[response_sa_key]
|
|
406
|
+
value = get_search_attribute_value(response_sa_type, response_sa)
|
|
407
|
+
result[response_sa_key] = value
|
|
408
|
+
|
|
409
|
+
return result
|
|
410
|
+
|
|
411
|
+
def set_workflow_search_attributes(
|
|
412
|
+
self,
|
|
413
|
+
workflow_class: type[ObjectWorkflow],
|
|
414
|
+
workflow_id: str,
|
|
415
|
+
search_attributes: list[SearchAttribute],
|
|
416
|
+
workflow_run_id: Optional[str] = None,
|
|
417
|
+
):
|
|
418
|
+
run_id = workflow_run_id if workflow_run_id is not None else ""
|
|
419
|
+
|
|
420
|
+
return self._do_set_workflow_search_attributes(
|
|
421
|
+
workflow_class, workflow_id, run_id, search_attributes
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
"""A long poll API to wait for the completion of the state.
|
|
425
|
+
Note 1 The state_completion to wait for is needed to registered on starting workflow due to limitation in https://github.com/superdurable/iwf/issues/349
|
|
426
|
+
Note 2 The max polling time is configured in client_options (default to 10s)
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
state_class the state class.
|
|
430
|
+
workflow_id the workflowId
|
|
431
|
+
state_execution_number the state execution number. E.g. if it's 2, it means the 2nd execution of the state
|
|
432
|
+
"""
|
|
433
|
+
|
|
434
|
+
def wait_for_state_execution_completion_with_state_execution_id(
|
|
435
|
+
self,
|
|
436
|
+
state_class: type[WorkflowState],
|
|
437
|
+
workflow_id: str,
|
|
438
|
+
state_execution_number: int = 1,
|
|
439
|
+
):
|
|
440
|
+
state_execution_id = get_state_execution_id(state_class, state_execution_number)
|
|
441
|
+
|
|
442
|
+
self._unregistered_client.wait_for_state_execution_completion_with_state_execution_id(
|
|
443
|
+
workflow_id, state_execution_id
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
"""A long poll API to wait for the completion of the state.
|
|
447
|
+
Note 1 The state_completion to wait for is needed to registered on starting workflow due to limitation in https://github.com/superdurable/iwf/issues/349
|
|
448
|
+
Note 2 The max polling time is configured in client_options (default to 10s)
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
state_class the state class.
|
|
452
|
+
workflow_id the workflowId
|
|
453
|
+
wait_for_key key provided by the client and to identity workflow
|
|
454
|
+
"""
|
|
455
|
+
|
|
456
|
+
def wait_for_state_execution_completion_with_wait_for_key(
|
|
457
|
+
self, state_class: type[WorkflowState], workflow_id: str, wait_for_key: str
|
|
458
|
+
):
|
|
459
|
+
state_id = get_state_id_by_class(state_class)
|
|
460
|
+
|
|
461
|
+
self._unregistered_client.wait_for_state_execution_completion_with_wait_for_key(
|
|
462
|
+
workflow_id, state_id, wait_for_key
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
def _do_set_workflow_search_attributes(
|
|
466
|
+
self,
|
|
467
|
+
workflow_class: type[ObjectWorkflow],
|
|
468
|
+
workflow_id: str,
|
|
469
|
+
workflow_run_id: str,
|
|
470
|
+
search_attributes: list[SearchAttribute],
|
|
471
|
+
):
|
|
472
|
+
wf_type = get_workflow_type_by_class(workflow_class)
|
|
473
|
+
self._registry.get_workflow_with_check(wf_type)
|
|
474
|
+
|
|
475
|
+
search_attribute_types = self._registry.get_search_attribute_types(wf_type)
|
|
476
|
+
|
|
477
|
+
# Check that the requested sa type is registered to the key
|
|
478
|
+
for search_attribute in search_attributes:
|
|
479
|
+
sa_key = unset_to_none(search_attribute.key)
|
|
480
|
+
if sa_key is None:
|
|
481
|
+
raise RuntimeError("search attribute key is None")
|
|
482
|
+
if sa_key not in search_attribute_types:
|
|
483
|
+
raise InvalidArgumentError(f"Search attribute not registered: {sa_key}")
|
|
484
|
+
registered_value_type = search_attribute_types[sa_key]
|
|
485
|
+
|
|
486
|
+
sa_value_type = unset_to_none(search_attribute.value_type)
|
|
487
|
+
if sa_value_type is None:
|
|
488
|
+
raise RuntimeError("search value type is None")
|
|
489
|
+
|
|
490
|
+
if (
|
|
491
|
+
sa_value_type is not None
|
|
492
|
+
and registered_value_type != sa_value_type.value
|
|
493
|
+
):
|
|
494
|
+
raise ValueError(
|
|
495
|
+
f"Search attribute key, {sa_key} is registered to type {registered_value_type}, but tried to add search attribute type {sa_value_type.value}"
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
self._unregistered_client.set_workflow_search_attributes(
|
|
499
|
+
workflow_id, workflow_run_id, search_attributes
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def convert_to_sa_list(
|
|
504
|
+
sa_types: dict[str, SearchAttributeValueType], initial_sas: dict[str, Any]
|
|
505
|
+
):
|
|
506
|
+
converted_sas: list[SearchAttribute] = []
|
|
507
|
+
if initial_sas:
|
|
508
|
+
for initial_sa_key, initial_sa_val in initial_sas.items():
|
|
509
|
+
if initial_sa_key not in sa_types:
|
|
510
|
+
raise WorkflowDefinitionError(
|
|
511
|
+
f"key {initial_sa_key} is not defined as search attribute, all keys are: {','.join(sa_types)}"
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
val_type = sa_types[initial_sa_key]
|
|
515
|
+
new_sa = SearchAttribute(key=initial_sa_key, value_type=val_type)
|
|
516
|
+
is_val_correct_type = False
|
|
517
|
+
if val_type == SearchAttributeValueType.INT:
|
|
518
|
+
if isinstance(initial_sa_val, int):
|
|
519
|
+
new_sa.integer_value = initial_sa_val
|
|
520
|
+
converted_sas.append(new_sa)
|
|
521
|
+
is_val_correct_type = True
|
|
522
|
+
elif val_type == SearchAttributeValueType.DOUBLE:
|
|
523
|
+
if isinstance(initial_sa_val, float):
|
|
524
|
+
new_sa.double_value = initial_sa_val
|
|
525
|
+
converted_sas.append(new_sa)
|
|
526
|
+
is_val_correct_type = True
|
|
527
|
+
elif val_type == SearchAttributeValueType.BOOL:
|
|
528
|
+
if isinstance(initial_sa_val, bool):
|
|
529
|
+
new_sa.bool_value = initial_sa_val
|
|
530
|
+
converted_sas.append(new_sa)
|
|
531
|
+
is_val_correct_type = True
|
|
532
|
+
elif (
|
|
533
|
+
val_type == SearchAttributeValueType.KEYWORD
|
|
534
|
+
or val_type == SearchAttributeValueType.TEXT
|
|
535
|
+
or val_type == SearchAttributeValueType.DATETIME
|
|
536
|
+
):
|
|
537
|
+
if isinstance(initial_sa_val, str):
|
|
538
|
+
new_sa.string_value = initial_sa_val
|
|
539
|
+
converted_sas.append(new_sa)
|
|
540
|
+
is_val_correct_type = True
|
|
541
|
+
elif val_type == SearchAttributeValueType.KEYWORD_ARRAY:
|
|
542
|
+
if isinstance(initial_sa_val, list):
|
|
543
|
+
new_sa.string_array_value = initial_sa_val
|
|
544
|
+
converted_sas.append(new_sa)
|
|
545
|
+
is_val_correct_type = True
|
|
546
|
+
else:
|
|
547
|
+
raise ValueError("unsupported type")
|
|
548
|
+
|
|
549
|
+
if not is_val_correct_type:
|
|
550
|
+
raise InvalidArgumentError(
|
|
551
|
+
f"search attribute value is not set correctly for key {initial_sa_key} with value type {val_type}"
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
return converted_sas
|
iwf/client_options.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from iwf.object_encoder import ObjectEncoder
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class ClientOptions:
|
|
8
|
+
server_url: str
|
|
9
|
+
worker_url: str
|
|
10
|
+
object_encoder: ObjectEncoder
|
|
11
|
+
api_timeout: int = 60
|
|
12
|
+
long_poll_api_max_wait_time_seconds: int = 10
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def local_default(cls):
|
|
16
|
+
return ClientOptions(
|
|
17
|
+
server_url="http://localhost:8801",
|
|
18
|
+
worker_url="http://localhost:8802",
|
|
19
|
+
object_encoder=ObjectEncoder.default,
|
|
20
|
+
)
|
iwf/command_request.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
from iwf.errors import WorkflowDefinitionError
|
|
5
|
+
from iwf.iwf_api.models import CommandWaitingType
|
|
6
|
+
from iwf.iwf_api.models.command_combination import CommandCombination
|
|
7
|
+
from iwf.iwf_api.models.command_request import (
|
|
8
|
+
CommandRequest as IdlCommandRequest,
|
|
9
|
+
)
|
|
10
|
+
from iwf.iwf_api.models.inter_state_channel_command import (
|
|
11
|
+
InterStateChannelCommand as IdlInternalChannelCommand,
|
|
12
|
+
)
|
|
13
|
+
from iwf.iwf_api.models.signal_command import SignalCommand as IdlSignalCommand
|
|
14
|
+
from iwf.iwf_api.models.timer_command import TimerCommand as IdlTimerCommand
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class TimerCommand:
|
|
19
|
+
command_id: str
|
|
20
|
+
duration_seconds: int
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def by_seconds(cls, duration_seconds: int, command_id: Optional[str] = None):
|
|
24
|
+
return TimerCommand(
|
|
25
|
+
command_id if command_id is not None else "", duration_seconds
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class InternalChannelCommand:
|
|
31
|
+
command_id: str
|
|
32
|
+
channel_name: str
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def by_name(cls, channel_name: str, command_id: Optional[str] = None):
|
|
36
|
+
return InternalChannelCommand(
|
|
37
|
+
command_id if command_id is not None else "", channel_name
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class SignalChannelCommand:
|
|
43
|
+
command_id: str
|
|
44
|
+
channel_name: str
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def by_name(cls, channel_name: str, command_id: Optional[str] = None):
|
|
48
|
+
return SignalChannelCommand(
|
|
49
|
+
command_id if command_id is not None else "",
|
|
50
|
+
channel_name,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
BaseCommand = Union[TimerCommand, InternalChannelCommand, SignalChannelCommand]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class CommandRequest:
|
|
59
|
+
commands: list[BaseCommand]
|
|
60
|
+
command_waiting_type: CommandWaitingType
|
|
61
|
+
command_combinations: list[CommandCombination]
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def for_any_command_completed(cls, *commands: BaseCommand):
|
|
65
|
+
bc = [c for c in commands]
|
|
66
|
+
return CommandRequest(bc, CommandWaitingType.ANY_COMPLETED, [])
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def for_all_command_completed(cls, *commands: BaseCommand):
|
|
70
|
+
bc = [c for c in commands]
|
|
71
|
+
return CommandRequest(bc, CommandWaitingType.ALL_COMPLETED, [])
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def for_any_command_combination_completed(
|
|
75
|
+
cls, command_combinations_list: list[list[str]], *commands: BaseCommand
|
|
76
|
+
):
|
|
77
|
+
return CommandRequest(
|
|
78
|
+
list(commands),
|
|
79
|
+
CommandWaitingType.ANY_COMBINATION_COMPLETED,
|
|
80
|
+
[CommandCombination(c) for c in command_combinations_list],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def empty(cls):
|
|
85
|
+
return CommandRequest(list(), CommandWaitingType.ALL_COMPLETED, [])
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _to_idl_command_request(request: CommandRequest) -> IdlCommandRequest:
|
|
89
|
+
req = IdlCommandRequest(
|
|
90
|
+
command_waiting_type=request.command_waiting_type,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
timer_commands = []
|
|
94
|
+
internal_channel_commands = []
|
|
95
|
+
signal_commands = []
|
|
96
|
+
for t in request.commands:
|
|
97
|
+
if isinstance(t, TimerCommand):
|
|
98
|
+
timer_commands.append(IdlTimerCommand(t.duration_seconds, t.command_id))
|
|
99
|
+
elif isinstance(t, InternalChannelCommand):
|
|
100
|
+
internal_channel_commands.append(
|
|
101
|
+
IdlInternalChannelCommand(t.channel_name, t.command_id)
|
|
102
|
+
)
|
|
103
|
+
elif isinstance(t, SignalChannelCommand):
|
|
104
|
+
signal_commands.append(IdlSignalCommand(t.channel_name, t.command_id))
|
|
105
|
+
else:
|
|
106
|
+
raise WorkflowDefinitionError(f"unknown command {t.__class__.__qualname__}")
|
|
107
|
+
|
|
108
|
+
if len(timer_commands) > 0:
|
|
109
|
+
req.timer_commands = timer_commands
|
|
110
|
+
if len(internal_channel_commands) > 0:
|
|
111
|
+
req.inter_state_channel_commands = internal_channel_commands
|
|
112
|
+
if len(signal_commands) > 0:
|
|
113
|
+
req.signal_commands = signal_commands
|
|
114
|
+
if len(request.command_combinations) > 0:
|
|
115
|
+
req.command_combinations = request.command_combinations
|
|
116
|
+
return req
|