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.
- dex/__init__.py +146 -0
- dex/_grpc_errors.py +73 -0
- dex/_invocation_context.py +269 -0
- dex/_native.pyd +0 -0
- dex/_native.pyi +20 -0
- dex/_utils.py +18 -0
- dex/_value_hydrator.py +223 -0
- dex/_value_mapper.py +174 -0
- dex/_worker_dispatcher.py +451 -0
- dex/_worker_service.py +60 -0
- dex/attribute.py +87 -0
- dex/blob_cache.py +73 -0
- dex/channel.py +165 -0
- dex/client.py +715 -0
- dex/client_options.py +19 -0
- dex/codec.py +286 -0
- dex/command_request.py +120 -0
- dex/command_results.py +107 -0
- dex/communication.py +136 -0
- dex/communication_schema.py +54 -0
- dex/condition.py +74 -0
- dex/context.py +86 -0
- dex/data_attributes.py +70 -0
- dex/dexpb/__init__.py +1 -0
- dex/dexpb/dex_pb2.py +381 -0
- dex/dexpb/dex_pb2.pyi +1734 -0
- dex/dexpb/dex_pb2_grpc.py +1298 -0
- dex/errors.py +109 -0
- dex/flow.py +456 -0
- dex/flow_config.py +29 -0
- dex/flow_info.py +51 -0
- dex/flow_options.py +122 -0
- dex/object_encoder.py +799 -0
- dex/persistence.py +89 -0
- dex/persistence_options.py +12 -0
- dex/persistence_schema.py +51 -0
- dex/py.typed +1 -0
- dex/registry.py +204 -0
- dex/reset_workflow_type_and_options.py +67 -0
- dex/rpc.py +93 -0
- dex/runtime_errors.py +81 -0
- dex/search_attributes.py +184 -0
- dex/state_decision.py +153 -0
- dex/state_execution_locals.py +66 -0
- dex/state_movement.py +115 -0
- dex/state_schema.py +48 -0
- dex/step.py +194 -0
- dex/step_execution.py +42 -0
- dex/stop_workflow_options.py +18 -0
- dex/tests/__init__.py +80 -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 +97 -0
- dex/tests/dex-service-env/dynamicconfig/README.md +39 -0
- dex/tests/dex-service-env/dynamicconfig/development-sql.yaml +9 -0
- dex/tests/dex-service-env/dynamicconfig/docker.yaml +2 -0
- dex/tests/test_abnormal_exit_workflow.py +43 -0
- dex/tests/test_basic_workflow.py +70 -0
- dex/tests/test_conditional_complete.py +50 -0
- dex/tests/test_describe_workflow.py +40 -0
- dex/tests/test_empty_data_decodes_properly.py +74 -0
- dex/tests/test_internal_channel.py +28 -0
- dex/tests/test_internal_channel_with_no_prefix_channel.py +41 -0
- dex/tests/test_persistence_data_attributes.py +62 -0
- dex/tests/test_persistence_search_attributes.py +127 -0
- dex/tests/test_persistence_state_execution_locals.py +38 -0
- dex/tests/test_rpc.py +64 -0
- dex/tests/test_rpc_with_memo.py +195 -0
- dex/tests/test_rpc_with_memo_duplicate_java_tests.py +117 -0
- dex/tests/test_signal.py +51 -0
- dex/tests/test_skip_wait_until.py +76 -0
- dex/tests/test_state_failure_recovery.py +28 -0
- dex/tests/test_timer.py +35 -0
- dex/tests/test_wait_for_state_execution_completion.py +53 -0
- dex/tests/test_workflow_errors.py +87 -0
- dex/tests/test_workflow_state_options.py +118 -0
- dex/tests/test_workflow_state_options_override.py +44 -0
- dex/tests/worker_server.py +64 -0
- dex/tests/workflows/abnormal_exit_workflow.py +42 -0
- dex/tests/workflows/basic_workflow.py +62 -0
- dex/tests/workflows/conditional_complete_workflow.py +95 -0
- dex/tests/workflows/describe_workflow.py +46 -0
- dex/tests/workflows/empty_data_workflow.py +45 -0
- dex/tests/workflows/internal_channel_workflow.py +129 -0
- dex/tests/workflows/internal_channel_workflow_with_no_prefix_channel.py +100 -0
- dex/tests/workflows/java_duplicate_rpc_memo_workflow.py +276 -0
- dex/tests/workflows/persistence_data_attributes_workflow.py +98 -0
- dex/tests/workflows/persistence_search_attributes_workflow.py +159 -0
- dex/tests/workflows/persistence_state_execution_local_workflow.py +63 -0
- dex/tests/workflows/recovery_workflow.py +82 -0
- dex/tests/workflows/rpc_memo_workflow.py +231 -0
- dex/tests/workflows/rpc_workflow.py +117 -0
- dex/tests/workflows/state_options_override_workflow.py +93 -0
- dex/tests/workflows/state_options_workflow.py +84 -0
- dex/tests/workflows/timer_workflow.py +46 -0
- dex/tests/workflows/wait_for_state_with_state_execution_id_workflow.py +70 -0
- dex/tests/workflows/wait_for_state_with_wait_for_key_workflow.py +71 -0
- dex/tests/workflows/wait_internal_channel_workflow.py +47 -0
- dex/tests/workflows/wait_signal_workflow.py +147 -0
- dex/timer.py +21 -0
- dex/type_store.py +99 -0
- dex/unregistered_client.py +585 -0
- dex/utils/__init__.py +3 -0
- dex/utils/dex_typing.py +25 -0
- dex/utils/persistence_utils.py +32 -0
- dex/wait.py +49 -0
- dex/worker.py +121 -0
- dex/worker_options.py +22 -0
- dex/worker_service.py +432 -0
- dex/workflow.py +79 -0
- dex/workflow_context.py +44 -0
- dex/workflow_info.py +16 -0
- dex/workflow_options.py +74 -0
- dex/workflow_state.py +123 -0
- dex/workflow_state_options.py +154 -0
- dex_python_sdk-0.0.2.dist-info/METADATA +202 -0
- dex_python_sdk-0.0.2.dist-info/RECORD +121 -0
- dex_python_sdk-0.0.2.dist-info/WHEEL +4 -0
- dex_python_sdk-0.0.2.dist-info/licenses/LEGACY_NOTICES.md +61 -0
- dex_python_sdk-0.0.2.dist-info/licenses/LICENSE +192 -0
- 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
|
+
)
|