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.
Files changed (121) hide show
  1. dex/__init__.py +146 -0
  2. dex/_grpc_errors.py +73 -0
  3. dex/_invocation_context.py +269 -0
  4. dex/_native.pyd +0 -0
  5. dex/_native.pyi +20 -0
  6. dex/_utils.py +18 -0
  7. dex/_value_hydrator.py +223 -0
  8. dex/_value_mapper.py +174 -0
  9. dex/_worker_dispatcher.py +451 -0
  10. dex/_worker_service.py +60 -0
  11. dex/attribute.py +87 -0
  12. dex/blob_cache.py +73 -0
  13. dex/channel.py +165 -0
  14. dex/client.py +715 -0
  15. dex/client_options.py +19 -0
  16. dex/codec.py +286 -0
  17. dex/command_request.py +120 -0
  18. dex/command_results.py +107 -0
  19. dex/communication.py +136 -0
  20. dex/communication_schema.py +54 -0
  21. dex/condition.py +74 -0
  22. dex/context.py +86 -0
  23. dex/data_attributes.py +70 -0
  24. dex/dexpb/__init__.py +1 -0
  25. dex/dexpb/dex_pb2.py +381 -0
  26. dex/dexpb/dex_pb2.pyi +1734 -0
  27. dex/dexpb/dex_pb2_grpc.py +1298 -0
  28. dex/errors.py +109 -0
  29. dex/flow.py +456 -0
  30. dex/flow_config.py +29 -0
  31. dex/flow_info.py +51 -0
  32. dex/flow_options.py +122 -0
  33. dex/object_encoder.py +799 -0
  34. dex/persistence.py +89 -0
  35. dex/persistence_options.py +12 -0
  36. dex/persistence_schema.py +51 -0
  37. dex/py.typed +1 -0
  38. dex/registry.py +204 -0
  39. dex/reset_workflow_type_and_options.py +67 -0
  40. dex/rpc.py +93 -0
  41. dex/runtime_errors.py +81 -0
  42. dex/search_attributes.py +184 -0
  43. dex/state_decision.py +153 -0
  44. dex/state_execution_locals.py +66 -0
  45. dex/state_movement.py +115 -0
  46. dex/state_schema.py +48 -0
  47. dex/step.py +194 -0
  48. dex/step_execution.py +42 -0
  49. dex/stop_workflow_options.py +18 -0
  50. dex/tests/__init__.py +80 -0
  51. dex/tests/dex-service-env/.env +7 -0
  52. dex/tests/dex-service-env/docker-compose-init.sh +44 -0
  53. dex/tests/dex-service-env/docker-compose.yml +97 -0
  54. dex/tests/dex-service-env/dynamicconfig/README.md +39 -0
  55. dex/tests/dex-service-env/dynamicconfig/development-sql.yaml +9 -0
  56. dex/tests/dex-service-env/dynamicconfig/docker.yaml +2 -0
  57. dex/tests/test_abnormal_exit_workflow.py +43 -0
  58. dex/tests/test_basic_workflow.py +70 -0
  59. dex/tests/test_conditional_complete.py +50 -0
  60. dex/tests/test_describe_workflow.py +40 -0
  61. dex/tests/test_empty_data_decodes_properly.py +74 -0
  62. dex/tests/test_internal_channel.py +28 -0
  63. dex/tests/test_internal_channel_with_no_prefix_channel.py +41 -0
  64. dex/tests/test_persistence_data_attributes.py +62 -0
  65. dex/tests/test_persistence_search_attributes.py +127 -0
  66. dex/tests/test_persistence_state_execution_locals.py +38 -0
  67. dex/tests/test_rpc.py +64 -0
  68. dex/tests/test_rpc_with_memo.py +195 -0
  69. dex/tests/test_rpc_with_memo_duplicate_java_tests.py +117 -0
  70. dex/tests/test_signal.py +51 -0
  71. dex/tests/test_skip_wait_until.py +76 -0
  72. dex/tests/test_state_failure_recovery.py +28 -0
  73. dex/tests/test_timer.py +35 -0
  74. dex/tests/test_wait_for_state_execution_completion.py +53 -0
  75. dex/tests/test_workflow_errors.py +87 -0
  76. dex/tests/test_workflow_state_options.py +118 -0
  77. dex/tests/test_workflow_state_options_override.py +44 -0
  78. dex/tests/worker_server.py +64 -0
  79. dex/tests/workflows/abnormal_exit_workflow.py +42 -0
  80. dex/tests/workflows/basic_workflow.py +62 -0
  81. dex/tests/workflows/conditional_complete_workflow.py +95 -0
  82. dex/tests/workflows/describe_workflow.py +46 -0
  83. dex/tests/workflows/empty_data_workflow.py +45 -0
  84. dex/tests/workflows/internal_channel_workflow.py +129 -0
  85. dex/tests/workflows/internal_channel_workflow_with_no_prefix_channel.py +100 -0
  86. dex/tests/workflows/java_duplicate_rpc_memo_workflow.py +276 -0
  87. dex/tests/workflows/persistence_data_attributes_workflow.py +98 -0
  88. dex/tests/workflows/persistence_search_attributes_workflow.py +159 -0
  89. dex/tests/workflows/persistence_state_execution_local_workflow.py +63 -0
  90. dex/tests/workflows/recovery_workflow.py +82 -0
  91. dex/tests/workflows/rpc_memo_workflow.py +231 -0
  92. dex/tests/workflows/rpc_workflow.py +117 -0
  93. dex/tests/workflows/state_options_override_workflow.py +93 -0
  94. dex/tests/workflows/state_options_workflow.py +84 -0
  95. dex/tests/workflows/timer_workflow.py +46 -0
  96. dex/tests/workflows/wait_for_state_with_state_execution_id_workflow.py +70 -0
  97. dex/tests/workflows/wait_for_state_with_wait_for_key_workflow.py +71 -0
  98. dex/tests/workflows/wait_internal_channel_workflow.py +47 -0
  99. dex/tests/workflows/wait_signal_workflow.py +147 -0
  100. dex/timer.py +21 -0
  101. dex/type_store.py +99 -0
  102. dex/unregistered_client.py +585 -0
  103. dex/utils/__init__.py +3 -0
  104. dex/utils/dex_typing.py +25 -0
  105. dex/utils/persistence_utils.py +32 -0
  106. dex/wait.py +49 -0
  107. dex/worker.py +121 -0
  108. dex/worker_options.py +22 -0
  109. dex/worker_service.py +432 -0
  110. dex/workflow.py +79 -0
  111. dex/workflow_context.py +44 -0
  112. dex/workflow_info.py +16 -0
  113. dex/workflow_options.py +74 -0
  114. dex/workflow_state.py +123 -0
  115. dex/workflow_state_options.py +154 -0
  116. dex_python_sdk-0.0.2.dist-info/METADATA +202 -0
  117. dex_python_sdk-0.0.2.dist-info/RECORD +121 -0
  118. dex_python_sdk-0.0.2.dist-info/WHEEL +4 -0
  119. dex_python_sdk-0.0.2.dist-info/licenses/LEGACY_NOTICES.md +61 -0
  120. dex_python_sdk-0.0.2.dist-info/licenses/LICENSE +192 -0
  121. 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
@@ -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"
@@ -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)