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,118 @@
1
+ # Legacy Materials in this file remain under their original licenses.
2
+ # See LEGACY_NOTICES.md.
3
+
4
+ # Modifications Copyright (c) 2026 Super Durable, Inc.
5
+ #
6
+ # Modifications after the Legacy Cutoff are licensed under the
7
+ # Super Durable Source License 1.0.
8
+ # Legacy Materials remain under their original licenses.
9
+ # See LICENSE and LEGACY_NOTICES.md.
10
+
11
+ import inspect
12
+ import time
13
+ import unittest
14
+
15
+ from dex.client import Client
16
+ from dex.dex_api.models import IDReusePolicy
17
+ from dex.dex_api.models import (
18
+ PersistenceLoadingPolicy,
19
+ PersistenceLoadingType,
20
+ WorkflowStateOptions as IdlWorkflowStateOptions,
21
+ RetryPolicy,
22
+ WaitUntilApiFailurePolicy,
23
+ )
24
+ from dex.tests.worker_server import registry
25
+ from dex.tests.workflows.state_options_workflow import (
26
+ StateOptionsWorkflow1,
27
+ StateOptionsWorkflow2,
28
+ )
29
+ from dex.workflow_options import WorkflowOptions
30
+ from dex.workflow_state_options import WorkflowStateOptions, _to_idl_state_options
31
+ from ..errors import WorkflowFailed
32
+
33
+ class TestWorkflowStateOptions(unittest.TestCase):
34
+ @classmethod
35
+ def setUpClass(cls):
36
+ cls.client = Client(registry)
37
+
38
+ def test_convert_to_idl(self):
39
+ empty_idl = IdlWorkflowStateOptions()
40
+ assert empty_idl == _to_idl_state_options(False, None, {})
41
+
42
+ non_empty = WorkflowStateOptions(
43
+ "state-id",
44
+ search_attributes_loading_policy=PersistenceLoadingPolicy(
45
+ persistence_loading_type=PersistenceLoadingType.LOAD_ALL_WITHOUT_LOCKING
46
+ ),
47
+ )
48
+ non_empty_idl = IdlWorkflowStateOptions(
49
+ skip_wait_until=True,
50
+ search_attributes_loading_policy=PersistenceLoadingPolicy(
51
+ persistence_loading_type=PersistenceLoadingType.LOAD_ALL_WITHOUT_LOCKING
52
+ ),
53
+ )
54
+ assert non_empty_idl == _to_idl_state_options(True, non_empty, {})
55
+ non_empty.state_id = "state-id-2"
56
+ assert non_empty.state_id == "state-id-2"
57
+
58
+ """Test that proceed_to_execute_when_wait_until_retry_exhausted correctly handles both enum values."""
59
+
60
+ def test_proceed_to_execute_when_wait_until_retry_exhausted(self):
61
+ retry_policy = RetryPolicy(maximum_attempts=1)
62
+
63
+ # Test PROCEED_ON_FAILURE
64
+ options_proceed = WorkflowStateOptions(
65
+ proceed_to_execute_when_wait_until_retry_exhausted=WaitUntilApiFailurePolicy.PROCEED_ON_FAILURE,
66
+ wait_until_api_retry_policy=retry_policy,
67
+ )
68
+ result_proceed = _to_idl_state_options(False, options_proceed, {})
69
+ assert (
70
+ result_proceed.wait_until_api_failure_policy
71
+ == WaitUntilApiFailurePolicy.PROCEED_ON_FAILURE
72
+ )
73
+
74
+ # Test FAIL_WORKFLOW_ON_FAILURE
75
+ options_fail = WorkflowStateOptions(
76
+ proceed_to_execute_when_wait_until_retry_exhausted=WaitUntilApiFailurePolicy.FAIL_WORKFLOW_ON_FAILURE,
77
+ wait_until_api_retry_policy=retry_policy,
78
+ )
79
+ result_fail = _to_idl_state_options(False, options_fail, {})
80
+ assert (
81
+ result_fail.wait_until_api_failure_policy
82
+ == WaitUntilApiFailurePolicy.FAIL_WORKFLOW_ON_FAILURE
83
+ )
84
+
85
+ # Test with None/unset value
86
+ options = WorkflowStateOptions()
87
+ result = _to_idl_state_options(False, options, {})
88
+ # By default, wait_until_api_failure_policy should not be set when proceed_to_execute_when_wait_until_retry_exhausted is None
89
+ # The DEX service will use FAIL_WORKFLOW_ON_FAILURE by default
90
+ from dex.dex_api.types import Unset
91
+
92
+ self.assertTrue(isinstance(result.wait_until_api_failure_policy, Unset))
93
+
94
+ def test_proceed_on_failure(self):
95
+ wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
96
+ self.client.start_workflow(
97
+ StateOptionsWorkflow1,
98
+ wf_id,
99
+ 10,
100
+ "input",
101
+ WorkflowOptions(workflow_id_reuse_policy=IDReusePolicy.DISALLOW_REUSE),
102
+ )
103
+ output = self.client.wait_for_workflow_completion(wf_id)
104
+
105
+ assert output == "InitState1_execute_completed"
106
+
107
+ def test_fail_workflow_on_failure(self):
108
+ wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
109
+ self.client.start_workflow(
110
+ StateOptionsWorkflow2,
111
+ wf_id,
112
+ 10,
113
+ "input",
114
+ WorkflowOptions(workflow_id_reuse_policy=IDReusePolicy.DISALLOW_REUSE),
115
+ )
116
+
117
+ with self.assertRaises(WorkflowFailed):
118
+ self.client.wait_for_workflow_completion(wf_id, str)
@@ -0,0 +1,44 @@
1
+ # Legacy Materials in this file remain under their original licenses.
2
+ # See LEGACY_NOTICES.md.
3
+
4
+ # Modifications Copyright (c) 2026 Super Durable, Inc.
5
+ #
6
+ # Modifications after the Legacy Cutoff are licensed under the
7
+ # Super Durable Source License 1.0.
8
+ # Legacy Materials remain under their original licenses.
9
+ # See LICENSE and LEGACY_NOTICES.md.
10
+
11
+ import inspect
12
+ import time
13
+ import unittest
14
+
15
+ from dex.client import Client
16
+ from dex.workflow_options import WorkflowOptions
17
+
18
+ from dex.dex_api.models import IDReusePolicy
19
+ from dex.tests.worker_server import registry
20
+
21
+ from dex.tests.workflows.state_options_override_workflow import (
22
+ StateOptionsOverrideWorkflow,
23
+ )
24
+
25
+ class TestStateOptionsOverrideWorkflow(unittest.TestCase):
26
+ @classmethod
27
+ def setUpClass(cls):
28
+ cls.client = Client(registry)
29
+
30
+ def test_override(self):
31
+ wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
32
+ self.client.start_workflow(
33
+ StateOptionsOverrideWorkflow,
34
+ wf_id,
35
+ 10,
36
+ "input",
37
+ WorkflowOptions(workflow_id_reuse_policy=IDReusePolicy.DISALLOW_REUSE),
38
+ )
39
+ output = self.client.wait_for_workflow_completion(wf_id)
40
+
41
+ assert (
42
+ output
43
+ == "input_InitState_waitUntil_completed_InitState_execute_completed_NonInitState_execute_completed"
44
+ )
@@ -0,0 +1,64 @@
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 threading import Thread
12
+
13
+ from flask import Flask, request
14
+
15
+ from dex.dex_api.models import (
16
+ WorkflowStateExecuteRequest,
17
+ WorkflowStateWaitUntilRequest,
18
+ WorkflowWorkerRpcRequest,
19
+ )
20
+ from dex.tests import registry
21
+ from dex.worker_service import (
22
+ WorkerService,
23
+ )
24
+
25
+ # NOTE: set this to true when debugging(using breakpoints)
26
+ # so that it keep the thread running so that we can see the error in history
27
+ debug_mode: bool = False
28
+
29
+ _flask_app = Flask(__name__)
30
+
31
+ _worker_service = WorkerService(registry)
32
+
33
+ @_flask_app.route("/")
34
+ def index():
35
+ return "hello"
36
+
37
+ @_flask_app.route(WorkerService.api_path_workflow_state_wait_until, methods=["POST"])
38
+ def handle_wait_until():
39
+ req = WorkflowStateWaitUntilRequest.from_dict(request.json)
40
+ resp = _worker_service.handle_workflow_state_wait_until(req)
41
+ return resp.to_dict()
42
+
43
+ @_flask_app.route(WorkerService.api_path_workflow_state_execute, methods=["POST"])
44
+ def handle_execute():
45
+ req = WorkflowStateExecuteRequest.from_dict(request.json)
46
+ resp = _worker_service.handle_workflow_state_execute(req)
47
+ return resp.to_dict()
48
+
49
+ @_flask_app.route(WorkerService.api_path_workflow_worker_rpc, methods=["POST"])
50
+ def handle_rpc():
51
+ req = WorkflowWorkerRpcRequest.from_dict(request.json)
52
+ resp = _worker_service.handle_workflow_worker_rpc(req)
53
+ return resp.to_dict()
54
+
55
+ # this handler is extremely useful for debugging Dex
56
+ # the WebUI will be able to show you the error with stacktrace
57
+ @_flask_app.errorhandler(Exception)
58
+ def internal_error(exception):
59
+ return _worker_service.handle_worker_error(exception), 500
60
+
61
+ _webApp = Thread(target=_flask_app.run, args=("0.0.0.0", 8802))
62
+ # when debugging, keep the thread running so that we can see the error in history
63
+ _webApp.setDaemon(not debug_mode)
64
+ _webApp.start()
@@ -0,0 +1,42 @@
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 Union
12
+
13
+ from dex.command_results import CommandResults
14
+ from dex.communication import Communication
15
+ from dex.dex_api.models import RetryPolicy
16
+ from dex.persistence import Persistence
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
+ from dex.workflow_state_options import WorkflowStateOptions
23
+
24
+ class AbnormalExitState1(WorkflowState[Union[int, str]]):
25
+ def execute(
26
+ self,
27
+ ctx: WorkflowContext,
28
+ input: T,
29
+ command_results: CommandResults,
30
+ persistence: Persistence,
31
+ communication: Communication,
32
+ ) -> StateDecision:
33
+ raise RuntimeError("abnormal exit state")
34
+
35
+ def get_state_options(self) -> WorkflowStateOptions:
36
+ return WorkflowStateOptions(
37
+ execute_api_retry_policy=RetryPolicy(maximum_attempts=1)
38
+ )
39
+
40
+ class AbnormalExitWorkflow(ObjectWorkflow):
41
+ def get_workflow_states(self) -> StateSchema:
42
+ return StateSchema.with_starting_state(AbnormalExitState1())
@@ -0,0 +1,62 @@
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 Union
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.persistence import Persistence
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
+ class State1(WorkflowState[Union[int, str]]):
24
+ def wait_until(
25
+ self,
26
+ ctx: WorkflowContext,
27
+ input: T,
28
+ persistence: Persistence,
29
+ communication: Communication,
30
+ ) -> CommandRequest:
31
+ if input != "input":
32
+ raise RuntimeError("input is incorrect")
33
+ return CommandRequest.for_all_command_completed(
34
+ TimerCommand.by_seconds(1),
35
+ )
36
+
37
+ def execute(
38
+ self,
39
+ ctx: WorkflowContext,
40
+ input: T,
41
+ command_results: CommandResults,
42
+ persistence: Persistence,
43
+ communication: Communication,
44
+ ) -> StateDecision:
45
+ if input != "input":
46
+ raise RuntimeError("input is incorrect")
47
+ return StateDecision.single_next_state(State2)
48
+
49
+ class State2(WorkflowState[None]):
50
+ def execute(
51
+ self,
52
+ ctx: WorkflowContext,
53
+ input: T,
54
+ command_results: CommandResults,
55
+ persistence: Persistence,
56
+ communication: Communication,
57
+ ) -> StateDecision:
58
+ return StateDecision.graceful_complete_workflow("done")
59
+
60
+ class BasicWorkflow(ObjectWorkflow):
61
+ def get_workflow_states(self) -> StateSchema:
62
+ return StateSchema.with_starting_state(State1(), State2())
@@ -0,0 +1,95 @@
1
+ # Legacy Materials in this file remain under their original licenses.
2
+ # See LEGACY_NOTICES.md.
3
+
4
+ # Modifications Copyright (c) 2026 Super Durable, Inc.
5
+ #
6
+ # Modifications after the Legacy Cutoff are licensed under the
7
+ # Super Durable Source License 1.0.
8
+ # Legacy Materials remain under their original licenses.
9
+ # See LICENSE and LEGACY_NOTICES.md.
10
+
11
+ import time
12
+
13
+ from dex.command_request import (
14
+ CommandRequest,
15
+ InternalChannelCommand,
16
+ SignalChannelCommand,
17
+ )
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_signal_channel = "test-1"
31
+ test_internal_channel = "test-2"
32
+
33
+ da_counter = "counter"
34
+
35
+ class WaitState(WorkflowState[bool]):
36
+ def wait_until(
37
+ self,
38
+ ctx: WorkflowContext,
39
+ use_signal: T,
40
+ persistence: Persistence,
41
+ communication: Communication,
42
+ ) -> CommandRequest:
43
+ if use_signal:
44
+ return CommandRequest.for_all_command_completed(
45
+ SignalChannelCommand.by_name(test_signal_channel),
46
+ )
47
+ else:
48
+ return CommandRequest.for_all_command_completed(
49
+ InternalChannelCommand.by_name(test_internal_channel),
50
+ )
51
+
52
+ def execute(
53
+ self,
54
+ ctx: WorkflowContext,
55
+ use_signal: T,
56
+ command_results: CommandResults,
57
+ persistence: Persistence,
58
+ communication: Communication,
59
+ ) -> StateDecision:
60
+ counter = persistence.get_data_attribute(da_counter)
61
+ if counter is None:
62
+ counter = 0
63
+ counter += 1
64
+ persistence.set_data_attribute(da_counter, counter)
65
+
66
+ if ctx.state_execution_id == "WaitState-1":
67
+ # wait for 3 seconds so that the channel can have a new message
68
+ time.sleep(3)
69
+ if use_signal:
70
+ return StateDecision.force_complete_if_signal_channel_empty_or_else(
71
+ test_signal_channel, counter, WaitState, use_signal
72
+ )
73
+ else:
74
+ return StateDecision.force_complete_if_internal_channel_empty_or_else(
75
+ test_internal_channel, counter, WaitState, use_signal
76
+ )
77
+
78
+ class ConditionalCompleteWorkflow(ObjectWorkflow):
79
+ def get_communication_schema(self) -> CommunicationSchema:
80
+ return CommunicationSchema.create(
81
+ CommunicationMethod.signal_channel_def(test_signal_channel, int),
82
+ CommunicationMethod.internal_channel_def(test_internal_channel, int),
83
+ )
84
+
85
+ def get_persistence_schema(self) -> PersistenceSchema:
86
+ return PersistenceSchema.create(
87
+ PersistenceField.data_attribute_def(da_counter, int),
88
+ )
89
+
90
+ def get_workflow_states(self) -> StateSchema:
91
+ return StateSchema.with_starting_state(WaitState())
92
+
93
+ @rpc()
94
+ def test_rpc_publish_channel(self, com: Communication):
95
+ com.publish_to_internal_channel(test_internal_channel, 0)
@@ -0,0 +1,46 @@
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, TimerCommand
12
+ from dex.command_results import CommandResults
13
+ from dex.communication import Communication
14
+ from dex.persistence import Persistence
15
+ from dex.state_decision import StateDecision
16
+ from dex.state_schema import StateSchema
17
+ from dex.workflow import ObjectWorkflow
18
+ from dex.workflow_context import WorkflowContext
19
+ from dex.workflow_state import T, WorkflowState
20
+
21
+ class WaitState(WorkflowState[None]):
22
+ def wait_until(
23
+ self,
24
+ ctx: WorkflowContext,
25
+ input: T,
26
+ persistence: Persistence,
27
+ communication: Communication,
28
+ ) -> CommandRequest:
29
+ return CommandRequest.for_all_command_completed(
30
+ TimerCommand.by_seconds(10),
31
+ )
32
+
33
+ def execute(
34
+ self,
35
+ ctx: WorkflowContext,
36
+ input: T,
37
+ command_results: CommandResults,
38
+ persistence: Persistence,
39
+ communication: Communication,
40
+ ) -> StateDecision:
41
+
42
+ return StateDecision.graceful_complete_workflow()
43
+
44
+ class DescribeWorkflow(ObjectWorkflow):
45
+ def get_workflow_states(self) -> StateSchema:
46
+ return StateSchema.with_starting_state(WaitState())
@@ -0,0 +1,45 @@
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_results import CommandResults
12
+ from dex.communication import Communication
13
+ from dex.persistence import Persistence
14
+ from dex.persistence_schema import PersistenceField, PersistenceSchema
15
+ from dex.state_decision import StateDecision
16
+ from dex.state_schema import StateSchema
17
+ from dex.workflow import ObjectWorkflow
18
+ from dex.workflow_context import WorkflowContext
19
+ from dex.workflow_state import WorkflowState
20
+
21
+ TEST_DA_KEY = "test-da"
22
+
23
+ class State1(WorkflowState[None]):
24
+ def execute(
25
+ self,
26
+ ctx: WorkflowContext,
27
+ input: None,
28
+ command_results: CommandResults,
29
+ persistence: Persistence,
30
+ communication: Communication,
31
+ ) -> StateDecision:
32
+ assert input is None
33
+ test_da = persistence.get_data_attribute(TEST_DA_KEY)
34
+ assert test_da is None
35
+
36
+ return StateDecision.graceful_complete_workflow(output="success")
37
+
38
+ class EmptyDataWorkflow(ObjectWorkflow):
39
+ def get_workflow_states(self) -> StateSchema:
40
+ return StateSchema.with_starting_state(State1())
41
+
42
+ def get_persistence_schema(self) -> PersistenceSchema:
43
+ return PersistenceSchema.create(
44
+ PersistenceField.data_attribute_def(TEST_DA_KEY, None),
45
+ )
@@ -0,0 +1,129 @@
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, InternalChannelCommandResult
13
+ from dex.communication import Communication
14
+ from dex.communication_schema import CommunicationMethod, CommunicationSchema
15
+ from dex.dex_api.models import ChannelRequestStatus
16
+ from dex.persistence import Persistence
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
+ test_channel_name1 = "test-internal-channel-1"
24
+ test_channel_name2 = "test-internal-channel-2"
25
+
26
+ test_channel_name3 = "test-internal-channel-3"
27
+ test_channel_name4 = "test-internal-channel-4"
28
+
29
+ test_channel_name_prefix = "test-internal-channel-prefix-"
30
+
31
+ class InitState(WorkflowState[None]):
32
+ def execute(
33
+ self,
34
+ ctx: WorkflowContext,
35
+ input: T,
36
+ command_results: CommandResults,
37
+ persistence: Persistence,
38
+ communication: Communication,
39
+ ) -> StateDecision:
40
+ return StateDecision.multi_next_states(
41
+ WaitAnyWithPublishState, WaitAllThenPublishState
42
+ )
43
+
44
+ class WaitAnyWithPublishState(WorkflowState[None]):
45
+ def wait_until(
46
+ self,
47
+ ctx: WorkflowContext,
48
+ input: T,
49
+ persistence: Persistence,
50
+ communication: Communication,
51
+ ) -> CommandRequest:
52
+ communication.publish_to_internal_channel(test_channel_name3, 123)
53
+ communication.publish_to_internal_channel(test_channel_name4, "str-value")
54
+ communication.publish_to_internal_channel(
55
+ test_channel_name_prefix + "abc", "str-value-for-prefix"
56
+ )
57
+ return CommandRequest.for_any_command_completed(
58
+ InternalChannelCommand.by_name(test_channel_name1),
59
+ InternalChannelCommand.by_name(test_channel_name2),
60
+ )
61
+
62
+ def execute(
63
+ self,
64
+ ctx: WorkflowContext,
65
+ input: T,
66
+ command_results: CommandResults,
67
+ persistence: Persistence,
68
+ communication: Communication,
69
+ ) -> StateDecision:
70
+ assert len(command_results.internal_channel_commands) == 2
71
+ assert command_results.internal_channel_commands[
72
+ 0
73
+ ] == InternalChannelCommandResult(
74
+ channel_name=test_channel_name1,
75
+ command_id="",
76
+ status=ChannelRequestStatus.WAITING,
77
+ value=None,
78
+ )
79
+ assert command_results.internal_channel_commands[
80
+ 1
81
+ ] == InternalChannelCommandResult(
82
+ channel_name=test_channel_name2,
83
+ command_id="",
84
+ status=ChannelRequestStatus.RECEIVED,
85
+ value=None,
86
+ )
87
+ return StateDecision.graceful_complete_workflow()
88
+
89
+ class WaitAllThenPublishState(WorkflowState[None]):
90
+ def wait_until(
91
+ self,
92
+ ctx: WorkflowContext,
93
+ input: T,
94
+ persistence: Persistence,
95
+ communication: Communication,
96
+ ) -> CommandRequest:
97
+ return CommandRequest.for_all_command_completed(
98
+ InternalChannelCommand.by_name(test_channel_name3),
99
+ InternalChannelCommand.by_name(test_channel_name4),
100
+ InternalChannelCommand.by_name(test_channel_name_prefix + "abc"),
101
+ )
102
+
103
+ def execute(
104
+ self,
105
+ ctx: WorkflowContext,
106
+ input: T,
107
+ command_results: CommandResults,
108
+ persistence: Persistence,
109
+ communication: Communication,
110
+ ) -> StateDecision:
111
+ communication.publish_to_internal_channel(test_channel_name2, None)
112
+ return StateDecision.dead_end
113
+
114
+ class InternalChannelWorkflow(ObjectWorkflow):
115
+ def get_workflow_states(self) -> StateSchema:
116
+ return StateSchema.with_starting_state(
117
+ InitState(), WaitAnyWithPublishState(), WaitAllThenPublishState()
118
+ )
119
+
120
+ def get_communication_schema(self) -> CommunicationSchema:
121
+ return CommunicationSchema.create(
122
+ CommunicationMethod.internal_channel_def(test_channel_name1, int),
123
+ CommunicationMethod.internal_channel_def(test_channel_name2, None),
124
+ CommunicationMethod.internal_channel_def(test_channel_name3, int),
125
+ CommunicationMethod.internal_channel_def(test_channel_name4, str),
126
+ CommunicationMethod.internal_channel_def_by_prefix(
127
+ test_channel_name_prefix, str
128
+ ),
129
+ )