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.
Files changed (93) hide show
  1. dex/__init__.py +14 -0
  2. dex/client.py +568 -0
  3. dex/client_options.py +34 -0
  4. dex/command_request.py +130 -0
  5. dex/command_results.py +116 -0
  6. dex/communication.py +141 -0
  7. dex/communication_schema.py +68 -0
  8. dex/data_attributes.py +75 -0
  9. dex/dexpb/__init__.py +1 -0
  10. dex/dexpb/dex_pb2.py +305 -0
  11. dex/dexpb/dex_pb2.pyi +1273 -0
  12. dex/dexpb/dex_pb2_grpc.py +1126 -0
  13. dex/errors.py +132 -0
  14. dex/object_encoder.py +818 -0
  15. dex/persistence.py +94 -0
  16. dex/persistence_options.py +24 -0
  17. dex/persistence_schema.py +58 -0
  18. dex/py.typed +1 -0
  19. dex/registry.py +209 -0
  20. dex/reset_workflow_type_and_options.py +77 -0
  21. dex/rpc.py +100 -0
  22. dex/search_attributes.py +189 -0
  23. dex/state_decision.py +161 -0
  24. dex/state_execution_locals.py +71 -0
  25. dex/state_movement.py +123 -0
  26. dex/state_schema.py +54 -0
  27. dex/stop_workflow_options.py +23 -0
  28. dex/tests/__init__.py +84 -0
  29. dex/tests/dex-service-env/.env +7 -0
  30. dex/tests/dex-service-env/docker-compose-init.sh +44 -0
  31. dex/tests/dex-service-env/docker-compose.yml +87 -0
  32. dex/tests/dex-service-env/dynamicconfig/README.md +39 -0
  33. dex/tests/dex-service-env/dynamicconfig/development-sql.yaml +6 -0
  34. dex/tests/dex-service-env/dynamicconfig/docker.yaml +0 -0
  35. dex/tests/test_abnormal_exit_workflow.py +48 -0
  36. dex/tests/test_basic_workflow.py +75 -0
  37. dex/tests/test_conditional_complete.py +56 -0
  38. dex/tests/test_describe_workflow.py +45 -0
  39. dex/tests/test_empty_data_decodes_properly.py +79 -0
  40. dex/tests/test_internal_channel.py +33 -0
  41. dex/tests/test_internal_channel_with_no_prefix_channel.py +46 -0
  42. dex/tests/test_persistence_data_attributes.py +67 -0
  43. dex/tests/test_persistence_search_attributes.py +132 -0
  44. dex/tests/test_persistence_state_execution_locals.py +43 -0
  45. dex/tests/test_rpc.py +69 -0
  46. dex/tests/test_rpc_with_memo.py +200 -0
  47. dex/tests/test_rpc_with_memo_duplicate_java_tests.py +122 -0
  48. dex/tests/test_signal.py +56 -0
  49. dex/tests/test_skip_wait_until.py +85 -0
  50. dex/tests/test_state_failure_recovery.py +33 -0
  51. dex/tests/test_timer.py +40 -0
  52. dex/tests/test_wait_for_state_execution_completion.py +58 -0
  53. dex/tests/test_workflow_errors.py +92 -0
  54. dex/tests/test_workflow_state_options.py +123 -0
  55. dex/tests/test_workflow_state_options_override.py +49 -0
  56. dex/tests/worker_server.py +74 -0
  57. dex/tests/workflows/abnormal_exit_workflow.py +48 -0
  58. dex/tests/workflows/basic_workflow.py +69 -0
  59. dex/tests/workflows/conditional_complete_workflow.py +101 -0
  60. dex/tests/workflows/describe_workflow.py +52 -0
  61. dex/tests/workflows/empty_data_workflow.py +51 -0
  62. dex/tests/workflows/internal_channel_workflow.py +137 -0
  63. dex/tests/workflows/internal_channel_workflow_with_no_prefix_channel.py +108 -0
  64. dex/tests/workflows/java_duplicate_rpc_memo_workflow.py +285 -0
  65. dex/tests/workflows/persistence_data_attributes_workflow.py +104 -0
  66. dex/tests/workflows/persistence_search_attributes_workflow.py +167 -0
  67. dex/tests/workflows/persistence_state_execution_local_workflow.py +69 -0
  68. dex/tests/workflows/recovery_workflow.py +90 -0
  69. dex/tests/workflows/rpc_memo_workflow.py +238 -0
  70. dex/tests/workflows/rpc_workflow.py +125 -0
  71. dex/tests/workflows/state_options_override_workflow.py +100 -0
  72. dex/tests/workflows/state_options_workflow.py +92 -0
  73. dex/tests/workflows/timer_workflow.py +52 -0
  74. dex/tests/workflows/wait_for_state_with_state_execution_id_workflow.py +77 -0
  75. dex/tests/workflows/wait_for_state_with_wait_for_key_workflow.py +78 -0
  76. dex/tests/workflows/wait_internal_channel_workflow.py +53 -0
  77. dex/tests/workflows/wait_signal_workflow.py +154 -0
  78. dex/type_store.py +105 -0
  79. dex/unregistered_client.py +595 -0
  80. dex/utils/__init__.py +14 -0
  81. dex/utils/dex_typing.py +31 -0
  82. dex/utils/persistence_utils.py +37 -0
  83. dex/worker_service.py +441 -0
  84. dex/workflow.py +86 -0
  85. dex/workflow_context.py +50 -0
  86. dex/workflow_info.py +21 -0
  87. dex/workflow_options.py +79 -0
  88. dex/workflow_state.py +134 -0
  89. dex/workflow_state_options.py +160 -0
  90. dex_python_sdk-0.0.1.dist-info/METADATA +140 -0
  91. dex_python_sdk-0.0.1.dist-info/RECORD +93 -0
  92. dex_python_sdk-0.0.1.dist-info/WHEEL +4 -0
  93. dex_python_sdk-0.0.1.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,285 @@
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
+ """
34
+ NOTE: This workflow and its tests were translated from dex-java-sdk, but currently, there is a
35
+ discrepency between the sdks in their handling of empty graceful complete workflow
36
+ messages. These tests will not pass, so they are curretly being skipped. They were added
37
+ here for posterity.
38
+ """
39
+
40
+
41
+ INTERNAL_CHANNEL_NAME = "test-channel-1"
42
+ TEST_DATA_OBJECT_KEY = "data-obj-1"
43
+ TEST_SEARCH_ATTRIBUTE_KEY = "CustomKeywordField"
44
+ TEST_SEARCH_ATTRIBUTE_INT = "CustomIntField"
45
+ RPC_INPUT = "rpc-input"
46
+ RPC_OUTPUT = 100
47
+ TEST_STR = "test-str"
48
+ TEST_DELAY = 0.1
49
+
50
+
51
+ class JavaDuplicateRpcMemoWorkflow(ObjectWorkflow):
52
+ def get_workflow_states(self) -> StateSchema:
53
+ return StateSchema.with_starting_state(
54
+ RpcMemoWorkflowState1(), RpcMemoWorkflowState2()
55
+ )
56
+
57
+ def get_persistence_schema(self) -> PersistenceSchema:
58
+ return PersistenceSchema.create(
59
+ PersistenceField.data_attribute_def(TEST_DATA_OBJECT_KEY, str),
60
+ PersistenceField.search_attribute_def(
61
+ TEST_SEARCH_ATTRIBUTE_INT, SearchAttributeValueType.INT
62
+ ),
63
+ PersistenceField.search_attribute_def(
64
+ TEST_SEARCH_ATTRIBUTE_KEY, SearchAttributeValueType.KEYWORD
65
+ ),
66
+ )
67
+
68
+ def get_communication_schema(self) -> CommunicationSchema:
69
+ return CommunicationSchema.create(
70
+ CommunicationMethod.internal_channel_def(INTERNAL_CHANNEL_NAME, None)
71
+ )
72
+
73
+ def get_persistence_options(self) -> PersistenceOptions:
74
+ return PersistenceOptions(enable_caching=True)
75
+
76
+ @rpc()
77
+ def test_rpc_func1(
78
+ self,
79
+ ctx: WorkflowContext,
80
+ input: str,
81
+ persistence: Persistence,
82
+ communication: Communication,
83
+ ) -> int:
84
+ if not ctx.workflow_id or not ctx.workflow_run_id:
85
+ raise RuntimeError("invalid context")
86
+
87
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, None)
88
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, input)
89
+ persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, input)
90
+ persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
91
+ communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
92
+ communication.trigger_state_execution(RpcMemoWorkflowState2)
93
+ return RPC_OUTPUT
94
+
95
+ @rpc()
96
+ def test_rpc_func0(
97
+ self,
98
+ ctx: WorkflowContext,
99
+ input: str,
100
+ persistence: Persistence,
101
+ communication: Communication,
102
+ ) -> int:
103
+ if not ctx.workflow_id or not ctx.workflow_run_id:
104
+ raise RuntimeError("invalid context")
105
+
106
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, TEST_STR)
107
+ persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, TEST_STR)
108
+ persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
109
+ communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
110
+ communication.trigger_state_execution(RpcMemoWorkflowState2)
111
+ return RPC_OUTPUT
112
+
113
+ @rpc()
114
+ def test_rpc_proc1(
115
+ self,
116
+ ctx: WorkflowContext,
117
+ input: str,
118
+ persistence: Persistence,
119
+ communication: Communication,
120
+ ):
121
+ if not ctx.workflow_id or not ctx.workflow_run_id:
122
+ raise RuntimeError("invalid context")
123
+
124
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, input)
125
+ persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, input)
126
+ persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
127
+ communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
128
+ communication.trigger_state_execution(RpcMemoWorkflowState2)
129
+
130
+ @rpc()
131
+ def test_rpc_proc0(
132
+ self,
133
+ ctx: WorkflowContext,
134
+ input: str,
135
+ persistence: Persistence,
136
+ communication: Communication,
137
+ ):
138
+ if not ctx.workflow_id or not ctx.workflow_run_id:
139
+ raise RuntimeError("invalid context")
140
+
141
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, TEST_STR)
142
+ persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, TEST_STR)
143
+ persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
144
+ communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
145
+ communication.trigger_state_execution(RpcMemoWorkflowState2)
146
+
147
+ @rpc()
148
+ def test_rpc_func1_readonly(
149
+ self,
150
+ ctx: WorkflowContext,
151
+ input: str,
152
+ persistence: Persistence,
153
+ communication: Communication,
154
+ ) -> int:
155
+ if not ctx.workflow_id or not ctx.workflow_run_id:
156
+ raise RuntimeError("invalid context")
157
+ return RPC_OUTPUT
158
+
159
+ @rpc()
160
+ def test_rpc_set_data_attribute(
161
+ self,
162
+ ctx: WorkflowContext,
163
+ input: str,
164
+ persistence: Persistence,
165
+ communication: Communication,
166
+ ):
167
+ if not ctx.workflow_id or not ctx.workflow_run_id:
168
+ raise RuntimeError("invalid context")
169
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, input)
170
+
171
+ @rpc()
172
+ def test_rpc_get_data_attribute(
173
+ self,
174
+ ctx: WorkflowContext,
175
+ input: str,
176
+ persistence: Persistence,
177
+ communication: Communication,
178
+ ) -> str:
179
+ if not ctx.workflow_id or not ctx.workflow_run_id:
180
+ raise RuntimeError("invalid context")
181
+ return persistence.get_data_attribute(TEST_DATA_OBJECT_KEY)
182
+
183
+ @rpc(bypass_caching_for_strong_consistency=True)
184
+ def test_rpc_get_data_attribute_strong_consistency(
185
+ self,
186
+ ctx: WorkflowContext,
187
+ input: str,
188
+ persistence: Persistence,
189
+ communication: Communication,
190
+ ) -> str:
191
+ if not ctx.workflow_id or not ctx.workflow_run_id:
192
+ raise RuntimeError("invalid context")
193
+ return persistence.get_data_attribute(TEST_DATA_OBJECT_KEY)
194
+
195
+ @rpc()
196
+ def test_rpc_set_keyword(
197
+ self,
198
+ ctx: WorkflowContext,
199
+ input: str,
200
+ persistence: Persistence,
201
+ communication: Communication,
202
+ ):
203
+ if not ctx.workflow_id or not ctx.workflow_run_id:
204
+ raise RuntimeError("invalid context")
205
+ persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, input)
206
+
207
+ @rpc(bypass_caching_for_strong_consistency=True)
208
+ def test_rpc_get_keyword_strong_consistency(
209
+ self,
210
+ ctx: WorkflowContext,
211
+ input: str,
212
+ persistence: Persistence,
213
+ communication: Communication,
214
+ ) -> Optional[str]:
215
+ if not ctx.workflow_id or not ctx.workflow_run_id:
216
+ raise RuntimeError("invalid context")
217
+ return persistence.get_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY)
218
+
219
+ @rpc()
220
+ def test_rpc_get_keyword(
221
+ self,
222
+ ctx: WorkflowContext,
223
+ input: str,
224
+ persistence: Persistence,
225
+ communication: Communication,
226
+ ) -> Optional[str]:
227
+ if not ctx.workflow_id or not ctx.workflow_run_id:
228
+ raise RuntimeError("invalid context")
229
+ return persistence.get_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY)
230
+
231
+
232
+ class RpcMemoWorkflowState1(WorkflowState[int]):
233
+ def wait_until(
234
+ self,
235
+ ctx: WorkflowContext,
236
+ input: int,
237
+ persistence: Persistence,
238
+ communication: Communication,
239
+ ) -> CommandRequest:
240
+ return CommandRequest.for_any_command_completed(
241
+ InternalChannelCommand.by_name(INTERNAL_CHANNEL_NAME)
242
+ )
243
+
244
+ def execute(
245
+ self,
246
+ ctx: WorkflowContext,
247
+ input: int,
248
+ command_results: CommandResults,
249
+ persistence: Persistence,
250
+ communication: Communication,
251
+ ) -> StateDecision:
252
+ return StateDecision.single_next_state(RpcMemoWorkflowState2, 0)
253
+
254
+
255
+ class RpcMemoWorkflowState2(WorkflowState[int]):
256
+ _counter: int = 0
257
+
258
+ def wait_until(
259
+ self,
260
+ ctx: WorkflowContext,
261
+ input: int,
262
+ persistence: Persistence,
263
+ communication: Communication,
264
+ ) -> CommandRequest:
265
+ return CommandRequest.empty()
266
+
267
+ def execute(
268
+ self,
269
+ ctx: WorkflowContext,
270
+ input: int,
271
+ command_results: CommandResults,
272
+ persistence: Persistence,
273
+ communication: Communication,
274
+ ) -> StateDecision:
275
+ self._counter += 1
276
+ if self._counter == 2:
277
+ return StateDecision.graceful_complete_workflow(self._counter)
278
+ else:
279
+ return StateDecision.graceful_complete_workflow()
280
+
281
+ @classmethod
282
+ def reset_counter(cls) -> int:
283
+ old = cls._counter
284
+ cls._counter = 0
285
+ return old
@@ -0,0 +1,104 @@
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 import Persistence
19
+ from dex.persistence_schema import PersistenceField, PersistenceSchema
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
+
26
+ initial_da_1 = "initial_da_1"
27
+ initial_da_value_1 = "value_1"
28
+ initial_da_2 = "initial_da_2"
29
+ initial_da_value_2 = "value_2"
30
+
31
+ test_da_1 = "test_da_1"
32
+ test_da_2 = "test_da_2"
33
+
34
+ final_test_da_value_1 = "1234"
35
+ final_test_da_value_2 = 1234
36
+ final_initial_da_value_1 = initial_da_value_1
37
+ final_initial_da_value_2 = "no-more-init"
38
+
39
+ test_da_prefix = "test-da-prefix"
40
+ test_da_prefix_key_1 = "test-da-prefix-1"
41
+ test_da_prefix_key_2 = "test-da-prefix-2"
42
+ test_da_prefix_value_1 = "test-da-value-1"
43
+ test_da_prefix_value_2 = "test-da-value-2"
44
+ test_da_set_key = "test_da_set_key"
45
+ test_da_set_value = "test_da_set_value"
46
+
47
+ expected_final_das = {
48
+ initial_da_1: initial_da_value_1,
49
+ initial_da_2: final_initial_da_value_2,
50
+ test_da_1: final_test_da_value_1,
51
+ test_da_2: final_test_da_value_2,
52
+ test_da_prefix_key_1: test_da_prefix_value_1,
53
+ test_da_prefix_key_2: test_da_prefix_value_2,
54
+ test_da_set_key: test_da_set_value,
55
+ }
56
+
57
+
58
+ class DataAttributeRWState(WorkflowState[None]):
59
+ def wait_until(
60
+ self,
61
+ ctx: WorkflowContext,
62
+ input: T,
63
+ persistence: Persistence,
64
+ communication: Communication,
65
+ ) -> CommandRequest:
66
+ persistence.set_data_attribute(test_da_1, "123")
67
+ persistence.set_data_attribute(test_da_2, 123)
68
+
69
+ return CommandRequest.empty()
70
+
71
+ def execute(
72
+ self,
73
+ ctx: WorkflowContext,
74
+ input: T,
75
+ command_results: CommandResults,
76
+ persistence: Persistence,
77
+ communication: Communication,
78
+ ) -> StateDecision:
79
+ da1 = persistence.get_data_attribute(test_da_1)
80
+ da2 = persistence.get_data_attribute(test_da_2)
81
+ assert da1 == "123"
82
+ assert da2 == 123
83
+
84
+ persistence.set_data_attribute(test_da_1, final_test_da_value_1)
85
+ persistence.set_data_attribute(test_da_2, final_test_da_value_2)
86
+ persistence.set_data_attribute(initial_da_2, final_initial_da_value_2)
87
+ persistence.set_data_attribute(test_da_prefix_key_1, test_da_prefix_value_1)
88
+ persistence.set_data_attribute(test_da_prefix_key_2, test_da_prefix_value_2)
89
+ return StateDecision.graceful_complete_workflow()
90
+
91
+
92
+ class PersistenceDataAttributesWorkflow(ObjectWorkflow):
93
+ def get_workflow_states(self) -> StateSchema:
94
+ return StateSchema.with_starting_state(DataAttributeRWState())
95
+
96
+ def get_persistence_schema(self) -> PersistenceSchema:
97
+ return PersistenceSchema.create(
98
+ PersistenceField.data_attribute_def(initial_da_1, str),
99
+ PersistenceField.data_attribute_def(initial_da_2, str),
100
+ PersistenceField.data_attribute_def(test_da_1, str),
101
+ PersistenceField.data_attribute_def(test_da_2, int),
102
+ PersistenceField.data_attribute_prefix_def(test_da_prefix, str),
103
+ PersistenceField.data_attribute_def(test_da_set_key, str),
104
+ )
@@ -0,0 +1,167 @@
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 time import sleep
16
+
17
+ from dex.command_request import CommandRequest, TimerCommand
18
+ from dex.command_results import CommandResults
19
+ from dex.communication import Communication
20
+ from dex.dex_api.models import SearchAttributeValueType
21
+ from dex.persistence import Persistence
22
+ from dex.persistence_schema import PersistenceField, PersistenceSchema
23
+ from dex.state_decision import StateDecision
24
+ from dex.state_schema import StateSchema
25
+ from dex.workflow import ObjectWorkflow
26
+ from dex.workflow_context import WorkflowContext
27
+ from dex.workflow_state import T, WorkflowState
28
+
29
+ sa_keyword_key = "CustomKeywordField"
30
+ sa_double_key = "CustomDoubleField"
31
+ sa_int_key = "CustomIntField"
32
+ sa_bool_key = "CustomBoolField"
33
+ sa_datetime_key = "CustomDatetimeField"
34
+ sa_keyword_array_key = "CustomKeywordArrayField"
35
+
36
+ initial_sa_keyword: str = "initial_keyword"
37
+ initial_sa_double: float = 1.11
38
+ initial_sa_int: int = 1
39
+ initial_sa_bool: bool = True
40
+ initial_sa_datetime: str = "2024-11-09T16:00:01.731455544-08:00"
41
+ initial_sa_keyword_array: list[str] = ["initial_keyword-1", "initial_keyword-2"]
42
+
43
+ sa_keyword: str = "keyword"
44
+ sa_double: float = 2.34
45
+ sa_int: int = 234
46
+ sa_bool: bool = False
47
+ sa_datetime: str = "2024-11-12T16:00:01.731455544-08:00"
48
+ sa_keyword_array: list[str] = ["keyword-1", "keyword-2"]
49
+
50
+ final_sa_keyword: str = "final_keyword"
51
+ final_sa_int: int = 567
52
+ final_sa_bool: bool = False
53
+ final_sa_datetime: str = "2024-12-13T16:00:01.731455544-08:00"
54
+ final_sa_keyword_array: list[str] = ["final_keyword-1", "final_keyword-2"]
55
+
56
+
57
+ class SearchAttributeStateInit(WorkflowState[None]):
58
+ def wait_until(
59
+ self,
60
+ ctx: WorkflowContext,
61
+ input: T,
62
+ persistence: Persistence,
63
+ communication: Communication,
64
+ ) -> CommandRequest:
65
+ return CommandRequest.for_all_command_completed(
66
+ TimerCommand.by_seconds(2),
67
+ )
68
+
69
+ def execute(
70
+ self,
71
+ ctx: WorkflowContext,
72
+ input: T,
73
+ command_results: CommandResults,
74
+ persistence: Persistence,
75
+ communication: Communication,
76
+ ) -> StateDecision:
77
+ persistence.set_search_attribute_keyword(sa_keyword_key, sa_keyword)
78
+ persistence.set_search_attribute_double(sa_double_key, sa_double)
79
+ persistence.set_search_attribute_boolean(sa_bool_key, sa_bool)
80
+ persistence.set_search_attribute_keyword_array(
81
+ sa_keyword_array_key, sa_keyword_array
82
+ )
83
+ persistence.set_search_attribute_int64(sa_int_key, sa_int)
84
+ persistence.set_search_attribute_datetime(sa_datetime_key, sa_datetime)
85
+ return StateDecision.single_next_state(SearchAttributeState1)
86
+
87
+
88
+ class SearchAttributeState1(WorkflowState[None]):
89
+ def wait_until(
90
+ self,
91
+ ctx: WorkflowContext,
92
+ input: T,
93
+ persistence: Persistence,
94
+ communication: Communication,
95
+ ) -> CommandRequest:
96
+ return CommandRequest.for_all_command_completed(
97
+ TimerCommand.by_seconds(2),
98
+ )
99
+
100
+ def execute(
101
+ self,
102
+ ctx: WorkflowContext,
103
+ input: T,
104
+ command_results: CommandResults,
105
+ persistence: Persistence,
106
+ communication: Communication,
107
+ ) -> StateDecision:
108
+ return StateDecision.single_next_state(SearchAttributeState2)
109
+
110
+
111
+ class SearchAttributeState2(WorkflowState[None]):
112
+ def wait_until(
113
+ self,
114
+ ctx: WorkflowContext,
115
+ input: T,
116
+ persistence: Persistence,
117
+ communication: Communication,
118
+ ) -> CommandRequest:
119
+ return CommandRequest.empty()
120
+
121
+ def execute(
122
+ self,
123
+ ctx: WorkflowContext,
124
+ input: T,
125
+ command_results: CommandResults,
126
+ persistence: Persistence,
127
+ communication: Communication,
128
+ ) -> StateDecision:
129
+ # Delay updating search attributes to allow for the previous assertion
130
+ sleep(1)
131
+ persistence.set_search_attribute_keyword(sa_keyword_key, final_sa_keyword)
132
+ persistence.set_search_attribute_boolean(sa_bool_key, final_sa_bool)
133
+ persistence.set_search_attribute_keyword_array(
134
+ sa_keyword_array_key, final_sa_keyword_array
135
+ )
136
+ persistence.set_search_attribute_int64(sa_int_key, final_sa_int)
137
+ persistence.set_search_attribute_datetime(sa_datetime_key, final_sa_datetime)
138
+ return StateDecision.graceful_complete_workflow()
139
+
140
+
141
+ class PersistenceSearchAttributesWorkflow(ObjectWorkflow):
142
+ def get_workflow_states(self) -> StateSchema:
143
+ return StateSchema.with_starting_state(
144
+ SearchAttributeStateInit(), SearchAttributeState1(), SearchAttributeState2()
145
+ )
146
+
147
+ def get_persistence_schema(self) -> PersistenceSchema:
148
+ return PersistenceSchema.create(
149
+ PersistenceField.search_attribute_def(
150
+ sa_keyword_key, SearchAttributeValueType.KEYWORD
151
+ ),
152
+ PersistenceField.search_attribute_def(
153
+ sa_double_key, SearchAttributeValueType.DOUBLE
154
+ ),
155
+ PersistenceField.search_attribute_def(
156
+ sa_bool_key, SearchAttributeValueType.BOOL
157
+ ),
158
+ PersistenceField.search_attribute_def(
159
+ sa_keyword_array_key, SearchAttributeValueType.KEYWORD_ARRAY
160
+ ),
161
+ PersistenceField.search_attribute_def(
162
+ sa_int_key, SearchAttributeValueType.INT
163
+ ),
164
+ PersistenceField.search_attribute_def(
165
+ sa_datetime_key, SearchAttributeValueType.DATETIME
166
+ ),
167
+ )
@@ -0,0 +1,69 @@
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.rpc import rpc
16
+ from dex.command_request import CommandRequest
17
+ from dex.command_results import CommandResults
18
+ from dex.communication import Communication
19
+ from dex.persistence import Persistence
20
+ from dex.persistence_schema import PersistenceField, PersistenceSchema
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
+
27
+ PERSISTENCE_LOCAL_KEY = "persistence-test-key"
28
+ PERSISTENCE_LOCAL_VALUE = "persistence-test-value"
29
+ PERSISTENCE_DATA_ATTRIBUTE_KEY = "persistence-data-attribute-key"
30
+
31
+
32
+ class PersistenceStateExecutionLocalRWState(WorkflowState[None]):
33
+ def wait_until(
34
+ self,
35
+ ctx: WorkflowContext,
36
+ input: T,
37
+ persistence: Persistence,
38
+ communication: Communication,
39
+ ):
40
+ persistence.set_state_execution_local(
41
+ PERSISTENCE_LOCAL_KEY, PERSISTENCE_LOCAL_VALUE
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
+ ):
53
+ value = persistence.get_state_execution_local(PERSISTENCE_LOCAL_KEY)
54
+ persistence.set_data_attribute(PERSISTENCE_DATA_ATTRIBUTE_KEY, value)
55
+ return StateDecision.graceful_complete_workflow()
56
+
57
+
58
+ class PersistenceStateExecutionLocalWorkflow(ObjectWorkflow):
59
+ def get_workflow_states(self) -> StateSchema:
60
+ return StateSchema.with_starting_state(PersistenceStateExecutionLocalRWState())
61
+
62
+ def get_persistence_schema(self) -> PersistenceSchema:
63
+ return PersistenceSchema.create(
64
+ PersistenceField.data_attribute_def(PERSISTENCE_DATA_ATTRIBUTE_KEY, str)
65
+ )
66
+
67
+ @rpc()
68
+ def test_persistence_read(self, persistence: Persistence):
69
+ return persistence.get_data_attribute(PERSISTENCE_DATA_ATTRIBUTE_KEY)