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,90 @@
|
|
|
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.command_results import CommandResults
|
|
16
|
+
from dex.communication import Communication
|
|
17
|
+
from dex.dex_api.models import RetryPolicy
|
|
18
|
+
from dex.dex_api.models.wait_until_api_failure_policy import WaitUntilApiFailurePolicy
|
|
19
|
+
from dex.persistence import Persistence
|
|
20
|
+
from dex.state_decision import StateDecision
|
|
21
|
+
from dex.state_schema import StateSchema
|
|
22
|
+
from dex.workflow import ObjectWorkflow
|
|
23
|
+
from dex.workflow_context import WorkflowContext
|
|
24
|
+
from dex.workflow_state import T, WorkflowState
|
|
25
|
+
from dex.workflow_state_options import WorkflowStateOptions
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class FailWaitUntilState(WorkflowState[None]):
|
|
29
|
+
def wait_until(
|
|
30
|
+
self,
|
|
31
|
+
ctx: WorkflowContext,
|
|
32
|
+
input: T,
|
|
33
|
+
persistence: Persistence,
|
|
34
|
+
communication: Communication,
|
|
35
|
+
):
|
|
36
|
+
raise RuntimeError("failed wait_until")
|
|
37
|
+
|
|
38
|
+
def execute(
|
|
39
|
+
self,
|
|
40
|
+
ctx: WorkflowContext,
|
|
41
|
+
input: T,
|
|
42
|
+
command_results: CommandResults,
|
|
43
|
+
persistence: Persistence,
|
|
44
|
+
communication: Communication,
|
|
45
|
+
):
|
|
46
|
+
return StateDecision.single_next_state(FailExecuteState)
|
|
47
|
+
|
|
48
|
+
def get_state_options(self) -> WorkflowStateOptions:
|
|
49
|
+
return WorkflowStateOptions(
|
|
50
|
+
execute_api_retry_policy=RetryPolicy(maximum_attempts=1),
|
|
51
|
+
wait_until_api_retry_policy=RetryPolicy(maximum_attempts=1),
|
|
52
|
+
proceed_to_execute_when_wait_until_retry_exhausted=WaitUntilApiFailurePolicy.PROCEED_ON_FAILURE,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class FailExecuteState(WorkflowState[None]):
|
|
57
|
+
def execute(
|
|
58
|
+
self,
|
|
59
|
+
ctx: WorkflowContext,
|
|
60
|
+
input: T,
|
|
61
|
+
command_results: CommandResults,
|
|
62
|
+
persistence: Persistence,
|
|
63
|
+
communication: Communication,
|
|
64
|
+
) -> StateDecision:
|
|
65
|
+
raise RuntimeError("a random error to fail the state")
|
|
66
|
+
|
|
67
|
+
def get_state_options(self) -> WorkflowStateOptions:
|
|
68
|
+
return WorkflowStateOptions(
|
|
69
|
+
execute_api_retry_policy=RetryPolicy(maximum_attempts=1),
|
|
70
|
+
proceed_to_state_when_execute_retry_exhausted=RecoveryState,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class RecoveryState(WorkflowState[None]):
|
|
75
|
+
def execute(
|
|
76
|
+
self,
|
|
77
|
+
ctx: WorkflowContext,
|
|
78
|
+
input: T,
|
|
79
|
+
command_results: CommandResults,
|
|
80
|
+
persistence: Persistence,
|
|
81
|
+
communication: Communication,
|
|
82
|
+
) -> StateDecision:
|
|
83
|
+
return StateDecision.graceful_complete_workflow("done")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class RecoveryWorkflow(ObjectWorkflow):
|
|
87
|
+
def get_workflow_states(self) -> StateSchema:
|
|
88
|
+
return StateSchema.with_starting_state(
|
|
89
|
+
FailWaitUntilState(), FailExecuteState(), RecoveryState()
|
|
90
|
+
)
|
|
@@ -0,0 +1,238 @@
|
|
|
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
|
|
16
|
+
|
|
17
|
+
from dex.command_request import CommandRequest, InternalChannelCommand
|
|
18
|
+
from dex.command_results import CommandResults
|
|
19
|
+
from dex.communication import Communication
|
|
20
|
+
from dex.communication_schema import CommunicationMethod, CommunicationSchema
|
|
21
|
+
from dex.dex_api.models.search_attribute_value_type import SearchAttributeValueType
|
|
22
|
+
from dex.persistence import Persistence
|
|
23
|
+
from dex.persistence_options import PersistenceOptions
|
|
24
|
+
from dex.persistence_schema import PersistenceField, PersistenceSchema
|
|
25
|
+
from dex.rpc import rpc
|
|
26
|
+
from dex.state_decision import StateDecision
|
|
27
|
+
from dex.state_schema import StateSchema
|
|
28
|
+
from dex.workflow import ObjectWorkflow
|
|
29
|
+
from dex.workflow_context import WorkflowContext
|
|
30
|
+
from dex.workflow_state import WorkflowState
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
INTERNAL_CHANNEL_NAME = "test-channel-1"
|
|
34
|
+
TEST_DATA_OBJECT_KEY = "data-obj-1"
|
|
35
|
+
TEST_SEARCH_ATTRIBUTE_KEY = "CustomKeywordField"
|
|
36
|
+
TEST_SEARCH_ATTRIBUTE_INT = "CustomIntField"
|
|
37
|
+
RPC_INPUT = "rpc-input"
|
|
38
|
+
RPC_OUTPUT = 100
|
|
39
|
+
TEST_STR = "test-str"
|
|
40
|
+
TEST_DELAY = 0.1
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class RpcMemoWorkflow(ObjectWorkflow):
|
|
44
|
+
def get_workflow_states(self) -> StateSchema:
|
|
45
|
+
return StateSchema.with_starting_state(RpcMemoWorkflowState1())
|
|
46
|
+
|
|
47
|
+
def get_persistence_schema(self) -> PersistenceSchema:
|
|
48
|
+
return PersistenceSchema.create(
|
|
49
|
+
PersistenceField.data_attribute_def(TEST_DATA_OBJECT_KEY, str),
|
|
50
|
+
PersistenceField.search_attribute_def(
|
|
51
|
+
TEST_SEARCH_ATTRIBUTE_INT, SearchAttributeValueType.INT
|
|
52
|
+
),
|
|
53
|
+
PersistenceField.search_attribute_def(
|
|
54
|
+
TEST_SEARCH_ATTRIBUTE_KEY, SearchAttributeValueType.KEYWORD
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def get_communication_schema(self) -> CommunicationSchema:
|
|
59
|
+
return CommunicationSchema.create(
|
|
60
|
+
CommunicationMethod.internal_channel_def(INTERNAL_CHANNEL_NAME, None)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def get_persistence_options(self) -> PersistenceOptions:
|
|
64
|
+
return PersistenceOptions(enable_caching=True)
|
|
65
|
+
|
|
66
|
+
@rpc()
|
|
67
|
+
def test_rpc_func1(
|
|
68
|
+
self,
|
|
69
|
+
ctx: WorkflowContext,
|
|
70
|
+
input: str,
|
|
71
|
+
persistence: Persistence,
|
|
72
|
+
communication: Communication,
|
|
73
|
+
) -> int:
|
|
74
|
+
if not ctx.workflow_id or not ctx.workflow_run_id:
|
|
75
|
+
raise RuntimeError("invalid context")
|
|
76
|
+
|
|
77
|
+
persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, None)
|
|
78
|
+
persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, input)
|
|
79
|
+
persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, input)
|
|
80
|
+
persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
|
|
81
|
+
communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
|
|
82
|
+
return RPC_OUTPUT
|
|
83
|
+
|
|
84
|
+
@rpc()
|
|
85
|
+
def test_rpc_func0(
|
|
86
|
+
self,
|
|
87
|
+
ctx: WorkflowContext,
|
|
88
|
+
input: str,
|
|
89
|
+
persistence: Persistence,
|
|
90
|
+
communication: Communication,
|
|
91
|
+
) -> int:
|
|
92
|
+
if not ctx.workflow_id or not ctx.workflow_run_id:
|
|
93
|
+
raise RuntimeError("invalid context")
|
|
94
|
+
|
|
95
|
+
persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, TEST_STR)
|
|
96
|
+
persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, TEST_STR)
|
|
97
|
+
persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
|
|
98
|
+
communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
|
|
99
|
+
return RPC_OUTPUT
|
|
100
|
+
|
|
101
|
+
@rpc()
|
|
102
|
+
def test_rpc_proc1(
|
|
103
|
+
self,
|
|
104
|
+
ctx: WorkflowContext,
|
|
105
|
+
input: str,
|
|
106
|
+
persistence: Persistence,
|
|
107
|
+
communication: Communication,
|
|
108
|
+
):
|
|
109
|
+
if not ctx.workflow_id or not ctx.workflow_run_id:
|
|
110
|
+
raise RuntimeError("invalid context")
|
|
111
|
+
|
|
112
|
+
persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, input)
|
|
113
|
+
persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, input)
|
|
114
|
+
persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
|
|
115
|
+
communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
|
|
116
|
+
|
|
117
|
+
@rpc()
|
|
118
|
+
def test_rpc_proc0(
|
|
119
|
+
self,
|
|
120
|
+
ctx: WorkflowContext,
|
|
121
|
+
input: str,
|
|
122
|
+
persistence: Persistence,
|
|
123
|
+
communication: Communication,
|
|
124
|
+
):
|
|
125
|
+
if not ctx.workflow_id or not ctx.workflow_run_id:
|
|
126
|
+
raise RuntimeError("invalid context")
|
|
127
|
+
|
|
128
|
+
persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, TEST_STR)
|
|
129
|
+
persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, TEST_STR)
|
|
130
|
+
persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
|
|
131
|
+
communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
|
|
132
|
+
|
|
133
|
+
@rpc()
|
|
134
|
+
def test_rpc_func1_readonly(
|
|
135
|
+
self,
|
|
136
|
+
ctx: WorkflowContext,
|
|
137
|
+
input: str,
|
|
138
|
+
persistence: Persistence,
|
|
139
|
+
communication: Communication,
|
|
140
|
+
) -> int:
|
|
141
|
+
if not ctx.workflow_id or not ctx.workflow_run_id:
|
|
142
|
+
raise RuntimeError("invalid context")
|
|
143
|
+
return RPC_OUTPUT
|
|
144
|
+
|
|
145
|
+
@rpc()
|
|
146
|
+
def test_rpc_set_data_attribute(
|
|
147
|
+
self,
|
|
148
|
+
ctx: WorkflowContext,
|
|
149
|
+
input: str,
|
|
150
|
+
persistence: Persistence,
|
|
151
|
+
communication: Communication,
|
|
152
|
+
):
|
|
153
|
+
if not ctx.workflow_id or not ctx.workflow_run_id:
|
|
154
|
+
raise RuntimeError("invalid context")
|
|
155
|
+
persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, input)
|
|
156
|
+
|
|
157
|
+
@rpc()
|
|
158
|
+
def test_rpc_get_data_attribute(
|
|
159
|
+
self,
|
|
160
|
+
ctx: WorkflowContext,
|
|
161
|
+
input: str,
|
|
162
|
+
persistence: Persistence,
|
|
163
|
+
communication: Communication,
|
|
164
|
+
) -> str:
|
|
165
|
+
if not ctx.workflow_id or not ctx.workflow_run_id:
|
|
166
|
+
raise RuntimeError("invalid context")
|
|
167
|
+
return persistence.get_data_attribute(TEST_DATA_OBJECT_KEY)
|
|
168
|
+
|
|
169
|
+
@rpc(bypass_caching_for_strong_consistency=True)
|
|
170
|
+
def test_rpc_get_data_attribute_strong_consistency(
|
|
171
|
+
self,
|
|
172
|
+
ctx: WorkflowContext,
|
|
173
|
+
input: str,
|
|
174
|
+
persistence: Persistence,
|
|
175
|
+
communication: Communication,
|
|
176
|
+
) -> str:
|
|
177
|
+
if not ctx.workflow_id or not ctx.workflow_run_id:
|
|
178
|
+
raise RuntimeError("invalid context")
|
|
179
|
+
return persistence.get_data_attribute(TEST_DATA_OBJECT_KEY)
|
|
180
|
+
|
|
181
|
+
@rpc()
|
|
182
|
+
def test_rpc_set_keyword(
|
|
183
|
+
self,
|
|
184
|
+
ctx: WorkflowContext,
|
|
185
|
+
input: str,
|
|
186
|
+
persistence: Persistence,
|
|
187
|
+
communication: Communication,
|
|
188
|
+
):
|
|
189
|
+
if not ctx.workflow_id or not ctx.workflow_run_id:
|
|
190
|
+
raise RuntimeError("invalid context")
|
|
191
|
+
persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, input)
|
|
192
|
+
|
|
193
|
+
@rpc(bypass_caching_for_strong_consistency=True)
|
|
194
|
+
def test_rpc_get_keyword_strong_consistency(
|
|
195
|
+
self,
|
|
196
|
+
ctx: WorkflowContext,
|
|
197
|
+
input: str,
|
|
198
|
+
persistence: Persistence,
|
|
199
|
+
communication: Communication,
|
|
200
|
+
) -> Optional[str]:
|
|
201
|
+
if not ctx.workflow_id or not ctx.workflow_run_id:
|
|
202
|
+
raise RuntimeError("invalid context")
|
|
203
|
+
return persistence.get_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY)
|
|
204
|
+
|
|
205
|
+
@rpc()
|
|
206
|
+
def test_rpc_get_keyword(
|
|
207
|
+
self,
|
|
208
|
+
ctx: WorkflowContext,
|
|
209
|
+
input: str,
|
|
210
|
+
persistence: Persistence,
|
|
211
|
+
communication: Communication,
|
|
212
|
+
) -> Optional[str]:
|
|
213
|
+
if not ctx.workflow_id or not ctx.workflow_run_id:
|
|
214
|
+
raise RuntimeError("invalid context")
|
|
215
|
+
return persistence.get_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class RpcMemoWorkflowState1(WorkflowState[int]):
|
|
219
|
+
def wait_until(
|
|
220
|
+
self,
|
|
221
|
+
ctx: WorkflowContext,
|
|
222
|
+
input: int,
|
|
223
|
+
persistence: Persistence,
|
|
224
|
+
communication: Communication,
|
|
225
|
+
) -> CommandRequest:
|
|
226
|
+
return CommandRequest.for_any_command_completed(
|
|
227
|
+
InternalChannelCommand.by_name(INTERNAL_CHANNEL_NAME)
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
def execute(
|
|
231
|
+
self,
|
|
232
|
+
ctx: WorkflowContext,
|
|
233
|
+
input: int,
|
|
234
|
+
command_results: CommandResults,
|
|
235
|
+
persistence: Persistence,
|
|
236
|
+
communication: Communication,
|
|
237
|
+
) -> StateDecision:
|
|
238
|
+
return StateDecision.graceful_complete_workflow()
|
|
@@ -0,0 +1,125 @@
|
|
|
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 dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
from dex.command_request import CommandRequest, InternalChannelCommand
|
|
18
|
+
from dex.command_results import CommandResults
|
|
19
|
+
from dex.communication import Communication
|
|
20
|
+
from dex.communication_schema import CommunicationMethod, CommunicationSchema
|
|
21
|
+
from dex.persistence import Persistence
|
|
22
|
+
from dex.persistence_schema import PersistenceField, PersistenceSchema
|
|
23
|
+
from dex.rpc import rpc
|
|
24
|
+
from dex.state_decision import StateDecision
|
|
25
|
+
from dex.state_schema import StateSchema
|
|
26
|
+
from dex.workflow import ObjectWorkflow
|
|
27
|
+
from dex.workflow_context import WorkflowContext
|
|
28
|
+
from dex.workflow_state import T, WorkflowState
|
|
29
|
+
|
|
30
|
+
test_data_attribute = "test-1"
|
|
31
|
+
channel_name = "test-2"
|
|
32
|
+
idle_channel_name = "test-3"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Mydata:
|
|
37
|
+
strdata: str
|
|
38
|
+
intdata: int
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class WaitState(WorkflowState[None]):
|
|
42
|
+
def wait_until(
|
|
43
|
+
self,
|
|
44
|
+
ctx: WorkflowContext,
|
|
45
|
+
input: T,
|
|
46
|
+
persistence: Persistence,
|
|
47
|
+
communication: Communication,
|
|
48
|
+
) -> CommandRequest:
|
|
49
|
+
return CommandRequest.for_all_command_completed(
|
|
50
|
+
InternalChannelCommand.by_name(channel_name)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def execute(
|
|
54
|
+
self,
|
|
55
|
+
ctx: WorkflowContext,
|
|
56
|
+
input: T,
|
|
57
|
+
command_results: CommandResults,
|
|
58
|
+
persistence: Persistence,
|
|
59
|
+
communication: Communication,
|
|
60
|
+
) -> StateDecision:
|
|
61
|
+
return StateDecision.single_next_state(EndState)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class EndState(WorkflowState[None]):
|
|
65
|
+
def execute(
|
|
66
|
+
self,
|
|
67
|
+
ctx: WorkflowContext,
|
|
68
|
+
input: T,
|
|
69
|
+
command_results: CommandResults,
|
|
70
|
+
persistence: Persistence,
|
|
71
|
+
communication: Communication,
|
|
72
|
+
) -> StateDecision:
|
|
73
|
+
return StateDecision.graceful_complete_workflow("done")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class RPCWorkflow(ObjectWorkflow):
|
|
77
|
+
def get_persistence_schema(self) -> PersistenceSchema:
|
|
78
|
+
return PersistenceSchema.create(
|
|
79
|
+
PersistenceField.data_attribute_def(test_data_attribute, int)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def get_communication_schema(self) -> CommunicationSchema:
|
|
83
|
+
return CommunicationSchema.create(
|
|
84
|
+
CommunicationMethod.internal_channel_def(channel_name, type(None)),
|
|
85
|
+
CommunicationMethod.internal_channel_def(idle_channel_name, str),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def get_workflow_states(self) -> StateSchema:
|
|
89
|
+
return StateSchema.no_starting_state(WaitState(), EndState())
|
|
90
|
+
|
|
91
|
+
@rpc(timeout_seconds=100)
|
|
92
|
+
def test_simple_rpc(self):
|
|
93
|
+
return 123
|
|
94
|
+
|
|
95
|
+
@rpc()
|
|
96
|
+
def test_rpc_persistence_write(
|
|
97
|
+
self,
|
|
98
|
+
input: int,
|
|
99
|
+
persistence: Persistence,
|
|
100
|
+
):
|
|
101
|
+
persistence.set_data_attribute(test_data_attribute, input)
|
|
102
|
+
|
|
103
|
+
@rpc()
|
|
104
|
+
def test_rpc_persistence_read(self, pers: Persistence):
|
|
105
|
+
return pers.get_data_attribute(test_data_attribute)
|
|
106
|
+
|
|
107
|
+
@rpc()
|
|
108
|
+
def test_rpc_trigger_state(self, pers: Persistence, com: Communication, i: int):
|
|
109
|
+
pers.set_data_attribute(test_data_attribute, i)
|
|
110
|
+
com.trigger_state_execution(WaitState)
|
|
111
|
+
|
|
112
|
+
@rpc()
|
|
113
|
+
def test_rpc_publish_channel(self, com: Communication):
|
|
114
|
+
com.publish_to_internal_channel(channel_name)
|
|
115
|
+
|
|
116
|
+
@rpc()
|
|
117
|
+
def test_rpc_publish_to_idle_channel(self, com: Communication, data: str):
|
|
118
|
+
com.publish_to_internal_channel(idle_channel_name, data)
|
|
119
|
+
return com.get_internal_channel_size(idle_channel_name)
|
|
120
|
+
|
|
121
|
+
@rpc()
|
|
122
|
+
def test_rpc_input_type(self, input: Mydata) -> Mydata:
|
|
123
|
+
if input.intdata != 100 or input.strdata != "test":
|
|
124
|
+
raise Exception("input type test failed")
|
|
125
|
+
return input
|
|
@@ -0,0 +1,100 @@
|
|
|
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.command_request import CommandRequest
|
|
16
|
+
from dex.command_results import CommandResults
|
|
17
|
+
from dex.communication import Communication
|
|
18
|
+
from dex.persistence_schema import PersistenceSchema, PersistenceField
|
|
19
|
+
|
|
20
|
+
from dex.dex_api.models import RetryPolicy, WaitUntilApiFailurePolicy
|
|
21
|
+
from dex.persistence import Persistence
|
|
22
|
+
from dex.state_decision import StateDecision
|
|
23
|
+
from dex.state_schema import StateSchema
|
|
24
|
+
from dex.workflow import ObjectWorkflow
|
|
25
|
+
from dex.workflow_context import WorkflowContext
|
|
26
|
+
from dex.workflow_state import T, WorkflowState
|
|
27
|
+
from dex.workflow_state_options import WorkflowStateOptions
|
|
28
|
+
|
|
29
|
+
output_da = "output_da"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class InitState(WorkflowState[str]):
|
|
33
|
+
def wait_until(
|
|
34
|
+
self,
|
|
35
|
+
ctx: WorkflowContext,
|
|
36
|
+
input: T,
|
|
37
|
+
persistence: Persistence,
|
|
38
|
+
communication: Communication,
|
|
39
|
+
) -> CommandRequest:
|
|
40
|
+
persistence.set_data_attribute(
|
|
41
|
+
output_da, str(input) + "_InitState_waitUntil_completed"
|
|
42
|
+
)
|
|
43
|
+
return CommandRequest.empty()
|
|
44
|
+
|
|
45
|
+
def execute(
|
|
46
|
+
self,
|
|
47
|
+
ctx: WorkflowContext,
|
|
48
|
+
input: T,
|
|
49
|
+
command_results: CommandResults,
|
|
50
|
+
persistence: Persistence,
|
|
51
|
+
communication: Communication,
|
|
52
|
+
) -> StateDecision:
|
|
53
|
+
data = persistence.get_data_attribute(output_da)
|
|
54
|
+
data += "_InitState_execute_completed"
|
|
55
|
+
return StateDecision.single_next_state(
|
|
56
|
+
NonInitState,
|
|
57
|
+
data,
|
|
58
|
+
WorkflowStateOptions(
|
|
59
|
+
wait_until_api_retry_policy=RetryPolicy(maximum_attempts=2),
|
|
60
|
+
proceed_to_execute_when_wait_until_retry_exhausted=WaitUntilApiFailurePolicy.PROCEED_ON_FAILURE,
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class NonInitState(WorkflowState[str]):
|
|
66
|
+
def wait_until(
|
|
67
|
+
self,
|
|
68
|
+
ctx: WorkflowContext,
|
|
69
|
+
input: T,
|
|
70
|
+
persistence: Persistence,
|
|
71
|
+
communication: Communication,
|
|
72
|
+
) -> CommandRequest:
|
|
73
|
+
raise RuntimeError("test failure")
|
|
74
|
+
|
|
75
|
+
def execute(
|
|
76
|
+
self,
|
|
77
|
+
ctx: WorkflowContext,
|
|
78
|
+
input: T,
|
|
79
|
+
command_results: CommandResults,
|
|
80
|
+
persistence: Persistence,
|
|
81
|
+
communication: Communication,
|
|
82
|
+
) -> StateDecision:
|
|
83
|
+
data = str(input) + "_NonInitState_execute_completed"
|
|
84
|
+
return StateDecision.graceful_complete_workflow(data)
|
|
85
|
+
|
|
86
|
+
def get_state_options(self) -> WorkflowStateOptions:
|
|
87
|
+
return WorkflowStateOptions(
|
|
88
|
+
wait_until_api_retry_policy=RetryPolicy(maximum_attempts=1),
|
|
89
|
+
proceed_to_execute_when_wait_until_retry_exhausted=WaitUntilApiFailurePolicy.FAIL_WORKFLOW_ON_FAILURE,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class StateOptionsOverrideWorkflow(ObjectWorkflow):
|
|
94
|
+
def get_persistence_schema(self) -> PersistenceSchema:
|
|
95
|
+
return PersistenceSchema.create(
|
|
96
|
+
PersistenceField.data_attribute_def(output_da, str),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def get_workflow_states(self) -> StateSchema:
|
|
100
|
+
return StateSchema.with_starting_state(InitState(), NonInitState())
|
|
@@ -0,0 +1,92 @@
|
|
|
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.command_request import CommandRequest
|
|
16
|
+
from dex.command_results import CommandResults
|
|
17
|
+
from dex.communication import Communication
|
|
18
|
+
|
|
19
|
+
from dex.dex_api.models import RetryPolicy, WaitUntilApiFailurePolicy
|
|
20
|
+
from dex.persistence import Persistence
|
|
21
|
+
from dex.state_decision import StateDecision
|
|
22
|
+
from dex.state_schema import StateSchema
|
|
23
|
+
from dex.workflow import ObjectWorkflow
|
|
24
|
+
from dex.workflow_context import WorkflowContext
|
|
25
|
+
from dex.workflow_state import T, WorkflowState
|
|
26
|
+
from dex.workflow_state_options import WorkflowStateOptions
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InitState1(WorkflowState[str]):
|
|
30
|
+
def get_state_options(self) -> WorkflowStateOptions:
|
|
31
|
+
return WorkflowStateOptions(
|
|
32
|
+
wait_until_api_retry_policy=RetryPolicy(maximum_attempts=1),
|
|
33
|
+
proceed_to_execute_when_wait_until_retry_exhausted=WaitUntilApiFailurePolicy.PROCEED_ON_FAILURE,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def wait_until(
|
|
37
|
+
self,
|
|
38
|
+
ctx: WorkflowContext,
|
|
39
|
+
input: T,
|
|
40
|
+
persistence: Persistence,
|
|
41
|
+
communication: Communication,
|
|
42
|
+
) -> CommandRequest:
|
|
43
|
+
raise RuntimeError("test failure")
|
|
44
|
+
|
|
45
|
+
def execute(
|
|
46
|
+
self,
|
|
47
|
+
ctx: WorkflowContext,
|
|
48
|
+
input: T,
|
|
49
|
+
command_results: CommandResults,
|
|
50
|
+
persistence: Persistence,
|
|
51
|
+
communication: Communication,
|
|
52
|
+
) -> StateDecision:
|
|
53
|
+
data = "InitState1_execute_completed"
|
|
54
|
+
return StateDecision.graceful_complete_workflow(data)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class InitState2(WorkflowState[str]):
|
|
58
|
+
def get_state_options(self) -> WorkflowStateOptions:
|
|
59
|
+
return WorkflowStateOptions(
|
|
60
|
+
wait_until_api_retry_policy=RetryPolicy(maximum_attempts=1),
|
|
61
|
+
proceed_to_execute_when_wait_until_retry_exhausted=WaitUntilApiFailurePolicy.FAIL_WORKFLOW_ON_FAILURE,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def wait_until(
|
|
65
|
+
self,
|
|
66
|
+
ctx: WorkflowContext,
|
|
67
|
+
input: T,
|
|
68
|
+
persistence: Persistence,
|
|
69
|
+
communication: Communication,
|
|
70
|
+
) -> CommandRequest:
|
|
71
|
+
raise RuntimeError("test failure")
|
|
72
|
+
|
|
73
|
+
def execute(
|
|
74
|
+
self,
|
|
75
|
+
ctx: WorkflowContext,
|
|
76
|
+
input: T,
|
|
77
|
+
command_results: CommandResults,
|
|
78
|
+
persistence: Persistence,
|
|
79
|
+
communication: Communication,
|
|
80
|
+
) -> StateDecision:
|
|
81
|
+
data = "InitState2_execute_completed"
|
|
82
|
+
return StateDecision.graceful_complete_workflow(data)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class StateOptionsWorkflow1(ObjectWorkflow):
|
|
86
|
+
def get_workflow_states(self) -> StateSchema:
|
|
87
|
+
return StateSchema.with_starting_state(InitState1())
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class StateOptionsWorkflow2(ObjectWorkflow):
|
|
91
|
+
def get_workflow_states(self) -> StateSchema:
|
|
92
|
+
return StateSchema.with_starting_state(InitState2())
|
|
@@ -0,0 +1,52 @@
|
|
|
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.command_request import CommandRequest, TimerCommand
|
|
16
|
+
from dex.command_results import CommandResults
|
|
17
|
+
from dex.communication import Communication
|
|
18
|
+
from dex.persistence import Persistence
|
|
19
|
+
from dex.state_decision import StateDecision
|
|
20
|
+
from dex.state_schema import StateSchema
|
|
21
|
+
from dex.workflow import ObjectWorkflow
|
|
22
|
+
from dex.workflow_context import WorkflowContext
|
|
23
|
+
from dex.workflow_state import T, WorkflowState
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WaitState(WorkflowState[int]):
|
|
27
|
+
def wait_until(
|
|
28
|
+
self,
|
|
29
|
+
ctx: WorkflowContext,
|
|
30
|
+
input: int,
|
|
31
|
+
persistence: Persistence,
|
|
32
|
+
communication: Communication,
|
|
33
|
+
) -> CommandRequest:
|
|
34
|
+
return CommandRequest.for_all_command_completed(
|
|
35
|
+
TimerCommand.by_seconds(input * 3600),
|
|
36
|
+
TimerCommand.by_seconds(input),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def execute(
|
|
40
|
+
self,
|
|
41
|
+
ctx: WorkflowContext,
|
|
42
|
+
input: T,
|
|
43
|
+
command_results: CommandResults,
|
|
44
|
+
persistence: Persistence,
|
|
45
|
+
communication: Communication,
|
|
46
|
+
) -> StateDecision:
|
|
47
|
+
return StateDecision.graceful_complete_workflow()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TimerWorkflow(ObjectWorkflow):
|
|
51
|
+
def get_workflow_states(self) -> StateSchema:
|
|
52
|
+
return StateSchema.with_starting_state(WaitState())
|