dex-python-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.
- dex/__init__.py +14 -0
- dex/client.py +568 -0
- dex/client_options.py +34 -0
- dex/command_request.py +130 -0
- dex/command_results.py +116 -0
- dex/communication.py +141 -0
- dex/communication_schema.py +68 -0
- dex/data_attributes.py +75 -0
- dex/dexpb/__init__.py +1 -0
- dex/dexpb/dex_pb2.py +305 -0
- dex/dexpb/dex_pb2.pyi +1273 -0
- dex/dexpb/dex_pb2_grpc.py +1126 -0
- dex/errors.py +132 -0
- dex/object_encoder.py +818 -0
- dex/persistence.py +94 -0
- dex/persistence_options.py +24 -0
- dex/persistence_schema.py +58 -0
- dex/py.typed +1 -0
- dex/registry.py +209 -0
- dex/reset_workflow_type_and_options.py +77 -0
- dex/rpc.py +100 -0
- dex/search_attributes.py +189 -0
- dex/state_decision.py +161 -0
- dex/state_execution_locals.py +71 -0
- dex/state_movement.py +123 -0
- dex/state_schema.py +54 -0
- dex/stop_workflow_options.py +23 -0
- dex/tests/__init__.py +84 -0
- dex/tests/dex-service-env/.env +7 -0
- dex/tests/dex-service-env/docker-compose-init.sh +44 -0
- dex/tests/dex-service-env/docker-compose.yml +87 -0
- dex/tests/dex-service-env/dynamicconfig/README.md +39 -0
- dex/tests/dex-service-env/dynamicconfig/development-sql.yaml +6 -0
- dex/tests/dex-service-env/dynamicconfig/docker.yaml +0 -0
- dex/tests/test_abnormal_exit_workflow.py +48 -0
- dex/tests/test_basic_workflow.py +75 -0
- dex/tests/test_conditional_complete.py +56 -0
- dex/tests/test_describe_workflow.py +45 -0
- dex/tests/test_empty_data_decodes_properly.py +79 -0
- dex/tests/test_internal_channel.py +33 -0
- dex/tests/test_internal_channel_with_no_prefix_channel.py +46 -0
- dex/tests/test_persistence_data_attributes.py +67 -0
- dex/tests/test_persistence_search_attributes.py +132 -0
- dex/tests/test_persistence_state_execution_locals.py +43 -0
- dex/tests/test_rpc.py +69 -0
- dex/tests/test_rpc_with_memo.py +200 -0
- dex/tests/test_rpc_with_memo_duplicate_java_tests.py +122 -0
- dex/tests/test_signal.py +56 -0
- dex/tests/test_skip_wait_until.py +85 -0
- dex/tests/test_state_failure_recovery.py +33 -0
- dex/tests/test_timer.py +40 -0
- dex/tests/test_wait_for_state_execution_completion.py +58 -0
- dex/tests/test_workflow_errors.py +92 -0
- dex/tests/test_workflow_state_options.py +123 -0
- dex/tests/test_workflow_state_options_override.py +49 -0
- dex/tests/worker_server.py +74 -0
- dex/tests/workflows/abnormal_exit_workflow.py +48 -0
- dex/tests/workflows/basic_workflow.py +69 -0
- dex/tests/workflows/conditional_complete_workflow.py +101 -0
- dex/tests/workflows/describe_workflow.py +52 -0
- dex/tests/workflows/empty_data_workflow.py +51 -0
- dex/tests/workflows/internal_channel_workflow.py +137 -0
- dex/tests/workflows/internal_channel_workflow_with_no_prefix_channel.py +108 -0
- dex/tests/workflows/java_duplicate_rpc_memo_workflow.py +285 -0
- dex/tests/workflows/persistence_data_attributes_workflow.py +104 -0
- dex/tests/workflows/persistence_search_attributes_workflow.py +167 -0
- dex/tests/workflows/persistence_state_execution_local_workflow.py +69 -0
- dex/tests/workflows/recovery_workflow.py +90 -0
- dex/tests/workflows/rpc_memo_workflow.py +238 -0
- dex/tests/workflows/rpc_workflow.py +125 -0
- dex/tests/workflows/state_options_override_workflow.py +100 -0
- dex/tests/workflows/state_options_workflow.py +92 -0
- dex/tests/workflows/timer_workflow.py +52 -0
- dex/tests/workflows/wait_for_state_with_state_execution_id_workflow.py +77 -0
- dex/tests/workflows/wait_for_state_with_wait_for_key_workflow.py +78 -0
- dex/tests/workflows/wait_internal_channel_workflow.py +53 -0
- dex/tests/workflows/wait_signal_workflow.py +154 -0
- dex/type_store.py +105 -0
- dex/unregistered_client.py +595 -0
- dex/utils/__init__.py +14 -0
- dex/utils/dex_typing.py +31 -0
- dex/utils/persistence_utils.py +37 -0
- dex/worker_service.py +441 -0
- dex/workflow.py +86 -0
- dex/workflow_context.py +50 -0
- dex/workflow_info.py +21 -0
- dex/workflow_options.py +79 -0
- dex/workflow_state.py +134 -0
- dex/workflow_state_options.py +160 -0
- dex_python_sdk-0.0.1.dist-info/METADATA +140 -0
- dex_python_sdk-0.0.1.dist-info/RECORD +93 -0
- dex_python_sdk-0.0.1.dist-info/WHEEL +4 -0
- dex_python_sdk-0.0.1.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import http
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from http import HTTPStatus
|
|
18
|
+
from httpx import Timeout
|
|
19
|
+
from typing import Any, List, Optional, Type, TypeVar
|
|
20
|
+
|
|
21
|
+
from dex.client_options import ClientOptions
|
|
22
|
+
from dex.errors import (
|
|
23
|
+
WorkflowDefinitionError,
|
|
24
|
+
WorkflowRPCAcquiringLockFailure,
|
|
25
|
+
WorkflowRPCExecutionError,
|
|
26
|
+
WorkflowStillRunningError,
|
|
27
|
+
parse_unexpected_error,
|
|
28
|
+
process_http_error,
|
|
29
|
+
process_workflow_abnormal_exit_error,
|
|
30
|
+
InvalidArgumentError,
|
|
31
|
+
)
|
|
32
|
+
from dex.dex_api import Client, errors
|
|
33
|
+
from dex.dex_api.api.default import (
|
|
34
|
+
post_api_v1_workflow_dataobjects_get,
|
|
35
|
+
post_api_v1_workflow_dataobjects_set,
|
|
36
|
+
post_api_v1_workflow_get,
|
|
37
|
+
post_api_v1_workflow_reset,
|
|
38
|
+
post_api_v1_workflow_rpc,
|
|
39
|
+
post_api_v1_workflow_search,
|
|
40
|
+
post_api_v1_workflow_searchattributes_get,
|
|
41
|
+
post_api_v1_workflow_searchattributes_set,
|
|
42
|
+
post_api_v1_workflow_signal,
|
|
43
|
+
post_api_v1_workflow_start,
|
|
44
|
+
post_api_v1_workflow_stop,
|
|
45
|
+
post_api_v1_workflow_timer_skip,
|
|
46
|
+
post_api_v_1_workflow_get_with_wait,
|
|
47
|
+
post_api_v_1_workflow_wait_for_state_completion,
|
|
48
|
+
)
|
|
49
|
+
from dex.dex_api.models import (
|
|
50
|
+
EncodedObject,
|
|
51
|
+
ErrorResponse,
|
|
52
|
+
IDReusePolicy,
|
|
53
|
+
PersistenceLoadingPolicy,
|
|
54
|
+
SearchAttribute,
|
|
55
|
+
SearchAttributeKeyAndType,
|
|
56
|
+
WorkflowConfig,
|
|
57
|
+
WorkflowGetDataObjectsRequest,
|
|
58
|
+
WorkflowGetRequest,
|
|
59
|
+
WorkflowGetResponse,
|
|
60
|
+
WorkflowGetSearchAttributesRequest,
|
|
61
|
+
WorkflowGetSearchAttributesResponse,
|
|
62
|
+
WorkflowResetRequest,
|
|
63
|
+
WorkflowRetryPolicy,
|
|
64
|
+
WorkflowRpcRequest,
|
|
65
|
+
WorkflowRpcResponse,
|
|
66
|
+
WorkflowSearchRequest,
|
|
67
|
+
WorkflowSearchResponse,
|
|
68
|
+
WorkflowSignalRequest,
|
|
69
|
+
WorkflowSkipTimerRequest,
|
|
70
|
+
WorkflowStartOptions,
|
|
71
|
+
WorkflowStartRequest,
|
|
72
|
+
WorkflowStateOptions,
|
|
73
|
+
WorkflowStatus,
|
|
74
|
+
WorkflowStopRequest,
|
|
75
|
+
WorkflowAlreadyStartedOptions,
|
|
76
|
+
KeyValue,
|
|
77
|
+
WorkflowSetSearchAttributesRequest,
|
|
78
|
+
WorkflowWaitForStateCompletionRequest,
|
|
79
|
+
)
|
|
80
|
+
from dex.dex_api.models.workflow_get_data_objects_response import (
|
|
81
|
+
WorkflowGetDataObjectsResponse,
|
|
82
|
+
)
|
|
83
|
+
from dex.dex_api.models.workflow_set_data_objects_request import (
|
|
84
|
+
WorkflowSetDataObjectsRequest,
|
|
85
|
+
)
|
|
86
|
+
from dex.dex_api.models.workflow_start_response import WorkflowStartResponse
|
|
87
|
+
from dex.dex_api.types import Response
|
|
88
|
+
from dex.reset_workflow_type_and_options import ResetWorkflowTypeAndOptions
|
|
89
|
+
from dex.stop_workflow_options import StopWorkflowOptions
|
|
90
|
+
from dex.utils.dex_typing import assert_not_unset
|
|
91
|
+
from dex.utils.persistence_utils import get_search_attribute_value
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class UnregisteredWorkflowOptions:
|
|
96
|
+
workflow_id_reuse_policy: Optional[IDReusePolicy] = None
|
|
97
|
+
cron_schedule: Optional[str] = None
|
|
98
|
+
workflow_start_delay_seconds: Optional[int] = None
|
|
99
|
+
workflow_retry_policy: Optional[WorkflowRetryPolicy] = None
|
|
100
|
+
start_state_options: Optional[WorkflowStateOptions] = None
|
|
101
|
+
workflow_config_override: Optional[WorkflowConfig] = None
|
|
102
|
+
workflow_already_started_options: Optional[WorkflowAlreadyStartedOptions] = None
|
|
103
|
+
initial_data_attributes: Optional[dict[str, Any]] = None
|
|
104
|
+
wait_for_completion_state_execution_ids: Optional[list[str]] = None
|
|
105
|
+
wait_for_completion_state_ids: Optional[list[str]] = None
|
|
106
|
+
initial_search_attributes: Optional[list[SearchAttribute]] = None
|
|
107
|
+
using_memo_for_data_attributes: Optional[bool] = None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
T = TypeVar("T")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# from https://stackoverflow.com/questions/45028991/best-way-to-extend-httpstatus-with-custom-value
|
|
114
|
+
# HERE BE DRAGONS!
|
|
115
|
+
# DO NOT do this unless you absolutely have to.
|
|
116
|
+
def add_http_status(name, value, phrase, description=""):
|
|
117
|
+
# call our new member factory, it's essentially the `HTTPStatus.__new__` method
|
|
118
|
+
new_status = HTTPStatus.__new_member__(HTTPStatus, value, phrase, description)
|
|
119
|
+
new_status._name_ = name # store the enum's member internal name
|
|
120
|
+
new_status.__objclass__ = (
|
|
121
|
+
HTTPStatus.__class__
|
|
122
|
+
) # store the enum's member parent class
|
|
123
|
+
setattr(HTTPStatus, name, new_status) # add it to the global HTTPStatus namespace
|
|
124
|
+
HTTPStatus._member_map_[name] = new_status # add it to the name=>member map
|
|
125
|
+
HTTPStatus._member_names_.append(
|
|
126
|
+
name
|
|
127
|
+
) # append the names so it appears in __members__
|
|
128
|
+
HTTPStatus._value2member_map_[value] = new_status # add it to the value=>member map
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
add_http_status("DEX_CUSTOM_ERROR_1", 420, "DEX_CUSTOM_ERROR_1")
|
|
132
|
+
add_http_status("DEX_CUSTOM_ERROR_2", 450, "DEX_CUSTOM_ERROR_2")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class UnregisteredClient:
|
|
136
|
+
def __init__(self, client_options: ClientOptions):
|
|
137
|
+
self.client_options = client_options
|
|
138
|
+
self.api_client = Client(
|
|
139
|
+
base_url=client_options.server_url,
|
|
140
|
+
timeout=Timeout(client_options.api_timeout),
|
|
141
|
+
raise_on_unexpected_status=True,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def start_workflow(
|
|
145
|
+
self,
|
|
146
|
+
workflow_type: str,
|
|
147
|
+
workflow_id: str,
|
|
148
|
+
start_state_id: Optional[str],
|
|
149
|
+
workflow_timeout_seconds: int,
|
|
150
|
+
input: Optional[Any],
|
|
151
|
+
options: Optional[UnregisteredWorkflowOptions],
|
|
152
|
+
) -> str:
|
|
153
|
+
request = WorkflowStartRequest(
|
|
154
|
+
workflow_id=workflow_id,
|
|
155
|
+
dex_worker_url=self.client_options.worker_url,
|
|
156
|
+
dex_workflow_type=workflow_type,
|
|
157
|
+
workflow_timeout_seconds=workflow_timeout_seconds,
|
|
158
|
+
state_input=self.client_options.object_encoder.encode(input),
|
|
159
|
+
)
|
|
160
|
+
if start_state_id:
|
|
161
|
+
request.start_state_id = start_state_id
|
|
162
|
+
|
|
163
|
+
if options:
|
|
164
|
+
if options.start_state_options:
|
|
165
|
+
request.state_options = options.start_state_options
|
|
166
|
+
|
|
167
|
+
start_options = WorkflowStartOptions()
|
|
168
|
+
if options.workflow_id_reuse_policy:
|
|
169
|
+
start_options.id_reuse_policy = options.workflow_id_reuse_policy
|
|
170
|
+
if options.cron_schedule:
|
|
171
|
+
start_options.cron_schedule = options.cron_schedule
|
|
172
|
+
if options.workflow_start_delay_seconds:
|
|
173
|
+
start_options.workflow_start_delay_seconds = (
|
|
174
|
+
options.workflow_start_delay_seconds
|
|
175
|
+
)
|
|
176
|
+
if options.workflow_retry_policy:
|
|
177
|
+
start_options.retry_policy = options.workflow_retry_policy
|
|
178
|
+
|
|
179
|
+
if options.workflow_config_override:
|
|
180
|
+
start_options.workflow_config_override = (
|
|
181
|
+
options.workflow_config_override
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if options.workflow_already_started_options:
|
|
185
|
+
start_options.workflow_already_started_options = (
|
|
186
|
+
options.workflow_already_started_options
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if options.initial_search_attributes:
|
|
190
|
+
for search_attribute in options.initial_search_attributes:
|
|
191
|
+
val = get_search_attribute_value(
|
|
192
|
+
assert_not_unset(search_attribute.value_type), search_attribute
|
|
193
|
+
)
|
|
194
|
+
if val is None:
|
|
195
|
+
raise InvalidArgumentError(
|
|
196
|
+
f"search attribute value is not set correctly for key {search_attribute.key} with value type {search_attribute.value_type}"
|
|
197
|
+
)
|
|
198
|
+
start_options.search_attributes = options.initial_search_attributes
|
|
199
|
+
|
|
200
|
+
if options.initial_data_attributes:
|
|
201
|
+
das = []
|
|
202
|
+
for key, value in options.initial_data_attributes.items():
|
|
203
|
+
das.append(
|
|
204
|
+
KeyValue(key, self.client_options.object_encoder.encode(value))
|
|
205
|
+
)
|
|
206
|
+
start_options.data_attributes = das
|
|
207
|
+
|
|
208
|
+
if options.using_memo_for_data_attributes:
|
|
209
|
+
start_options.use_memo_for_data_attributes = (
|
|
210
|
+
options.using_memo_for_data_attributes
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
request.workflow_start_options = start_options
|
|
214
|
+
|
|
215
|
+
if options.wait_for_completion_state_execution_ids:
|
|
216
|
+
request.wait_for_completion_state_execution_ids = (
|
|
217
|
+
options.wait_for_completion_state_execution_ids
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if options.wait_for_completion_state_ids:
|
|
221
|
+
request.wait_for_completion_state_ids = (
|
|
222
|
+
options.wait_for_completion_state_ids
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
workflow_start_response = handler_error_and_return(
|
|
226
|
+
post_api_v1_workflow_start.sync_detailed(
|
|
227
|
+
client=self.api_client,
|
|
228
|
+
body=request,
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
if workflow_start_response is None or not isinstance(
|
|
232
|
+
workflow_start_response, WorkflowStartResponse
|
|
233
|
+
):
|
|
234
|
+
raise RuntimeError("Failed to parse WorkflowStartResponse")
|
|
235
|
+
|
|
236
|
+
return workflow_start_response.workflow_run_id
|
|
237
|
+
|
|
238
|
+
def get_simple_workflow_result_with_wait(
|
|
239
|
+
self,
|
|
240
|
+
workflow_id: str,
|
|
241
|
+
workflow_run_id: str,
|
|
242
|
+
type_hint: Optional[Type[T]] = None,
|
|
243
|
+
) -> Optional[T]:
|
|
244
|
+
"""For most cases, a workflow only has one result(one completion state)
|
|
245
|
+
Use this API to retrieve the output of the state"""
|
|
246
|
+
request = WorkflowGetRequest(
|
|
247
|
+
workflow_id=workflow_id,
|
|
248
|
+
workflow_run_id=workflow_run_id,
|
|
249
|
+
needs_results=True,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
response = post_api_v_1_workflow_get_with_wait.sync_detailed(
|
|
254
|
+
client=self.api_client,
|
|
255
|
+
body=request,
|
|
256
|
+
)
|
|
257
|
+
except errors.UnexpectedStatus as err:
|
|
258
|
+
err_resp = parse_unexpected_error(err)
|
|
259
|
+
if err.status_code == 420:
|
|
260
|
+
raise WorkflowStillRunningError(err.status_code, err_resp)
|
|
261
|
+
else:
|
|
262
|
+
raise RuntimeError(f"unknown error code {err.status_code}")
|
|
263
|
+
|
|
264
|
+
if response.status_code != http.HTTPStatus.OK:
|
|
265
|
+
assert isinstance(response.parsed, ErrorResponse)
|
|
266
|
+
raise process_http_error(response.status_code, response.parsed)
|
|
267
|
+
|
|
268
|
+
parsed = response.parsed
|
|
269
|
+
assert isinstance(parsed, WorkflowGetResponse)
|
|
270
|
+
if parsed.workflow_status != WorkflowStatus.COMPLETED:
|
|
271
|
+
raise process_workflow_abnormal_exit_error(parsed)
|
|
272
|
+
|
|
273
|
+
if not parsed.results or len(parsed.results) == 0:
|
|
274
|
+
return None
|
|
275
|
+
filtered_results = [
|
|
276
|
+
result for result in parsed.results if result.completed_state_output
|
|
277
|
+
]
|
|
278
|
+
if len(filtered_results) == 0:
|
|
279
|
+
return None
|
|
280
|
+
elif len(filtered_results) == 1:
|
|
281
|
+
result = filtered_results[0]
|
|
282
|
+
else:
|
|
283
|
+
raise WorkflowDefinitionError(
|
|
284
|
+
"workflow must have one or zero state output for using this API"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
assert isinstance(result.completed_state_output, EncodedObject)
|
|
288
|
+
return self.client_options.object_encoder.decode(
|
|
289
|
+
result.completed_state_output,
|
|
290
|
+
type_hint,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def invoke_rpc(
|
|
294
|
+
self,
|
|
295
|
+
input: Any,
|
|
296
|
+
workflow_id: str,
|
|
297
|
+
workflow_run_id: str,
|
|
298
|
+
rpc_name: str,
|
|
299
|
+
timeout_seconds: int,
|
|
300
|
+
data_attribute_policy: Optional[PersistenceLoadingPolicy],
|
|
301
|
+
all_defined_search_attribute_types: list[SearchAttributeKeyAndType],
|
|
302
|
+
return_type_hint: Optional[Type[T]] = None,
|
|
303
|
+
use_memo_for_data_attributes: bool = False,
|
|
304
|
+
search_attribute_policy: Optional[PersistenceLoadingPolicy] = None,
|
|
305
|
+
) -> Optional[T]:
|
|
306
|
+
request = WorkflowRpcRequest(
|
|
307
|
+
input_=self.client_options.object_encoder.encode(input),
|
|
308
|
+
workflow_id=workflow_id,
|
|
309
|
+
workflow_run_id=workflow_run_id,
|
|
310
|
+
rpc_name=rpc_name,
|
|
311
|
+
timeout_seconds=timeout_seconds,
|
|
312
|
+
use_memo_for_data_attributes=use_memo_for_data_attributes,
|
|
313
|
+
search_attributes=all_defined_search_attribute_types,
|
|
314
|
+
)
|
|
315
|
+
if data_attribute_policy is not None:
|
|
316
|
+
request.data_attributes_loading_policy = data_attribute_policy
|
|
317
|
+
if search_attribute_policy is not None:
|
|
318
|
+
request.search_attributes_loading_policy = search_attribute_policy
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
response = post_api_v1_workflow_rpc.sync_detailed(
|
|
322
|
+
client=self.api_client,
|
|
323
|
+
body=request,
|
|
324
|
+
)
|
|
325
|
+
except errors.UnexpectedStatus as err:
|
|
326
|
+
err_resp = parse_unexpected_error(err)
|
|
327
|
+
if err.status_code == 420:
|
|
328
|
+
raise WorkflowRPCExecutionError(err.status_code, err_resp)
|
|
329
|
+
if err.status_code == 450:
|
|
330
|
+
raise WorkflowRPCAcquiringLockFailure(err.status_code, err_resp)
|
|
331
|
+
else:
|
|
332
|
+
raise RuntimeError(f"unknown error code {err.status_code}")
|
|
333
|
+
|
|
334
|
+
if response.status_code != http.HTTPStatus.OK:
|
|
335
|
+
assert isinstance(response.parsed, ErrorResponse)
|
|
336
|
+
raise process_http_error(response.status_code, response.parsed)
|
|
337
|
+
assert isinstance(response.parsed, WorkflowRpcResponse)
|
|
338
|
+
return self.client_options.object_encoder.decode(
|
|
339
|
+
response.parsed.output,
|
|
340
|
+
return_type_hint,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def signal_workflow(
|
|
344
|
+
self,
|
|
345
|
+
workflow_id: str,
|
|
346
|
+
workflow_run_id: str,
|
|
347
|
+
signal_channel_name: str,
|
|
348
|
+
signal_value: Optional[Any],
|
|
349
|
+
):
|
|
350
|
+
request = WorkflowSignalRequest(
|
|
351
|
+
workflow_id=workflow_id,
|
|
352
|
+
workflow_run_id=workflow_run_id,
|
|
353
|
+
signal_channel_name=signal_channel_name,
|
|
354
|
+
signal_value=self.client_options.object_encoder.encode(signal_value),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
response = post_api_v1_workflow_signal.sync_detailed(
|
|
358
|
+
client=self.api_client,
|
|
359
|
+
body=request,
|
|
360
|
+
)
|
|
361
|
+
handler_error_and_return(response)
|
|
362
|
+
|
|
363
|
+
def reset_workflow(
|
|
364
|
+
self,
|
|
365
|
+
workflow_id: str,
|
|
366
|
+
workflow_run_id: str,
|
|
367
|
+
reset_workflow_type_and_options: ResetWorkflowTypeAndOptions,
|
|
368
|
+
) -> None:
|
|
369
|
+
request = WorkflowResetRequest(
|
|
370
|
+
workflow_id=workflow_id,
|
|
371
|
+
workflow_run_id=workflow_run_id,
|
|
372
|
+
reset_type=reset_workflow_type_and_options.reset_type,
|
|
373
|
+
reason=reset_workflow_type_and_options.reason,
|
|
374
|
+
)
|
|
375
|
+
if reset_workflow_type_and_options.history_event_id:
|
|
376
|
+
request.history_event_id = reset_workflow_type_and_options.history_event_id
|
|
377
|
+
if reset_workflow_type_and_options.history_event_time:
|
|
378
|
+
request.history_event_time = (
|
|
379
|
+
reset_workflow_type_and_options.history_event_time
|
|
380
|
+
)
|
|
381
|
+
if reset_workflow_type_and_options.state_id:
|
|
382
|
+
request.state_id = reset_workflow_type_and_options.state_id
|
|
383
|
+
if reset_workflow_type_and_options.state_execution_id:
|
|
384
|
+
request.state_execution_id = (
|
|
385
|
+
reset_workflow_type_and_options.state_execution_id
|
|
386
|
+
)
|
|
387
|
+
if reset_workflow_type_and_options.skip_signal_reapply:
|
|
388
|
+
request.skip_signal_reapply = (
|
|
389
|
+
reset_workflow_type_and_options.skip_signal_reapply
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
response = post_api_v1_workflow_reset.sync_detailed(
|
|
393
|
+
client=self.api_client,
|
|
394
|
+
body=request,
|
|
395
|
+
)
|
|
396
|
+
return handler_error_and_return(response)
|
|
397
|
+
|
|
398
|
+
def skip_timer_by_command_id(
|
|
399
|
+
self,
|
|
400
|
+
workflow_id: str,
|
|
401
|
+
workflow_run_id: str,
|
|
402
|
+
workflow_state_id: str,
|
|
403
|
+
timer_command_id: str,
|
|
404
|
+
state_execution_number: int,
|
|
405
|
+
):
|
|
406
|
+
request = WorkflowSkipTimerRequest(
|
|
407
|
+
workflow_id=workflow_id,
|
|
408
|
+
workflow_run_id=workflow_run_id,
|
|
409
|
+
workflow_state_execution_id=f"{workflow_state_id}-{state_execution_number}",
|
|
410
|
+
timer_command_id=timer_command_id,
|
|
411
|
+
)
|
|
412
|
+
response = post_api_v1_workflow_timer_skip.sync_detailed(
|
|
413
|
+
client=self.api_client,
|
|
414
|
+
body=request,
|
|
415
|
+
)
|
|
416
|
+
handler_error_and_return(response)
|
|
417
|
+
|
|
418
|
+
def skip_timer_at_command_index(
|
|
419
|
+
self,
|
|
420
|
+
workflow_id: str,
|
|
421
|
+
workflow_run_id: str,
|
|
422
|
+
workflow_state_id: str,
|
|
423
|
+
state_execution_number: int,
|
|
424
|
+
timer_command_index: int,
|
|
425
|
+
):
|
|
426
|
+
request = WorkflowSkipTimerRequest(
|
|
427
|
+
workflow_id=workflow_id,
|
|
428
|
+
workflow_run_id=workflow_run_id,
|
|
429
|
+
workflow_state_execution_id=f"{workflow_state_id}-{state_execution_number}",
|
|
430
|
+
timer_command_index=timer_command_index,
|
|
431
|
+
)
|
|
432
|
+
response = post_api_v1_workflow_timer_skip.sync_detailed(
|
|
433
|
+
client=self.api_client,
|
|
434
|
+
body=request,
|
|
435
|
+
)
|
|
436
|
+
handler_error_and_return(response)
|
|
437
|
+
|
|
438
|
+
def stop_workflow(
|
|
439
|
+
self,
|
|
440
|
+
workflow_id: str,
|
|
441
|
+
workflow_run_id: str,
|
|
442
|
+
options: Optional[StopWorkflowOptions] = None,
|
|
443
|
+
):
|
|
444
|
+
request = WorkflowStopRequest(
|
|
445
|
+
workflow_id=workflow_id,
|
|
446
|
+
workflow_run_id=workflow_run_id,
|
|
447
|
+
)
|
|
448
|
+
if options:
|
|
449
|
+
request.stop_type = options.workflow_stop_type
|
|
450
|
+
request.reason = options.reason
|
|
451
|
+
response = post_api_v1_workflow_stop.sync_detailed(
|
|
452
|
+
client=self.api_client,
|
|
453
|
+
body=request,
|
|
454
|
+
)
|
|
455
|
+
return handler_error_and_return(response)
|
|
456
|
+
|
|
457
|
+
def get_workflow(
|
|
458
|
+
self,
|
|
459
|
+
workflow_id: str,
|
|
460
|
+
workflow_run_id: str,
|
|
461
|
+
) -> WorkflowGetResponse:
|
|
462
|
+
request = WorkflowGetRequest(
|
|
463
|
+
workflow_id=workflow_id,
|
|
464
|
+
workflow_run_id=workflow_run_id,
|
|
465
|
+
)
|
|
466
|
+
response = post_api_v1_workflow_get.sync_detailed(
|
|
467
|
+
client=self.api_client,
|
|
468
|
+
body=request,
|
|
469
|
+
)
|
|
470
|
+
return handler_error_and_return(response)
|
|
471
|
+
|
|
472
|
+
def get_workflow_data_attributes(
|
|
473
|
+
self,
|
|
474
|
+
workflow_id: str,
|
|
475
|
+
workflow_run_id: str,
|
|
476
|
+
attribute_keys: Optional[List[str]] = None,
|
|
477
|
+
use_memo_for_data_attributes: Optional[bool] = None,
|
|
478
|
+
) -> WorkflowGetDataObjectsResponse:
|
|
479
|
+
request = WorkflowGetDataObjectsRequest(
|
|
480
|
+
workflow_id=workflow_id,
|
|
481
|
+
workflow_run_id=workflow_run_id,
|
|
482
|
+
)
|
|
483
|
+
if attribute_keys:
|
|
484
|
+
request.keys = attribute_keys
|
|
485
|
+
if use_memo_for_data_attributes is not None:
|
|
486
|
+
request.use_memo_for_data_attributes = use_memo_for_data_attributes
|
|
487
|
+
response = post_api_v1_workflow_dataobjects_get.sync_detailed(
|
|
488
|
+
client=self.api_client,
|
|
489
|
+
body=request,
|
|
490
|
+
)
|
|
491
|
+
return handler_error_and_return(response)
|
|
492
|
+
|
|
493
|
+
def set_workflow_data_attributes(
|
|
494
|
+
self, workflow_id: str, workflow_run_id: str, attrs: dict[str, Any]
|
|
495
|
+
):
|
|
496
|
+
objects = [
|
|
497
|
+
KeyValue(key, self.client_options.object_encoder.encode(value))
|
|
498
|
+
for key, value in attrs.items()
|
|
499
|
+
]
|
|
500
|
+
|
|
501
|
+
request = WorkflowSetDataObjectsRequest(
|
|
502
|
+
workflow_id,
|
|
503
|
+
workflow_run_id,
|
|
504
|
+
objects,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
response = post_api_v1_workflow_dataobjects_set.sync_detailed(
|
|
508
|
+
client=self.api_client, body=request
|
|
509
|
+
)
|
|
510
|
+
return handler_error_and_return(response)
|
|
511
|
+
|
|
512
|
+
def search_workflow(
|
|
513
|
+
self,
|
|
514
|
+
request: WorkflowSearchRequest,
|
|
515
|
+
) -> WorkflowSearchResponse:
|
|
516
|
+
response = post_api_v1_workflow_search.sync_detailed(
|
|
517
|
+
client=self.api_client,
|
|
518
|
+
body=request,
|
|
519
|
+
)
|
|
520
|
+
return handler_error_and_return(response)
|
|
521
|
+
|
|
522
|
+
def get_workflow_search_attributes(
|
|
523
|
+
self,
|
|
524
|
+
workflow_id: str,
|
|
525
|
+
workflow_run_id: str,
|
|
526
|
+
attribute_keys: Optional[List[SearchAttributeKeyAndType]] = None,
|
|
527
|
+
) -> WorkflowGetSearchAttributesResponse:
|
|
528
|
+
request = WorkflowGetSearchAttributesRequest(
|
|
529
|
+
workflow_id=workflow_id,
|
|
530
|
+
workflow_run_id=workflow_run_id,
|
|
531
|
+
)
|
|
532
|
+
if attribute_keys:
|
|
533
|
+
request.keys = attribute_keys
|
|
534
|
+
response = post_api_v1_workflow_searchattributes_get.sync_detailed(
|
|
535
|
+
client=self.api_client,
|
|
536
|
+
body=request,
|
|
537
|
+
)
|
|
538
|
+
return handler_error_and_return(response)
|
|
539
|
+
|
|
540
|
+
def set_workflow_search_attributes(
|
|
541
|
+
self,
|
|
542
|
+
workflow_id: str,
|
|
543
|
+
workflow_run_id: str,
|
|
544
|
+
search_attributes: list[SearchAttribute],
|
|
545
|
+
):
|
|
546
|
+
request = WorkflowSetSearchAttributesRequest(
|
|
547
|
+
workflow_id=workflow_id,
|
|
548
|
+
workflow_run_id=workflow_run_id,
|
|
549
|
+
search_attributes=search_attributes,
|
|
550
|
+
)
|
|
551
|
+
response = post_api_v1_workflow_searchattributes_set.sync_detailed(
|
|
552
|
+
client=self.api_client,
|
|
553
|
+
body=request,
|
|
554
|
+
)
|
|
555
|
+
return handler_error_and_return(response)
|
|
556
|
+
|
|
557
|
+
def wait_for_state_execution_completion_with_state_execution_id(
|
|
558
|
+
self, workflow_id: str, state_execution_id: str
|
|
559
|
+
):
|
|
560
|
+
request = WorkflowWaitForStateCompletionRequest(
|
|
561
|
+
workflow_id=workflow_id,
|
|
562
|
+
state_execution_id=state_execution_id,
|
|
563
|
+
wait_time_seconds=self.client_options.long_poll_api_max_wait_time_seconds,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
response = post_api_v_1_workflow_wait_for_state_completion.sync_detailed(
|
|
567
|
+
client=self.api_client,
|
|
568
|
+
body=request,
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
return handler_error_and_return(response)
|
|
572
|
+
|
|
573
|
+
def wait_for_state_execution_completion_with_wait_for_key(
|
|
574
|
+
self, workflow_id: str, state_id: str, wait_for_key: str
|
|
575
|
+
):
|
|
576
|
+
request = WorkflowWaitForStateCompletionRequest(
|
|
577
|
+
workflow_id=workflow_id,
|
|
578
|
+
state_id=state_id,
|
|
579
|
+
wait_for_key=wait_for_key,
|
|
580
|
+
wait_time_seconds=self.client_options.long_poll_api_max_wait_time_seconds,
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
response = post_api_v_1_workflow_wait_for_state_completion.sync_detailed(
|
|
584
|
+
client=self.api_client,
|
|
585
|
+
body=request,
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
return handler_error_and_return(response)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def handler_error_and_return(response: Response):
|
|
592
|
+
if response.status_code != http.HTTPStatus.OK:
|
|
593
|
+
assert isinstance(response.parsed, ErrorResponse)
|
|
594
|
+
raise process_http_error(response.status_code, response.parsed)
|
|
595
|
+
return response.parsed
|
dex/utils/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
dex/utils/dex_typing.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from typing import Optional, TypeVar, Union
|
|
16
|
+
|
|
17
|
+
from dex.dex_api.types import Unset
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def unset_to_none(input: Union[Unset, T]) -> Optional[T]:
|
|
23
|
+
if isinstance(input, Unset):
|
|
24
|
+
return None
|
|
25
|
+
return input
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def assert_not_unset(input: Union[Unset, T]) -> T:
|
|
29
|
+
if isinstance(input, Unset):
|
|
30
|
+
raise RuntimeError("the value shouldn't be Unset")
|
|
31
|
+
return input
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from dex.dex_api.models import SearchAttributeValueType, SearchAttribute
|
|
16
|
+
from dex.utils.dex_typing import unset_to_none
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_search_attribute_value(
|
|
20
|
+
sa_type: SearchAttributeValueType, attribute: SearchAttribute
|
|
21
|
+
):
|
|
22
|
+
if (
|
|
23
|
+
sa_type == SearchAttributeValueType.KEYWORD
|
|
24
|
+
or sa_type == SearchAttributeValueType.DATETIME
|
|
25
|
+
or sa_type == SearchAttributeValueType.TEXT
|
|
26
|
+
):
|
|
27
|
+
return unset_to_none(attribute.string_value)
|
|
28
|
+
elif sa_type == SearchAttributeValueType.INT:
|
|
29
|
+
return unset_to_none(attribute.integer_value)
|
|
30
|
+
elif sa_type == SearchAttributeValueType.DOUBLE:
|
|
31
|
+
return unset_to_none(attribute.double_value)
|
|
32
|
+
elif sa_type == SearchAttributeValueType.BOOL:
|
|
33
|
+
return unset_to_none(attribute.bool_value)
|
|
34
|
+
elif sa_type == SearchAttributeValueType.KEYWORD_ARRAY:
|
|
35
|
+
return unset_to_none(attribute.string_array_value)
|
|
36
|
+
else:
|
|
37
|
+
raise ValueError(f"not supported search attribute value type, {sa_type}")
|