dex-python-sdk 0.0.2__cp311-abi3-win_amd64.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 +146 -0
- dex/_grpc_errors.py +73 -0
- dex/_invocation_context.py +269 -0
- dex/_native.pyd +0 -0
- dex/_native.pyi +20 -0
- dex/_utils.py +18 -0
- dex/_value_hydrator.py +223 -0
- dex/_value_mapper.py +174 -0
- dex/_worker_dispatcher.py +451 -0
- dex/_worker_service.py +60 -0
- dex/attribute.py +87 -0
- dex/blob_cache.py +73 -0
- dex/channel.py +165 -0
- dex/client.py +715 -0
- dex/client_options.py +19 -0
- dex/codec.py +286 -0
- dex/command_request.py +120 -0
- dex/command_results.py +107 -0
- dex/communication.py +136 -0
- dex/communication_schema.py +54 -0
- dex/condition.py +74 -0
- dex/context.py +86 -0
- dex/data_attributes.py +70 -0
- dex/dexpb/__init__.py +1 -0
- dex/dexpb/dex_pb2.py +381 -0
- dex/dexpb/dex_pb2.pyi +1734 -0
- dex/dexpb/dex_pb2_grpc.py +1298 -0
- dex/errors.py +109 -0
- dex/flow.py +456 -0
- dex/flow_config.py +29 -0
- dex/flow_info.py +51 -0
- dex/flow_options.py +122 -0
- dex/object_encoder.py +799 -0
- dex/persistence.py +89 -0
- dex/persistence_options.py +12 -0
- dex/persistence_schema.py +51 -0
- dex/py.typed +1 -0
- dex/registry.py +204 -0
- dex/reset_workflow_type_and_options.py +67 -0
- dex/rpc.py +93 -0
- dex/runtime_errors.py +81 -0
- dex/search_attributes.py +184 -0
- dex/state_decision.py +153 -0
- dex/state_execution_locals.py +66 -0
- dex/state_movement.py +115 -0
- dex/state_schema.py +48 -0
- dex/step.py +194 -0
- dex/step_execution.py +42 -0
- dex/stop_workflow_options.py +18 -0
- dex/tests/__init__.py +80 -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 +97 -0
- dex/tests/dex-service-env/dynamicconfig/README.md +39 -0
- dex/tests/dex-service-env/dynamicconfig/development-sql.yaml +9 -0
- dex/tests/dex-service-env/dynamicconfig/docker.yaml +2 -0
- dex/tests/test_abnormal_exit_workflow.py +43 -0
- dex/tests/test_basic_workflow.py +70 -0
- dex/tests/test_conditional_complete.py +50 -0
- dex/tests/test_describe_workflow.py +40 -0
- dex/tests/test_empty_data_decodes_properly.py +74 -0
- dex/tests/test_internal_channel.py +28 -0
- dex/tests/test_internal_channel_with_no_prefix_channel.py +41 -0
- dex/tests/test_persistence_data_attributes.py +62 -0
- dex/tests/test_persistence_search_attributes.py +127 -0
- dex/tests/test_persistence_state_execution_locals.py +38 -0
- dex/tests/test_rpc.py +64 -0
- dex/tests/test_rpc_with_memo.py +195 -0
- dex/tests/test_rpc_with_memo_duplicate_java_tests.py +117 -0
- dex/tests/test_signal.py +51 -0
- dex/tests/test_skip_wait_until.py +76 -0
- dex/tests/test_state_failure_recovery.py +28 -0
- dex/tests/test_timer.py +35 -0
- dex/tests/test_wait_for_state_execution_completion.py +53 -0
- dex/tests/test_workflow_errors.py +87 -0
- dex/tests/test_workflow_state_options.py +118 -0
- dex/tests/test_workflow_state_options_override.py +44 -0
- dex/tests/worker_server.py +64 -0
- dex/tests/workflows/abnormal_exit_workflow.py +42 -0
- dex/tests/workflows/basic_workflow.py +62 -0
- dex/tests/workflows/conditional_complete_workflow.py +95 -0
- dex/tests/workflows/describe_workflow.py +46 -0
- dex/tests/workflows/empty_data_workflow.py +45 -0
- dex/tests/workflows/internal_channel_workflow.py +129 -0
- dex/tests/workflows/internal_channel_workflow_with_no_prefix_channel.py +100 -0
- dex/tests/workflows/java_duplicate_rpc_memo_workflow.py +276 -0
- dex/tests/workflows/persistence_data_attributes_workflow.py +98 -0
- dex/tests/workflows/persistence_search_attributes_workflow.py +159 -0
- dex/tests/workflows/persistence_state_execution_local_workflow.py +63 -0
- dex/tests/workflows/recovery_workflow.py +82 -0
- dex/tests/workflows/rpc_memo_workflow.py +231 -0
- dex/tests/workflows/rpc_workflow.py +117 -0
- dex/tests/workflows/state_options_override_workflow.py +93 -0
- dex/tests/workflows/state_options_workflow.py +84 -0
- dex/tests/workflows/timer_workflow.py +46 -0
- dex/tests/workflows/wait_for_state_with_state_execution_id_workflow.py +70 -0
- dex/tests/workflows/wait_for_state_with_wait_for_key_workflow.py +71 -0
- dex/tests/workflows/wait_internal_channel_workflow.py +47 -0
- dex/tests/workflows/wait_signal_workflow.py +147 -0
- dex/timer.py +21 -0
- dex/type_store.py +99 -0
- dex/unregistered_client.py +585 -0
- dex/utils/__init__.py +3 -0
- dex/utils/dex_typing.py +25 -0
- dex/utils/persistence_utils.py +32 -0
- dex/wait.py +49 -0
- dex/worker.py +121 -0
- dex/worker_options.py +22 -0
- dex/worker_service.py +432 -0
- dex/workflow.py +79 -0
- dex/workflow_context.py +44 -0
- dex/workflow_info.py +16 -0
- dex/workflow_options.py +74 -0
- dex/workflow_state.py +123 -0
- dex/workflow_state_options.py +154 -0
- dex_python_sdk-0.0.2.dist-info/METADATA +202 -0
- dex_python_sdk-0.0.2.dist-info/RECORD +121 -0
- dex_python_sdk-0.0.2.dist-info/WHEEL +4 -0
- dex_python_sdk-0.0.2.dist-info/licenses/LEGACY_NOTICES.md +61 -0
- dex_python_sdk-0.0.2.dist-info/licenses/LICENSE +192 -0
- dex_python_sdk-0.0.2.dist-info/sboms/dex-blob-cache-python.cyclonedx.json +2406 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
import inspect
|
|
12
|
+
import time
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
from dex.client import Client
|
|
16
|
+
from dex.dex_api.models.workflow_stop_type import WorkflowStopType
|
|
17
|
+
from dex.stop_workflow_options import StopWorkflowOptions
|
|
18
|
+
from dex.tests import registry
|
|
19
|
+
from dex.tests.workflows.rpc_memo_workflow import (
|
|
20
|
+
RpcMemoWorkflow,
|
|
21
|
+
TEST_DATA_OBJECT_KEY,
|
|
22
|
+
TEST_SEARCH_ATTRIBUTE_INT,
|
|
23
|
+
TEST_SEARCH_ATTRIBUTE_KEY,
|
|
24
|
+
RPC_INPUT,
|
|
25
|
+
RPC_OUTPUT,
|
|
26
|
+
TEST_STR,
|
|
27
|
+
TEST_DELAY,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
class TestRpcWithMemo(unittest.TestCase):
|
|
31
|
+
@classmethod
|
|
32
|
+
def setUpClass(cls):
|
|
33
|
+
cls.client = Client(registry)
|
|
34
|
+
|
|
35
|
+
def test_rpc_memo_workflow_func1(self):
|
|
36
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
37
|
+
run_id = self.client.start_workflow(RpcMemoWorkflow, wf_id, 30, 999)
|
|
38
|
+
|
|
39
|
+
self.client.invoke_rpc(
|
|
40
|
+
wf_id, RpcMemoWorkflow.test_rpc_set_data_attribute, TEST_STR
|
|
41
|
+
)
|
|
42
|
+
value = self.client.invoke_rpc(
|
|
43
|
+
wf_id, RpcMemoWorkflow.test_rpc_get_data_attribute_strong_consistency
|
|
44
|
+
)
|
|
45
|
+
assert value == TEST_STR
|
|
46
|
+
time.sleep(TEST_DELAY)
|
|
47
|
+
value = self.client.invoke_rpc(
|
|
48
|
+
wf_id, RpcMemoWorkflow.test_rpc_get_data_attribute, None, str
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
self.client.invoke_rpc(wf_id, RpcMemoWorkflow.test_rpc_set_data_attribute, None)
|
|
52
|
+
value = self.client.invoke_rpc(
|
|
53
|
+
wf_id, RpcMemoWorkflow.test_rpc_get_data_attribute_strong_consistency
|
|
54
|
+
)
|
|
55
|
+
assert value is None
|
|
56
|
+
time.sleep(TEST_DELAY)
|
|
57
|
+
value = self.client.invoke_rpc(
|
|
58
|
+
wf_id, RpcMemoWorkflow.test_rpc_get_data_attribute
|
|
59
|
+
)
|
|
60
|
+
assert value is None
|
|
61
|
+
|
|
62
|
+
self.client.invoke_rpc(wf_id, RpcMemoWorkflow.test_rpc_set_keyword, TEST_STR)
|
|
63
|
+
value = self.client.invoke_rpc(
|
|
64
|
+
wf_id, RpcMemoWorkflow.test_rpc_get_keyword_strong_consistency
|
|
65
|
+
)
|
|
66
|
+
assert value == TEST_STR
|
|
67
|
+
time.sleep(TEST_DELAY)
|
|
68
|
+
value = self.client.invoke_rpc(wf_id, RpcMemoWorkflow.test_rpc_get_keyword)
|
|
69
|
+
assert value == TEST_STR
|
|
70
|
+
|
|
71
|
+
self.client.invoke_rpc(wf_id, RpcMemoWorkflow.test_rpc_set_keyword, None)
|
|
72
|
+
value = self.client.invoke_rpc(
|
|
73
|
+
wf_id, RpcMemoWorkflow.test_rpc_get_keyword_strong_consistency
|
|
74
|
+
)
|
|
75
|
+
assert value is None
|
|
76
|
+
time.sleep(TEST_DELAY)
|
|
77
|
+
value = self.client.invoke_rpc(wf_id, RpcMemoWorkflow.test_rpc_get_keyword)
|
|
78
|
+
assert value is None
|
|
79
|
+
|
|
80
|
+
rpc_output = self.client.invoke_rpc(
|
|
81
|
+
wf_id, RpcMemoWorkflow.test_rpc_func1, RPC_INPUT
|
|
82
|
+
)
|
|
83
|
+
assert RPC_OUTPUT == rpc_output
|
|
84
|
+
|
|
85
|
+
self.client.wait_for_workflow_completion(wf_id)
|
|
86
|
+
|
|
87
|
+
data_attributes = self.client.get_workflow_data_attributes(
|
|
88
|
+
RpcMemoWorkflow, wf_id, run_id, [TEST_DATA_OBJECT_KEY]
|
|
89
|
+
)
|
|
90
|
+
assert TEST_DATA_OBJECT_KEY in data_attributes
|
|
91
|
+
assert data_attributes[TEST_DATA_OBJECT_KEY] == RPC_INPUT
|
|
92
|
+
|
|
93
|
+
search_attributes = self.client.get_workflow_search_attributes(
|
|
94
|
+
RpcMemoWorkflow,
|
|
95
|
+
wf_id,
|
|
96
|
+
[TEST_SEARCH_ATTRIBUTE_KEY, TEST_SEARCH_ATTRIBUTE_INT],
|
|
97
|
+
run_id,
|
|
98
|
+
)
|
|
99
|
+
assert TEST_SEARCH_ATTRIBUTE_INT in search_attributes
|
|
100
|
+
assert TEST_SEARCH_ATTRIBUTE_KEY in search_attributes
|
|
101
|
+
assert search_attributes[TEST_SEARCH_ATTRIBUTE_INT] == RPC_OUTPUT
|
|
102
|
+
assert search_attributes[TEST_SEARCH_ATTRIBUTE_KEY] == RPC_INPUT
|
|
103
|
+
|
|
104
|
+
def test_rpc_memo_workflow_func0(self):
|
|
105
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
106
|
+
run_id = self.client.start_workflow(RpcMemoWorkflow, wf_id, 30, 999)
|
|
107
|
+
|
|
108
|
+
rpc_output = self.client.invoke_rpc(
|
|
109
|
+
wf_id, RpcMemoWorkflow.test_rpc_func0, TEST_STR
|
|
110
|
+
)
|
|
111
|
+
assert RPC_OUTPUT == rpc_output
|
|
112
|
+
|
|
113
|
+
self.client.wait_for_workflow_completion(wf_id)
|
|
114
|
+
|
|
115
|
+
data_attributes = self.client.get_workflow_data_attributes(
|
|
116
|
+
RpcMemoWorkflow, wf_id, run_id, [TEST_DATA_OBJECT_KEY]
|
|
117
|
+
)
|
|
118
|
+
assert TEST_DATA_OBJECT_KEY in data_attributes
|
|
119
|
+
assert data_attributes[TEST_DATA_OBJECT_KEY] == TEST_STR
|
|
120
|
+
|
|
121
|
+
search_attributes = self.client.get_workflow_search_attributes(
|
|
122
|
+
RpcMemoWorkflow,
|
|
123
|
+
wf_id,
|
|
124
|
+
[TEST_SEARCH_ATTRIBUTE_KEY, TEST_SEARCH_ATTRIBUTE_INT],
|
|
125
|
+
run_id,
|
|
126
|
+
)
|
|
127
|
+
assert TEST_SEARCH_ATTRIBUTE_INT in search_attributes
|
|
128
|
+
assert TEST_SEARCH_ATTRIBUTE_KEY in search_attributes
|
|
129
|
+
assert search_attributes[TEST_SEARCH_ATTRIBUTE_INT] == RPC_OUTPUT
|
|
130
|
+
assert search_attributes[TEST_SEARCH_ATTRIBUTE_KEY] == TEST_STR
|
|
131
|
+
|
|
132
|
+
def test_rpc_memo_workflow_proc1(self):
|
|
133
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
134
|
+
run_id = self.client.start_workflow(RpcMemoWorkflow, wf_id, 30, 999)
|
|
135
|
+
|
|
136
|
+
self.client.invoke_rpc(wf_id, RpcMemoWorkflow.test_rpc_proc1, RPC_INPUT)
|
|
137
|
+
self.client.wait_for_workflow_completion(wf_id)
|
|
138
|
+
|
|
139
|
+
data_attributes = self.client.get_workflow_data_attributes(
|
|
140
|
+
RpcMemoWorkflow, wf_id, run_id, [TEST_DATA_OBJECT_KEY]
|
|
141
|
+
)
|
|
142
|
+
assert TEST_DATA_OBJECT_KEY in data_attributes
|
|
143
|
+
assert data_attributes[TEST_DATA_OBJECT_KEY] == RPC_INPUT
|
|
144
|
+
|
|
145
|
+
search_attributes = self.client.get_workflow_search_attributes(
|
|
146
|
+
RpcMemoWorkflow,
|
|
147
|
+
wf_id,
|
|
148
|
+
[TEST_SEARCH_ATTRIBUTE_KEY, TEST_SEARCH_ATTRIBUTE_INT],
|
|
149
|
+
run_id,
|
|
150
|
+
)
|
|
151
|
+
assert TEST_SEARCH_ATTRIBUTE_INT in search_attributes
|
|
152
|
+
assert TEST_SEARCH_ATTRIBUTE_KEY in search_attributes
|
|
153
|
+
assert search_attributes[TEST_SEARCH_ATTRIBUTE_INT] == RPC_OUTPUT
|
|
154
|
+
assert search_attributes[TEST_SEARCH_ATTRIBUTE_KEY] == RPC_INPUT
|
|
155
|
+
|
|
156
|
+
def test_rpc_memo_workflow_proc0(self):
|
|
157
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
158
|
+
run_id = self.client.start_workflow(RpcMemoWorkflow, wf_id, 30, 999)
|
|
159
|
+
|
|
160
|
+
self.client.invoke_rpc(wf_id, RpcMemoWorkflow.test_rpc_proc0)
|
|
161
|
+
self.client.wait_for_workflow_completion(wf_id)
|
|
162
|
+
|
|
163
|
+
data_attributes = self.client.get_workflow_data_attributes(
|
|
164
|
+
RpcMemoWorkflow, wf_id, run_id, [TEST_DATA_OBJECT_KEY]
|
|
165
|
+
)
|
|
166
|
+
assert TEST_DATA_OBJECT_KEY in data_attributes
|
|
167
|
+
assert data_attributes[TEST_DATA_OBJECT_KEY] == TEST_STR
|
|
168
|
+
|
|
169
|
+
search_attributes = self.client.get_workflow_search_attributes(
|
|
170
|
+
RpcMemoWorkflow,
|
|
171
|
+
wf_id,
|
|
172
|
+
[TEST_SEARCH_ATTRIBUTE_KEY, TEST_SEARCH_ATTRIBUTE_INT],
|
|
173
|
+
run_id,
|
|
174
|
+
)
|
|
175
|
+
assert TEST_SEARCH_ATTRIBUTE_INT in search_attributes
|
|
176
|
+
assert TEST_SEARCH_ATTRIBUTE_KEY in search_attributes
|
|
177
|
+
assert search_attributes[TEST_SEARCH_ATTRIBUTE_INT] == RPC_OUTPUT
|
|
178
|
+
assert search_attributes[TEST_SEARCH_ATTRIBUTE_KEY] == TEST_STR
|
|
179
|
+
|
|
180
|
+
def test_rpc_memo_workflow_func1_readonly(self):
|
|
181
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
182
|
+
self.client.start_workflow(RpcMemoWorkflow, wf_id, 30, 999)
|
|
183
|
+
|
|
184
|
+
rpc_output = self.client.invoke_rpc(
|
|
185
|
+
wf_id, RpcMemoWorkflow.test_rpc_func1_readonly, RPC_INPUT
|
|
186
|
+
)
|
|
187
|
+
assert RPC_OUTPUT == rpc_output
|
|
188
|
+
|
|
189
|
+
self.client.stop_workflow(
|
|
190
|
+
wf_id,
|
|
191
|
+
StopWorkflowOptions(
|
|
192
|
+
workflow_stop_type=WorkflowStopType.FAIL,
|
|
193
|
+
reason=TEST_STR,
|
|
194
|
+
),
|
|
195
|
+
)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
import inspect
|
|
12
|
+
import time
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
from dex.client import Client
|
|
16
|
+
from dex.tests.worker_server import registry
|
|
17
|
+
from dex.tests.workflows.java_duplicate_rpc_memo_workflow import (
|
|
18
|
+
JavaDuplicateRpcMemoWorkflow,
|
|
19
|
+
RpcMemoWorkflowState2,
|
|
20
|
+
TEST_DATA_OBJECT_KEY,
|
|
21
|
+
TEST_SEARCH_ATTRIBUTE_INT,
|
|
22
|
+
TEST_SEARCH_ATTRIBUTE_KEY,
|
|
23
|
+
RPC_INPUT,
|
|
24
|
+
RPC_OUTPUT,
|
|
25
|
+
TEST_STR,
|
|
26
|
+
TEST_DELAY,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
class TestRpcWithMemo(unittest.TestCase):
|
|
30
|
+
@classmethod
|
|
31
|
+
def setUpClass(cls):
|
|
32
|
+
cls.client = Client(registry)
|
|
33
|
+
|
|
34
|
+
def test_rpc_memo_workflow_func1(self):
|
|
35
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
36
|
+
run_id = self.client.start_workflow(
|
|
37
|
+
JavaDuplicateRpcMemoWorkflow, wf_id, 30, 999
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
self.client.invoke_rpc(
|
|
41
|
+
wf_id, JavaDuplicateRpcMemoWorkflow.test_rpc_set_data_attribute, TEST_STR
|
|
42
|
+
)
|
|
43
|
+
value = self.client.invoke_rpc(
|
|
44
|
+
wf_id,
|
|
45
|
+
JavaDuplicateRpcMemoWorkflow.test_rpc_get_data_attribute_strong_consistency,
|
|
46
|
+
)
|
|
47
|
+
assert value == TEST_STR
|
|
48
|
+
time.sleep(TEST_DELAY)
|
|
49
|
+
value = self.client.invoke_rpc(
|
|
50
|
+
wf_id, JavaDuplicateRpcMemoWorkflow.test_rpc_get_data_attribute, None, str
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
self.client.invoke_rpc(
|
|
54
|
+
wf_id, JavaDuplicateRpcMemoWorkflow.test_rpc_set_data_attribute, None
|
|
55
|
+
)
|
|
56
|
+
value = self.client.invoke_rpc(
|
|
57
|
+
wf_id,
|
|
58
|
+
JavaDuplicateRpcMemoWorkflow.test_rpc_get_data_attribute_strong_consistency,
|
|
59
|
+
)
|
|
60
|
+
assert value is None
|
|
61
|
+
time.sleep(TEST_DELAY)
|
|
62
|
+
value = self.client.invoke_rpc(
|
|
63
|
+
wf_id, JavaDuplicateRpcMemoWorkflow.test_rpc_get_data_attribute
|
|
64
|
+
)
|
|
65
|
+
assert value is None
|
|
66
|
+
|
|
67
|
+
self.client.invoke_rpc(
|
|
68
|
+
wf_id, JavaDuplicateRpcMemoWorkflow.test_rpc_set_keyword, TEST_STR
|
|
69
|
+
)
|
|
70
|
+
value = self.client.invoke_rpc(
|
|
71
|
+
wf_id, JavaDuplicateRpcMemoWorkflow.test_rpc_get_keyword_strong_consistency
|
|
72
|
+
)
|
|
73
|
+
assert value == TEST_STR
|
|
74
|
+
time.sleep(TEST_DELAY)
|
|
75
|
+
value = self.client.invoke_rpc(
|
|
76
|
+
wf_id, JavaDuplicateRpcMemoWorkflow.test_rpc_get_keyword
|
|
77
|
+
)
|
|
78
|
+
assert value == TEST_STR
|
|
79
|
+
|
|
80
|
+
self.client.invoke_rpc(
|
|
81
|
+
wf_id, JavaDuplicateRpcMemoWorkflow.test_rpc_set_keyword, None
|
|
82
|
+
)
|
|
83
|
+
value = self.client.invoke_rpc(
|
|
84
|
+
wf_id, JavaDuplicateRpcMemoWorkflow.test_rpc_get_keyword_strong_consistency
|
|
85
|
+
)
|
|
86
|
+
assert value is None
|
|
87
|
+
time.sleep(TEST_DELAY)
|
|
88
|
+
value = self.client.invoke_rpc(
|
|
89
|
+
wf_id, JavaDuplicateRpcMemoWorkflow.test_rpc_get_keyword
|
|
90
|
+
)
|
|
91
|
+
assert value is None
|
|
92
|
+
|
|
93
|
+
rpc_output = self.client.invoke_rpc(
|
|
94
|
+
wf_id, JavaDuplicateRpcMemoWorkflow.test_rpc_func1, RPC_INPUT
|
|
95
|
+
)
|
|
96
|
+
assert RPC_OUTPUT == rpc_output
|
|
97
|
+
|
|
98
|
+
output = self.client.wait_for_workflow_completion(wf_id, int)
|
|
99
|
+
RpcMemoWorkflowState2.reset_counter()
|
|
100
|
+
assert 2 == output
|
|
101
|
+
|
|
102
|
+
data_attributes = self.client.get_workflow_data_attributes(
|
|
103
|
+
JavaDuplicateRpcMemoWorkflow, wf_id, run_id, [TEST_DATA_OBJECT_KEY]
|
|
104
|
+
)
|
|
105
|
+
assert TEST_DATA_OBJECT_KEY in data_attributes
|
|
106
|
+
assert data_attributes[TEST_DATA_OBJECT_KEY] == RPC_INPUT
|
|
107
|
+
|
|
108
|
+
search_attributes = self.client.get_workflow_search_attributes(
|
|
109
|
+
JavaDuplicateRpcMemoWorkflow,
|
|
110
|
+
wf_id,
|
|
111
|
+
[TEST_SEARCH_ATTRIBUTE_KEY, TEST_SEARCH_ATTRIBUTE_INT],
|
|
112
|
+
run_id,
|
|
113
|
+
)
|
|
114
|
+
assert TEST_SEARCH_ATTRIBUTE_INT in search_attributes
|
|
115
|
+
assert TEST_SEARCH_ATTRIBUTE_KEY in search_attributes
|
|
116
|
+
assert search_attributes[TEST_SEARCH_ATTRIBUTE_INT] == RPC_OUTPUT
|
|
117
|
+
assert search_attributes[TEST_SEARCH_ATTRIBUTE_KEY] == RPC_INPUT
|
dex/tests/test_signal.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
import inspect
|
|
12
|
+
import time
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
from dex.client import Client
|
|
16
|
+
from dex.tests.worker_server import registry
|
|
17
|
+
from dex.tests.workflows.wait_signal_workflow import (
|
|
18
|
+
WaitSignalWorkflow,
|
|
19
|
+
test_channel_int,
|
|
20
|
+
test_channel_str,
|
|
21
|
+
test_channel_none,
|
|
22
|
+
test_channel1,
|
|
23
|
+
test_idle_channel_none,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
class TestSignal(unittest.TestCase):
|
|
27
|
+
@classmethod
|
|
28
|
+
def setUpClass(cls):
|
|
29
|
+
cls.client = Client(registry)
|
|
30
|
+
|
|
31
|
+
def test_signal(self):
|
|
32
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
33
|
+
self.client.start_workflow(WaitSignalWorkflow, wf_id, 10)
|
|
34
|
+
self.client.signal_workflow(wf_id, test_channel_int, 123)
|
|
35
|
+
self.client.signal_workflow(wf_id, test_channel_str, "abc")
|
|
36
|
+
self.client.signal_workflow(wf_id, test_channel_none)
|
|
37
|
+
|
|
38
|
+
self.client.signal_workflow(wf_id, test_channel1)
|
|
39
|
+
res = self.client.wait_for_workflow_completion(wf_id, str)
|
|
40
|
+
assert res == "abc"
|
|
41
|
+
|
|
42
|
+
def test_signal_channel_size(self):
|
|
43
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
44
|
+
self.client.start_workflow(WaitSignalWorkflow, wf_id, 1)
|
|
45
|
+
self.client.signal_workflow(wf_id, test_idle_channel_none)
|
|
46
|
+
self.client.signal_workflow(wf_id, test_idle_channel_none)
|
|
47
|
+
self.client.signal_workflow(wf_id, test_idle_channel_none)
|
|
48
|
+
res = self.client.invoke_rpc(
|
|
49
|
+
wf_id, WaitSignalWorkflow.get_idle_signal_channel_size
|
|
50
|
+
)
|
|
51
|
+
assert res == 3
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
import unittest
|
|
12
|
+
|
|
13
|
+
from dex.command_request import CommandRequest
|
|
14
|
+
from dex.command_results import CommandResults
|
|
15
|
+
from dex.communication import Communication
|
|
16
|
+
from dex.persistence import Persistence
|
|
17
|
+
from dex.state_decision import StateDecision
|
|
18
|
+
from dex.workflow_context import WorkflowContext
|
|
19
|
+
from dex.workflow_state import T, WorkflowState, should_skip_wait_until
|
|
20
|
+
|
|
21
|
+
class DirectStateSkip(WorkflowState[None]):
|
|
22
|
+
def execute(
|
|
23
|
+
self,
|
|
24
|
+
ctx: WorkflowContext,
|
|
25
|
+
input: T,
|
|
26
|
+
command_results: CommandResults,
|
|
27
|
+
persistence: Persistence,
|
|
28
|
+
communication: Communication,
|
|
29
|
+
) -> StateDecision:
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
class DirectStateNotSkip(WorkflowState[int]):
|
|
33
|
+
def wait_until(
|
|
34
|
+
self,
|
|
35
|
+
ctx: WorkflowContext,
|
|
36
|
+
input: int,
|
|
37
|
+
persistence: Persistence,
|
|
38
|
+
communication: Communication,
|
|
39
|
+
) -> CommandRequest:
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
def execute(
|
|
43
|
+
self,
|
|
44
|
+
ctx: WorkflowContext,
|
|
45
|
+
input: int,
|
|
46
|
+
command_results: CommandResults,
|
|
47
|
+
persistence: Persistence,
|
|
48
|
+
communication: Communication,
|
|
49
|
+
) -> StateDecision:
|
|
50
|
+
raise NotImplementedError
|
|
51
|
+
|
|
52
|
+
class IndirectStateSkip(DirectStateSkip):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
class IndirectStateNotSkip(DirectStateSkip):
|
|
56
|
+
def wait_until(
|
|
57
|
+
self,
|
|
58
|
+
ctx: WorkflowContext,
|
|
59
|
+
input: T,
|
|
60
|
+
persistence: Persistence,
|
|
61
|
+
communication: Communication,
|
|
62
|
+
) -> CommandRequest:
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
class TestSkipWaitUntil(unittest.TestCase):
|
|
66
|
+
def test_should_skip_wait_until(self) -> None:
|
|
67
|
+
direct_skip = DirectStateSkip()
|
|
68
|
+
direct_not_skip = DirectStateNotSkip()
|
|
69
|
+
indirect_skip = IndirectStateSkip()
|
|
70
|
+
indirect_not_skip = IndirectStateNotSkip()
|
|
71
|
+
|
|
72
|
+
assert should_skip_wait_until(direct_skip)
|
|
73
|
+
assert should_skip_wait_until(indirect_skip)
|
|
74
|
+
|
|
75
|
+
assert not should_skip_wait_until(direct_not_skip)
|
|
76
|
+
assert not should_skip_wait_until(indirect_not_skip)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
import inspect
|
|
12
|
+
import time
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
from dex.client import Client
|
|
16
|
+
from dex.tests.worker_server import registry
|
|
17
|
+
from dex.tests.workflows.recovery_workflow import RecoveryWorkflow
|
|
18
|
+
|
|
19
|
+
class Test(unittest.TestCase):
|
|
20
|
+
@classmethod
|
|
21
|
+
def setUpClass(cls):
|
|
22
|
+
cls.client = Client(registry)
|
|
23
|
+
|
|
24
|
+
def test_workflow_recovery(self):
|
|
25
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
26
|
+
self.client.start_workflow(RecoveryWorkflow, wf_id, 10)
|
|
27
|
+
result = self.client.wait_for_workflow_completion(wf_id, str)
|
|
28
|
+
assert result == "done"
|
dex/tests/test_timer.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
import inspect
|
|
12
|
+
import time
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
from dex.client import Client
|
|
16
|
+
from dex.tests.worker_server import registry
|
|
17
|
+
from dex.tests.workflows.timer_workflow import TimerWorkflow, WaitState
|
|
18
|
+
|
|
19
|
+
class TestTimer(unittest.TestCase):
|
|
20
|
+
@classmethod
|
|
21
|
+
def setUpClass(cls):
|
|
22
|
+
cls.client = Client(registry)
|
|
23
|
+
|
|
24
|
+
def test_timer(self):
|
|
25
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
26
|
+
|
|
27
|
+
self.client.start_workflow(TimerWorkflow, wf_id, 100, 5)
|
|
28
|
+
time.sleep(1)
|
|
29
|
+
self.client.skip_timer_at_command_index(wf_id, WaitState)
|
|
30
|
+
start_ms = time.time_ns() / 1000000
|
|
31
|
+
self.client.wait_for_workflow_completion(wf_id, None)
|
|
32
|
+
elapsed_ms = time.time_ns() / 1000000 - start_ms
|
|
33
|
+
assert (
|
|
34
|
+
3000 <= elapsed_ms <= 6000
|
|
35
|
+
), f"expected 5000 ms timer, actual is {elapsed_ms}"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
import inspect
|
|
12
|
+
import time
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
from dex.client import Client
|
|
16
|
+
from dex.tests.worker_server import registry
|
|
17
|
+
from dex.tests.workflows.wait_for_state_with_wait_for_key_workflow import (
|
|
18
|
+
WaitForStateWithWaitForKeyWorkflow,
|
|
19
|
+
WaitForStateWithWaitForKeyState2,
|
|
20
|
+
)
|
|
21
|
+
from dex.workflow_options import WorkflowOptions
|
|
22
|
+
|
|
23
|
+
class TestWaitForStateWithWaitForKey(unittest.TestCase):
|
|
24
|
+
@classmethod
|
|
25
|
+
def setUpClass(cls):
|
|
26
|
+
cls.client = Client(registry)
|
|
27
|
+
|
|
28
|
+
def test_wait_for_state_with_wait_for_key_workflow(self):
|
|
29
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
30
|
+
|
|
31
|
+
wf_opts = WorkflowOptions()
|
|
32
|
+
wf_opts.add_wait_for_completion_state_ids(WaitForStateWithWaitForKeyState2)
|
|
33
|
+
|
|
34
|
+
self.client.start_workflow(
|
|
35
|
+
WaitForStateWithWaitForKeyWorkflow, wf_id, 100, None, wf_opts
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
self.client.wait_for_state_execution_completion_with_wait_for_key(
|
|
39
|
+
WaitForStateWithWaitForKeyState2, wf_id, "testKey"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
self.client.wait_for_workflow_completion(wf_id)
|
|
43
|
+
|
|
44
|
+
# TODO: Improve this test by verifying both workflows completed with describe_workflow method
|
|
45
|
+
# https://github.com/superdurable/dex-python-sdk/issues/22
|
|
46
|
+
#
|
|
47
|
+
# child_wf_id =
|
|
48
|
+
#
|
|
49
|
+
# wf_info = self.client.describe_workflow(wf_id);
|
|
50
|
+
# assert wf_info.workflow_status == WorkflowStatus.COMPLETED
|
|
51
|
+
#
|
|
52
|
+
# child_wf_info = self.client.describe_workflow(child_wf_id);
|
|
53
|
+
# assert child_wf_info.workflow_status == WorkflowStatus.COMPLETED
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
import inspect
|
|
12
|
+
import time
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
from dex.client import Client
|
|
16
|
+
from dex.errors import (
|
|
17
|
+
WorkflowAlreadyStartedError,
|
|
18
|
+
WorkflowCanceled,
|
|
19
|
+
WorkflowFailed,
|
|
20
|
+
WorkflowNotExistsError,
|
|
21
|
+
WorkflowStillRunningError,
|
|
22
|
+
WorkflowTerminated,
|
|
23
|
+
WorkflowTimeout,
|
|
24
|
+
)
|
|
25
|
+
from dex.dex_api.models import WorkflowStopType
|
|
26
|
+
from dex.stop_workflow_options import StopWorkflowOptions
|
|
27
|
+
from dex.tests.worker_server import registry
|
|
28
|
+
from dex.tests.workflows.wait_internal_channel_workflow import (
|
|
29
|
+
WaitInternalChannelWorkflow,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
class TestWorkflowErrors(unittest.TestCase):
|
|
33
|
+
@classmethod
|
|
34
|
+
def setUpClass(cls):
|
|
35
|
+
cls.client = Client(registry)
|
|
36
|
+
|
|
37
|
+
def test_workflow_timeout(self):
|
|
38
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
39
|
+
self.client.start_workflow(WaitInternalChannelWorkflow, wf_id, 1)
|
|
40
|
+
with self.assertRaises(WorkflowTimeout):
|
|
41
|
+
self.client.wait_for_workflow_completion(wf_id, str)
|
|
42
|
+
with self.assertRaises(WorkflowNotExistsError):
|
|
43
|
+
self.client.wait_for_workflow_completion("invalid_id", str)
|
|
44
|
+
|
|
45
|
+
def test_workflow_still_running_when_wait(self):
|
|
46
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
47
|
+
# client_options = ClientOptions.local_default()
|
|
48
|
+
# client_options.api_timeout = 5
|
|
49
|
+
# TODO using a shorter api timeout will throw a different timeout error, it's better to unify it
|
|
50
|
+
self.client.start_workflow(WaitInternalChannelWorkflow, wf_id, 61)
|
|
51
|
+
|
|
52
|
+
with self.assertRaises(WorkflowAlreadyStartedError):
|
|
53
|
+
self.client.start_workflow(WaitInternalChannelWorkflow, wf_id, 61)
|
|
54
|
+
|
|
55
|
+
with self.assertRaises(WorkflowStillRunningError):
|
|
56
|
+
self.client.wait_for_workflow_completion(wf_id, str)
|
|
57
|
+
|
|
58
|
+
def test_workflow_canceled(self):
|
|
59
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
60
|
+
self.client.start_workflow(WaitInternalChannelWorkflow, wf_id, 10)
|
|
61
|
+
self.client.stop_workflow(wf_id)
|
|
62
|
+
with self.assertRaises(WorkflowCanceled):
|
|
63
|
+
self.client.wait_for_workflow_completion(wf_id, str)
|
|
64
|
+
|
|
65
|
+
def test_workflow_terminated(self):
|
|
66
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
67
|
+
self.client.start_workflow(WaitInternalChannelWorkflow, wf_id, 10)
|
|
68
|
+
self.client.stop_workflow(
|
|
69
|
+
wf_id,
|
|
70
|
+
StopWorkflowOptions(
|
|
71
|
+
workflow_stop_type=WorkflowStopType.TERMINATE, reason="test"
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
with self.assertRaises(WorkflowTerminated):
|
|
75
|
+
self.client.wait_for_workflow_completion(wf_id, str)
|
|
76
|
+
|
|
77
|
+
def test_workflow_failed(self):
|
|
78
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
79
|
+
self.client.start_workflow(WaitInternalChannelWorkflow, wf_id, 10)
|
|
80
|
+
self.client.stop_workflow(
|
|
81
|
+
wf_id,
|
|
82
|
+
StopWorkflowOptions(
|
|
83
|
+
workflow_stop_type=WorkflowStopType.FAIL, reason="test"
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
with self.assertRaises(WorkflowFailed):
|
|
87
|
+
self.client.wait_for_workflow_completion(wf_id, str)
|