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,100 @@
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
+ from dex.command_request import CommandRequest, InternalChannelCommand
12
+ from dex.command_results import CommandResults
13
+ from dex.communication import Communication
14
+ from dex.communication_schema import CommunicationMethod, CommunicationSchema
15
+ from dex.persistence import Persistence
16
+ from dex.state_decision import StateDecision
17
+ from dex.state_schema import StateSchema
18
+ from dex.workflow import ObjectWorkflow
19
+ from dex.workflow_context import WorkflowContext
20
+ from dex.workflow_state import T, WorkflowState
21
+
22
+ internal_channel_name = "internal-channel-1"
23
+
24
+ test_non_prefix_channel_name = "test-channel-"
25
+ test_non_prefix_channel_name_with_suffix = test_non_prefix_channel_name + "abc"
26
+
27
+ class InitState(WorkflowState[None]):
28
+ def execute(
29
+ self,
30
+ ctx: WorkflowContext,
31
+ input: T,
32
+ command_results: CommandResults,
33
+ persistence: Persistence,
34
+ communication: Communication,
35
+ ) -> StateDecision:
36
+ return StateDecision.multi_next_states(
37
+ WaitAnyWithPublishState, WaitAllThenPublishState
38
+ )
39
+
40
+ class WaitAnyWithPublishState(WorkflowState[None]):
41
+ def wait_until(
42
+ self,
43
+ ctx: WorkflowContext,
44
+ input: T,
45
+ persistence: Persistence,
46
+ communication: Communication,
47
+ ) -> CommandRequest:
48
+ # Trying to publish to a non-existing channel; this would only work if test_channel_name_non_prefix was defined as a prefix channel
49
+ communication.publish_to_internal_channel(
50
+ test_non_prefix_channel_name_with_suffix, "str-value-for-prefix"
51
+ )
52
+ return CommandRequest.for_any_command_completed(
53
+ InternalChannelCommand.by_name(internal_channel_name),
54
+ )
55
+
56
+ def execute(
57
+ self,
58
+ ctx: WorkflowContext,
59
+ input: T,
60
+ command_results: CommandResults,
61
+ persistence: Persistence,
62
+ communication: Communication,
63
+ ) -> StateDecision:
64
+ return StateDecision.graceful_complete_workflow()
65
+
66
+ class WaitAllThenPublishState(WorkflowState[None]):
67
+ def wait_until(
68
+ self,
69
+ ctx: WorkflowContext,
70
+ input: T,
71
+ persistence: Persistence,
72
+ communication: Communication,
73
+ ) -> CommandRequest:
74
+ return CommandRequest.for_all_command_completed(
75
+ InternalChannelCommand.by_name(test_non_prefix_channel_name),
76
+ )
77
+
78
+ def execute(
79
+ self,
80
+ ctx: WorkflowContext,
81
+ input: T,
82
+ command_results: CommandResults,
83
+ persistence: Persistence,
84
+ communication: Communication,
85
+ ) -> StateDecision:
86
+ communication.publish_to_internal_channel(internal_channel_name, None)
87
+ return StateDecision.dead_end
88
+
89
+ class InternalChannelWorkflowWithNoPrefixChannel(ObjectWorkflow):
90
+ def get_workflow_states(self) -> StateSchema:
91
+ return StateSchema.with_starting_state(
92
+ InitState(), WaitAnyWithPublishState(), WaitAllThenPublishState()
93
+ )
94
+
95
+ def get_communication_schema(self) -> CommunicationSchema:
96
+ return CommunicationSchema.create(
97
+ CommunicationMethod.internal_channel_def(internal_channel_name, None),
98
+ # Defining a standard channel (non-prefix) to make sure messages to the channel with a suffix added will not be accepted
99
+ CommunicationMethod.internal_channel_def(test_non_prefix_channel_name, str),
100
+ )
@@ -0,0 +1,276 @@
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
+ from typing import Optional
12
+
13
+ from dex.command_request import CommandRequest, InternalChannelCommand
14
+ from dex.command_results import CommandResults
15
+ from dex.communication import Communication
16
+ from dex.communication_schema import CommunicationMethod, CommunicationSchema
17
+ from dex.dex_api.models.search_attribute_value_type import SearchAttributeValueType
18
+ from dex.persistence import Persistence
19
+ from dex.persistence_options import PersistenceOptions
20
+ from dex.persistence_schema import PersistenceField, PersistenceSchema
21
+ from dex.rpc import rpc
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 WorkflowState
27
+
28
+ """
29
+ NOTE: This workflow and its tests were translated from dex-java-sdk, but currently, there is a
30
+ discrepency between the sdks in their handling of empty graceful complete workflow
31
+ messages. These tests will not pass, so they are curretly being skipped. They were added
32
+ here for posterity.
33
+ """
34
+
35
+ INTERNAL_CHANNEL_NAME = "test-channel-1"
36
+ TEST_DATA_OBJECT_KEY = "data-obj-1"
37
+ TEST_SEARCH_ATTRIBUTE_KEY = "CustomKeywordField"
38
+ TEST_SEARCH_ATTRIBUTE_INT = "CustomIntField"
39
+ RPC_INPUT = "rpc-input"
40
+ RPC_OUTPUT = 100
41
+ TEST_STR = "test-str"
42
+ TEST_DELAY = 0.1
43
+
44
+ class JavaDuplicateRpcMemoWorkflow(ObjectWorkflow):
45
+ def get_workflow_states(self) -> StateSchema:
46
+ return StateSchema.with_starting_state(
47
+ RpcMemoWorkflowState1(), RpcMemoWorkflowState2()
48
+ )
49
+
50
+ def get_persistence_schema(self) -> PersistenceSchema:
51
+ return PersistenceSchema.create(
52
+ PersistenceField.data_attribute_def(TEST_DATA_OBJECT_KEY, str),
53
+ PersistenceField.search_attribute_def(
54
+ TEST_SEARCH_ATTRIBUTE_INT, SearchAttributeValueType.INT
55
+ ),
56
+ PersistenceField.search_attribute_def(
57
+ TEST_SEARCH_ATTRIBUTE_KEY, SearchAttributeValueType.KEYWORD
58
+ ),
59
+ )
60
+
61
+ def get_communication_schema(self) -> CommunicationSchema:
62
+ return CommunicationSchema.create(
63
+ CommunicationMethod.internal_channel_def(INTERNAL_CHANNEL_NAME, None)
64
+ )
65
+
66
+ def get_persistence_options(self) -> PersistenceOptions:
67
+ return PersistenceOptions(enable_caching=True)
68
+
69
+ @rpc()
70
+ def test_rpc_func1(
71
+ self,
72
+ ctx: WorkflowContext,
73
+ input: str,
74
+ persistence: Persistence,
75
+ communication: Communication,
76
+ ) -> int:
77
+ if not ctx.workflow_id or not ctx.workflow_run_id:
78
+ raise RuntimeError("invalid context")
79
+
80
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, None)
81
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, input)
82
+ persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, input)
83
+ persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
84
+ communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
85
+ communication.trigger_state_execution(RpcMemoWorkflowState2)
86
+ return RPC_OUTPUT
87
+
88
+ @rpc()
89
+ def test_rpc_func0(
90
+ self,
91
+ ctx: WorkflowContext,
92
+ input: str,
93
+ persistence: Persistence,
94
+ communication: Communication,
95
+ ) -> int:
96
+ if not ctx.workflow_id or not ctx.workflow_run_id:
97
+ raise RuntimeError("invalid context")
98
+
99
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, TEST_STR)
100
+ persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, TEST_STR)
101
+ persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
102
+ communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
103
+ communication.trigger_state_execution(RpcMemoWorkflowState2)
104
+ return RPC_OUTPUT
105
+
106
+ @rpc()
107
+ def test_rpc_proc1(
108
+ self,
109
+ ctx: WorkflowContext,
110
+ input: str,
111
+ persistence: Persistence,
112
+ communication: Communication,
113
+ ):
114
+ if not ctx.workflow_id or not ctx.workflow_run_id:
115
+ raise RuntimeError("invalid context")
116
+
117
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, input)
118
+ persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, input)
119
+ persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
120
+ communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
121
+ communication.trigger_state_execution(RpcMemoWorkflowState2)
122
+
123
+ @rpc()
124
+ def test_rpc_proc0(
125
+ self,
126
+ ctx: WorkflowContext,
127
+ input: str,
128
+ persistence: Persistence,
129
+ communication: Communication,
130
+ ):
131
+ if not ctx.workflow_id or not ctx.workflow_run_id:
132
+ raise RuntimeError("invalid context")
133
+
134
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, TEST_STR)
135
+ persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, TEST_STR)
136
+ persistence.set_search_attribute_int64(TEST_SEARCH_ATTRIBUTE_INT, RPC_OUTPUT)
137
+ communication.publish_to_internal_channel(INTERNAL_CHANNEL_NAME, None)
138
+ communication.trigger_state_execution(RpcMemoWorkflowState2)
139
+
140
+ @rpc()
141
+ def test_rpc_func1_readonly(
142
+ self,
143
+ ctx: WorkflowContext,
144
+ input: str,
145
+ persistence: Persistence,
146
+ communication: Communication,
147
+ ) -> int:
148
+ if not ctx.workflow_id or not ctx.workflow_run_id:
149
+ raise RuntimeError("invalid context")
150
+ return RPC_OUTPUT
151
+
152
+ @rpc()
153
+ def test_rpc_set_data_attribute(
154
+ self,
155
+ ctx: WorkflowContext,
156
+ input: str,
157
+ persistence: Persistence,
158
+ communication: Communication,
159
+ ):
160
+ if not ctx.workflow_id or not ctx.workflow_run_id:
161
+ raise RuntimeError("invalid context")
162
+ persistence.set_data_attribute(TEST_DATA_OBJECT_KEY, input)
163
+
164
+ @rpc()
165
+ def test_rpc_get_data_attribute(
166
+ self,
167
+ ctx: WorkflowContext,
168
+ input: str,
169
+ persistence: Persistence,
170
+ communication: Communication,
171
+ ) -> str:
172
+ if not ctx.workflow_id or not ctx.workflow_run_id:
173
+ raise RuntimeError("invalid context")
174
+ return persistence.get_data_attribute(TEST_DATA_OBJECT_KEY)
175
+
176
+ @rpc(bypass_caching_for_strong_consistency=True)
177
+ def test_rpc_get_data_attribute_strong_consistency(
178
+ self,
179
+ ctx: WorkflowContext,
180
+ input: str,
181
+ persistence: Persistence,
182
+ communication: Communication,
183
+ ) -> str:
184
+ if not ctx.workflow_id or not ctx.workflow_run_id:
185
+ raise RuntimeError("invalid context")
186
+ return persistence.get_data_attribute(TEST_DATA_OBJECT_KEY)
187
+
188
+ @rpc()
189
+ def test_rpc_set_keyword(
190
+ self,
191
+ ctx: WorkflowContext,
192
+ input: str,
193
+ persistence: Persistence,
194
+ communication: Communication,
195
+ ):
196
+ if not ctx.workflow_id or not ctx.workflow_run_id:
197
+ raise RuntimeError("invalid context")
198
+ persistence.set_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY, input)
199
+
200
+ @rpc(bypass_caching_for_strong_consistency=True)
201
+ def test_rpc_get_keyword_strong_consistency(
202
+ self,
203
+ ctx: WorkflowContext,
204
+ input: str,
205
+ persistence: Persistence,
206
+ communication: Communication,
207
+ ) -> Optional[str]:
208
+ if not ctx.workflow_id or not ctx.workflow_run_id:
209
+ raise RuntimeError("invalid context")
210
+ return persistence.get_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY)
211
+
212
+ @rpc()
213
+ def test_rpc_get_keyword(
214
+ self,
215
+ ctx: WorkflowContext,
216
+ input: str,
217
+ persistence: Persistence,
218
+ communication: Communication,
219
+ ) -> Optional[str]:
220
+ if not ctx.workflow_id or not ctx.workflow_run_id:
221
+ raise RuntimeError("invalid context")
222
+ return persistence.get_search_attribute_keyword(TEST_SEARCH_ATTRIBUTE_KEY)
223
+
224
+ class RpcMemoWorkflowState1(WorkflowState[int]):
225
+ def wait_until(
226
+ self,
227
+ ctx: WorkflowContext,
228
+ input: int,
229
+ persistence: Persistence,
230
+ communication: Communication,
231
+ ) -> CommandRequest:
232
+ return CommandRequest.for_any_command_completed(
233
+ InternalChannelCommand.by_name(INTERNAL_CHANNEL_NAME)
234
+ )
235
+
236
+ def execute(
237
+ self,
238
+ ctx: WorkflowContext,
239
+ input: int,
240
+ command_results: CommandResults,
241
+ persistence: Persistence,
242
+ communication: Communication,
243
+ ) -> StateDecision:
244
+ return StateDecision.single_next_state(RpcMemoWorkflowState2, 0)
245
+
246
+ class RpcMemoWorkflowState2(WorkflowState[int]):
247
+ _counter: int = 0
248
+
249
+ def wait_until(
250
+ self,
251
+ ctx: WorkflowContext,
252
+ input: int,
253
+ persistence: Persistence,
254
+ communication: Communication,
255
+ ) -> CommandRequest:
256
+ return CommandRequest.empty()
257
+
258
+ def execute(
259
+ self,
260
+ ctx: WorkflowContext,
261
+ input: int,
262
+ command_results: CommandResults,
263
+ persistence: Persistence,
264
+ communication: Communication,
265
+ ) -> StateDecision:
266
+ self._counter += 1
267
+ if self._counter == 2:
268
+ return StateDecision.graceful_complete_workflow(self._counter)
269
+ else:
270
+ return StateDecision.graceful_complete_workflow()
271
+
272
+ @classmethod
273
+ def reset_counter(cls) -> int:
274
+ old = cls._counter
275
+ cls._counter = 0
276
+ return old
@@ -0,0 +1,98 @@
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
+ from dex.command_request import CommandRequest
12
+ from dex.command_results import CommandResults
13
+ from dex.communication import Communication
14
+ from dex.persistence import Persistence
15
+ from dex.persistence_schema import PersistenceField, PersistenceSchema
16
+ from dex.state_decision import StateDecision
17
+ from dex.state_schema import StateSchema
18
+ from dex.workflow import ObjectWorkflow
19
+ from dex.workflow_context import WorkflowContext
20
+ from dex.workflow_state import T, WorkflowState
21
+
22
+ initial_da_1 = "initial_da_1"
23
+ initial_da_value_1 = "value_1"
24
+ initial_da_2 = "initial_da_2"
25
+ initial_da_value_2 = "value_2"
26
+
27
+ test_da_1 = "test_da_1"
28
+ test_da_2 = "test_da_2"
29
+
30
+ final_test_da_value_1 = "1234"
31
+ final_test_da_value_2 = 1234
32
+ final_initial_da_value_1 = initial_da_value_1
33
+ final_initial_da_value_2 = "no-more-init"
34
+
35
+ test_da_prefix = "test-da-prefix"
36
+ test_da_prefix_key_1 = "test-da-prefix-1"
37
+ test_da_prefix_key_2 = "test-da-prefix-2"
38
+ test_da_prefix_value_1 = "test-da-value-1"
39
+ test_da_prefix_value_2 = "test-da-value-2"
40
+ test_da_set_key = "test_da_set_key"
41
+ test_da_set_value = "test_da_set_value"
42
+
43
+ expected_final_das = {
44
+ initial_da_1: initial_da_value_1,
45
+ initial_da_2: final_initial_da_value_2,
46
+ test_da_1: final_test_da_value_1,
47
+ test_da_2: final_test_da_value_2,
48
+ test_da_prefix_key_1: test_da_prefix_value_1,
49
+ test_da_prefix_key_2: test_da_prefix_value_2,
50
+ test_da_set_key: test_da_set_value,
51
+ }
52
+
53
+ class DataAttributeRWState(WorkflowState[None]):
54
+ def wait_until(
55
+ self,
56
+ ctx: WorkflowContext,
57
+ input: T,
58
+ persistence: Persistence,
59
+ communication: Communication,
60
+ ) -> CommandRequest:
61
+ persistence.set_data_attribute(test_da_1, "123")
62
+ persistence.set_data_attribute(test_da_2, 123)
63
+
64
+ return CommandRequest.empty()
65
+
66
+ def execute(
67
+ self,
68
+ ctx: WorkflowContext,
69
+ input: T,
70
+ command_results: CommandResults,
71
+ persistence: Persistence,
72
+ communication: Communication,
73
+ ) -> StateDecision:
74
+ da1 = persistence.get_data_attribute(test_da_1)
75
+ da2 = persistence.get_data_attribute(test_da_2)
76
+ assert da1 == "123"
77
+ assert da2 == 123
78
+
79
+ persistence.set_data_attribute(test_da_1, final_test_da_value_1)
80
+ persistence.set_data_attribute(test_da_2, final_test_da_value_2)
81
+ persistence.set_data_attribute(initial_da_2, final_initial_da_value_2)
82
+ persistence.set_data_attribute(test_da_prefix_key_1, test_da_prefix_value_1)
83
+ persistence.set_data_attribute(test_da_prefix_key_2, test_da_prefix_value_2)
84
+ return StateDecision.graceful_complete_workflow()
85
+
86
+ class PersistenceDataAttributesWorkflow(ObjectWorkflow):
87
+ def get_workflow_states(self) -> StateSchema:
88
+ return StateSchema.with_starting_state(DataAttributeRWState())
89
+
90
+ def get_persistence_schema(self) -> PersistenceSchema:
91
+ return PersistenceSchema.create(
92
+ PersistenceField.data_attribute_def(initial_da_1, str),
93
+ PersistenceField.data_attribute_def(initial_da_2, str),
94
+ PersistenceField.data_attribute_def(test_da_1, str),
95
+ PersistenceField.data_attribute_def(test_da_2, int),
96
+ PersistenceField.data_attribute_prefix_def(test_da_prefix, str),
97
+ PersistenceField.data_attribute_def(test_da_set_key, str),
98
+ )
@@ -0,0 +1,159 @@
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
+ from time import sleep
12
+
13
+ from dex.command_request import CommandRequest, TimerCommand
14
+ from dex.command_results import CommandResults
15
+ from dex.communication import Communication
16
+ from dex.dex_api.models import SearchAttributeValueType
17
+ from dex.persistence import Persistence
18
+ from dex.persistence_schema import PersistenceField, PersistenceSchema
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
+ sa_keyword_key = "CustomKeywordField"
26
+ sa_double_key = "CustomDoubleField"
27
+ sa_int_key = "CustomIntField"
28
+ sa_bool_key = "CustomBoolField"
29
+ sa_datetime_key = "CustomDatetimeField"
30
+ sa_keyword_array_key = "CustomKeywordArrayField"
31
+
32
+ initial_sa_keyword: str = "initial_keyword"
33
+ initial_sa_double: float = 1.11
34
+ initial_sa_int: int = 1
35
+ initial_sa_bool: bool = True
36
+ initial_sa_datetime: str = "2024-11-09T16:00:01.731455544-08:00"
37
+ initial_sa_keyword_array: list[str] = ["initial_keyword-1", "initial_keyword-2"]
38
+
39
+ sa_keyword: str = "keyword"
40
+ sa_double: float = 2.34
41
+ sa_int: int = 234
42
+ sa_bool: bool = False
43
+ sa_datetime: str = "2024-11-12T16:00:01.731455544-08:00"
44
+ sa_keyword_array: list[str] = ["keyword-1", "keyword-2"]
45
+
46
+ final_sa_keyword: str = "final_keyword"
47
+ final_sa_int: int = 567
48
+ final_sa_bool: bool = False
49
+ final_sa_datetime: str = "2024-12-13T16:00:01.731455544-08:00"
50
+ final_sa_keyword_array: list[str] = ["final_keyword-1", "final_keyword-2"]
51
+
52
+ class SearchAttributeStateInit(WorkflowState[None]):
53
+ def wait_until(
54
+ self,
55
+ ctx: WorkflowContext,
56
+ input: T,
57
+ persistence: Persistence,
58
+ communication: Communication,
59
+ ) -> CommandRequest:
60
+ return CommandRequest.for_all_command_completed(
61
+ TimerCommand.by_seconds(2),
62
+ )
63
+
64
+ def execute(
65
+ self,
66
+ ctx: WorkflowContext,
67
+ input: T,
68
+ command_results: CommandResults,
69
+ persistence: Persistence,
70
+ communication: Communication,
71
+ ) -> StateDecision:
72
+ persistence.set_search_attribute_keyword(sa_keyword_key, sa_keyword)
73
+ persistence.set_search_attribute_double(sa_double_key, sa_double)
74
+ persistence.set_search_attribute_boolean(sa_bool_key, sa_bool)
75
+ persistence.set_search_attribute_keyword_array(
76
+ sa_keyword_array_key, sa_keyword_array
77
+ )
78
+ persistence.set_search_attribute_int64(sa_int_key, sa_int)
79
+ persistence.set_search_attribute_datetime(sa_datetime_key, sa_datetime)
80
+ return StateDecision.single_next_state(SearchAttributeState1)
81
+
82
+ class SearchAttributeState1(WorkflowState[None]):
83
+ def wait_until(
84
+ self,
85
+ ctx: WorkflowContext,
86
+ input: T,
87
+ persistence: Persistence,
88
+ communication: Communication,
89
+ ) -> CommandRequest:
90
+ return CommandRequest.for_all_command_completed(
91
+ TimerCommand.by_seconds(2),
92
+ )
93
+
94
+ def execute(
95
+ self,
96
+ ctx: WorkflowContext,
97
+ input: T,
98
+ command_results: CommandResults,
99
+ persistence: Persistence,
100
+ communication: Communication,
101
+ ) -> StateDecision:
102
+ return StateDecision.single_next_state(SearchAttributeState2)
103
+
104
+ class SearchAttributeState2(WorkflowState[None]):
105
+ def wait_until(
106
+ self,
107
+ ctx: WorkflowContext,
108
+ input: T,
109
+ persistence: Persistence,
110
+ communication: Communication,
111
+ ) -> CommandRequest:
112
+ return CommandRequest.empty()
113
+
114
+ def execute(
115
+ self,
116
+ ctx: WorkflowContext,
117
+ input: T,
118
+ command_results: CommandResults,
119
+ persistence: Persistence,
120
+ communication: Communication,
121
+ ) -> StateDecision:
122
+ # Delay updating search attributes to allow for the previous assertion
123
+ sleep(1)
124
+ persistence.set_search_attribute_keyword(sa_keyword_key, final_sa_keyword)
125
+ persistence.set_search_attribute_boolean(sa_bool_key, final_sa_bool)
126
+ persistence.set_search_attribute_keyword_array(
127
+ sa_keyword_array_key, final_sa_keyword_array
128
+ )
129
+ persistence.set_search_attribute_int64(sa_int_key, final_sa_int)
130
+ persistence.set_search_attribute_datetime(sa_datetime_key, final_sa_datetime)
131
+ return StateDecision.graceful_complete_workflow()
132
+
133
+ class PersistenceSearchAttributesWorkflow(ObjectWorkflow):
134
+ def get_workflow_states(self) -> StateSchema:
135
+ return StateSchema.with_starting_state(
136
+ SearchAttributeStateInit(), SearchAttributeState1(), SearchAttributeState2()
137
+ )
138
+
139
+ def get_persistence_schema(self) -> PersistenceSchema:
140
+ return PersistenceSchema.create(
141
+ PersistenceField.search_attribute_def(
142
+ sa_keyword_key, SearchAttributeValueType.KEYWORD
143
+ ),
144
+ PersistenceField.search_attribute_def(
145
+ sa_double_key, SearchAttributeValueType.DOUBLE
146
+ ),
147
+ PersistenceField.search_attribute_def(
148
+ sa_bool_key, SearchAttributeValueType.BOOL
149
+ ),
150
+ PersistenceField.search_attribute_def(
151
+ sa_keyword_array_key, SearchAttributeValueType.KEYWORD_ARRAY
152
+ ),
153
+ PersistenceField.search_attribute_def(
154
+ sa_int_key, SearchAttributeValueType.INT
155
+ ),
156
+ PersistenceField.search_attribute_def(
157
+ sa_datetime_key, SearchAttributeValueType.DATETIME
158
+ ),
159
+ )
@@ -0,0 +1,63 @@
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
+ from dex.rpc import rpc
12
+ from dex.command_request import CommandRequest
13
+ from dex.command_results import CommandResults
14
+ from dex.communication import Communication
15
+ from dex.persistence import Persistence
16
+ from dex.persistence_schema import PersistenceField, PersistenceSchema
17
+ from dex.state_decision import StateDecision
18
+ from dex.state_schema import StateSchema
19
+ from dex.workflow import ObjectWorkflow
20
+ from dex.workflow_context import WorkflowContext
21
+ from dex.workflow_state import T, WorkflowState
22
+
23
+ PERSISTENCE_LOCAL_KEY = "persistence-test-key"
24
+ PERSISTENCE_LOCAL_VALUE = "persistence-test-value"
25
+ PERSISTENCE_DATA_ATTRIBUTE_KEY = "persistence-data-attribute-key"
26
+
27
+ class PersistenceStateExecutionLocalRWState(WorkflowState[None]):
28
+ def wait_until(
29
+ self,
30
+ ctx: WorkflowContext,
31
+ input: T,
32
+ persistence: Persistence,
33
+ communication: Communication,
34
+ ):
35
+ persistence.set_state_execution_local(
36
+ PERSISTENCE_LOCAL_KEY, PERSISTENCE_LOCAL_VALUE
37
+ )
38
+ return CommandRequest.empty()
39
+
40
+ def execute(
41
+ self,
42
+ ctx: WorkflowContext,
43
+ input: T,
44
+ command_results: CommandResults,
45
+ persistence: Persistence,
46
+ communication: Communication,
47
+ ):
48
+ value = persistence.get_state_execution_local(PERSISTENCE_LOCAL_KEY)
49
+ persistence.set_data_attribute(PERSISTENCE_DATA_ATTRIBUTE_KEY, value)
50
+ return StateDecision.graceful_complete_workflow()
51
+
52
+ class PersistenceStateExecutionLocalWorkflow(ObjectWorkflow):
53
+ def get_workflow_states(self) -> StateSchema:
54
+ return StateSchema.with_starting_state(PersistenceStateExecutionLocalRWState())
55
+
56
+ def get_persistence_schema(self) -> PersistenceSchema:
57
+ return PersistenceSchema.create(
58
+ PersistenceField.data_attribute_def(PERSISTENCE_DATA_ATTRIBUTE_KEY, str)
59
+ )
60
+
61
+ @rpc()
62
+ def test_persistence_read(self, persistence: Persistence):
63
+ return persistence.get_data_attribute(PERSISTENCE_DATA_ATTRIBUTE_KEY)