dex-python-sdk 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. dex/__init__.py +14 -0
  2. dex/client.py +568 -0
  3. dex/client_options.py +34 -0
  4. dex/command_request.py +130 -0
  5. dex/command_results.py +116 -0
  6. dex/communication.py +141 -0
  7. dex/communication_schema.py +68 -0
  8. dex/data_attributes.py +75 -0
  9. dex/dexpb/__init__.py +1 -0
  10. dex/dexpb/dex_pb2.py +305 -0
  11. dex/dexpb/dex_pb2.pyi +1273 -0
  12. dex/dexpb/dex_pb2_grpc.py +1126 -0
  13. dex/errors.py +132 -0
  14. dex/object_encoder.py +818 -0
  15. dex/persistence.py +94 -0
  16. dex/persistence_options.py +24 -0
  17. dex/persistence_schema.py +58 -0
  18. dex/py.typed +1 -0
  19. dex/registry.py +209 -0
  20. dex/reset_workflow_type_and_options.py +77 -0
  21. dex/rpc.py +100 -0
  22. dex/search_attributes.py +189 -0
  23. dex/state_decision.py +161 -0
  24. dex/state_execution_locals.py +71 -0
  25. dex/state_movement.py +123 -0
  26. dex/state_schema.py +54 -0
  27. dex/stop_workflow_options.py +23 -0
  28. dex/tests/__init__.py +84 -0
  29. dex/tests/dex-service-env/.env +7 -0
  30. dex/tests/dex-service-env/docker-compose-init.sh +44 -0
  31. dex/tests/dex-service-env/docker-compose.yml +87 -0
  32. dex/tests/dex-service-env/dynamicconfig/README.md +39 -0
  33. dex/tests/dex-service-env/dynamicconfig/development-sql.yaml +6 -0
  34. dex/tests/dex-service-env/dynamicconfig/docker.yaml +0 -0
  35. dex/tests/test_abnormal_exit_workflow.py +48 -0
  36. dex/tests/test_basic_workflow.py +75 -0
  37. dex/tests/test_conditional_complete.py +56 -0
  38. dex/tests/test_describe_workflow.py +45 -0
  39. dex/tests/test_empty_data_decodes_properly.py +79 -0
  40. dex/tests/test_internal_channel.py +33 -0
  41. dex/tests/test_internal_channel_with_no_prefix_channel.py +46 -0
  42. dex/tests/test_persistence_data_attributes.py +67 -0
  43. dex/tests/test_persistence_search_attributes.py +132 -0
  44. dex/tests/test_persistence_state_execution_locals.py +43 -0
  45. dex/tests/test_rpc.py +69 -0
  46. dex/tests/test_rpc_with_memo.py +200 -0
  47. dex/tests/test_rpc_with_memo_duplicate_java_tests.py +122 -0
  48. dex/tests/test_signal.py +56 -0
  49. dex/tests/test_skip_wait_until.py +85 -0
  50. dex/tests/test_state_failure_recovery.py +33 -0
  51. dex/tests/test_timer.py +40 -0
  52. dex/tests/test_wait_for_state_execution_completion.py +58 -0
  53. dex/tests/test_workflow_errors.py +92 -0
  54. dex/tests/test_workflow_state_options.py +123 -0
  55. dex/tests/test_workflow_state_options_override.py +49 -0
  56. dex/tests/worker_server.py +74 -0
  57. dex/tests/workflows/abnormal_exit_workflow.py +48 -0
  58. dex/tests/workflows/basic_workflow.py +69 -0
  59. dex/tests/workflows/conditional_complete_workflow.py +101 -0
  60. dex/tests/workflows/describe_workflow.py +52 -0
  61. dex/tests/workflows/empty_data_workflow.py +51 -0
  62. dex/tests/workflows/internal_channel_workflow.py +137 -0
  63. dex/tests/workflows/internal_channel_workflow_with_no_prefix_channel.py +108 -0
  64. dex/tests/workflows/java_duplicate_rpc_memo_workflow.py +285 -0
  65. dex/tests/workflows/persistence_data_attributes_workflow.py +104 -0
  66. dex/tests/workflows/persistence_search_attributes_workflow.py +167 -0
  67. dex/tests/workflows/persistence_state_execution_local_workflow.py +69 -0
  68. dex/tests/workflows/recovery_workflow.py +90 -0
  69. dex/tests/workflows/rpc_memo_workflow.py +238 -0
  70. dex/tests/workflows/rpc_workflow.py +125 -0
  71. dex/tests/workflows/state_options_override_workflow.py +100 -0
  72. dex/tests/workflows/state_options_workflow.py +92 -0
  73. dex/tests/workflows/timer_workflow.py +52 -0
  74. dex/tests/workflows/wait_for_state_with_state_execution_id_workflow.py +77 -0
  75. dex/tests/workflows/wait_for_state_with_wait_for_key_workflow.py +78 -0
  76. dex/tests/workflows/wait_internal_channel_workflow.py +53 -0
  77. dex/tests/workflows/wait_signal_workflow.py +154 -0
  78. dex/type_store.py +105 -0
  79. dex/unregistered_client.py +595 -0
  80. dex/utils/__init__.py +14 -0
  81. dex/utils/dex_typing.py +31 -0
  82. dex/utils/persistence_utils.py +37 -0
  83. dex/worker_service.py +441 -0
  84. dex/workflow.py +86 -0
  85. dex/workflow_context.py +50 -0
  86. dex/workflow_info.py +21 -0
  87. dex/workflow_options.py +79 -0
  88. dex/workflow_state.py +134 -0
  89. dex/workflow_state_options.py +160 -0
  90. dex_python_sdk-0.0.1.dist-info/METADATA +140 -0
  91. dex_python_sdk-0.0.1.dist-info/RECORD +93 -0
  92. dex_python_sdk-0.0.1.dist-info/WHEEL +4 -0
  93. dex_python_sdk-0.0.1.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,101 @@
1
+ # Copyright (c) 2022-2026 Super Durable, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import time
16
+
17
+ from dex.command_request import (
18
+ CommandRequest,
19
+ InternalChannelCommand,
20
+ SignalChannelCommand,
21
+ )
22
+ from dex.command_results import CommandResults
23
+ from dex.communication import Communication
24
+ from dex.communication_schema import CommunicationMethod, CommunicationSchema
25
+ from dex.persistence import Persistence
26
+ from dex.persistence_schema import PersistenceField, PersistenceSchema
27
+ from dex.rpc import rpc
28
+ from dex.state_decision import StateDecision
29
+ from dex.state_schema import StateSchema
30
+ from dex.workflow import ObjectWorkflow
31
+ from dex.workflow_context import WorkflowContext
32
+ from dex.workflow_state import T, WorkflowState
33
+
34
+ test_signal_channel = "test-1"
35
+ test_internal_channel = "test-2"
36
+
37
+ da_counter = "counter"
38
+
39
+
40
+ class WaitState(WorkflowState[bool]):
41
+ def wait_until(
42
+ self,
43
+ ctx: WorkflowContext,
44
+ use_signal: T,
45
+ persistence: Persistence,
46
+ communication: Communication,
47
+ ) -> CommandRequest:
48
+ if use_signal:
49
+ return CommandRequest.for_all_command_completed(
50
+ SignalChannelCommand.by_name(test_signal_channel),
51
+ )
52
+ else:
53
+ return CommandRequest.for_all_command_completed(
54
+ InternalChannelCommand.by_name(test_internal_channel),
55
+ )
56
+
57
+ def execute(
58
+ self,
59
+ ctx: WorkflowContext,
60
+ use_signal: T,
61
+ command_results: CommandResults,
62
+ persistence: Persistence,
63
+ communication: Communication,
64
+ ) -> StateDecision:
65
+ counter = persistence.get_data_attribute(da_counter)
66
+ if counter is None:
67
+ counter = 0
68
+ counter += 1
69
+ persistence.set_data_attribute(da_counter, counter)
70
+
71
+ if ctx.state_execution_id == "WaitState-1":
72
+ # wait for 3 seconds so that the channel can have a new message
73
+ time.sleep(3)
74
+ if use_signal:
75
+ return StateDecision.force_complete_if_signal_channel_empty_or_else(
76
+ test_signal_channel, counter, WaitState, use_signal
77
+ )
78
+ else:
79
+ return StateDecision.force_complete_if_internal_channel_empty_or_else(
80
+ test_internal_channel, counter, WaitState, use_signal
81
+ )
82
+
83
+
84
+ class ConditionalCompleteWorkflow(ObjectWorkflow):
85
+ def get_communication_schema(self) -> CommunicationSchema:
86
+ return CommunicationSchema.create(
87
+ CommunicationMethod.signal_channel_def(test_signal_channel, int),
88
+ CommunicationMethod.internal_channel_def(test_internal_channel, int),
89
+ )
90
+
91
+ def get_persistence_schema(self) -> PersistenceSchema:
92
+ return PersistenceSchema.create(
93
+ PersistenceField.data_attribute_def(da_counter, int),
94
+ )
95
+
96
+ def get_workflow_states(self) -> StateSchema:
97
+ return StateSchema.with_starting_state(WaitState())
98
+
99
+ @rpc()
100
+ def test_rpc_publish_channel(self, com: Communication):
101
+ com.publish_to_internal_channel(test_internal_channel, 0)
@@ -0,0 +1,52 @@
1
+ # Copyright (c) 2022-2026 Super Durable, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from dex.command_request import CommandRequest, TimerCommand
16
+ from dex.command_results import CommandResults
17
+ from dex.communication import Communication
18
+ from dex.persistence import Persistence
19
+ from dex.state_decision import StateDecision
20
+ from dex.state_schema import StateSchema
21
+ from dex.workflow import ObjectWorkflow
22
+ from dex.workflow_context import WorkflowContext
23
+ from dex.workflow_state import T, WorkflowState
24
+
25
+
26
+ class WaitState(WorkflowState[None]):
27
+ def wait_until(
28
+ self,
29
+ ctx: WorkflowContext,
30
+ input: T,
31
+ persistence: Persistence,
32
+ communication: Communication,
33
+ ) -> CommandRequest:
34
+ return CommandRequest.for_all_command_completed(
35
+ TimerCommand.by_seconds(10),
36
+ )
37
+
38
+ def execute(
39
+ self,
40
+ ctx: WorkflowContext,
41
+ input: T,
42
+ command_results: CommandResults,
43
+ persistence: Persistence,
44
+ communication: Communication,
45
+ ) -> StateDecision:
46
+
47
+ return StateDecision.graceful_complete_workflow()
48
+
49
+
50
+ class DescribeWorkflow(ObjectWorkflow):
51
+ def get_workflow_states(self) -> StateSchema:
52
+ return StateSchema.with_starting_state(WaitState())
@@ -0,0 +1,51 @@
1
+ # Copyright (c) 2022-2026 Super Durable, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from dex.command_results import CommandResults
16
+ from dex.communication import Communication
17
+ from dex.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 WorkflowState
24
+
25
+ TEST_DA_KEY = "test-da"
26
+
27
+
28
+ class State1(WorkflowState[None]):
29
+ def execute(
30
+ self,
31
+ ctx: WorkflowContext,
32
+ input: None,
33
+ command_results: CommandResults,
34
+ persistence: Persistence,
35
+ communication: Communication,
36
+ ) -> StateDecision:
37
+ assert input is None
38
+ test_da = persistence.get_data_attribute(TEST_DA_KEY)
39
+ assert test_da is None
40
+
41
+ return StateDecision.graceful_complete_workflow(output="success")
42
+
43
+
44
+ class EmptyDataWorkflow(ObjectWorkflow):
45
+ def get_workflow_states(self) -> StateSchema:
46
+ return StateSchema.with_starting_state(State1())
47
+
48
+ def get_persistence_schema(self) -> PersistenceSchema:
49
+ return PersistenceSchema.create(
50
+ PersistenceField.data_attribute_def(TEST_DA_KEY, None),
51
+ )
@@ -0,0 +1,137 @@
1
+ # Copyright (c) 2022-2026 Super Durable, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from dex.command_request import CommandRequest, InternalChannelCommand
16
+ from dex.command_results import CommandResults, InternalChannelCommandResult
17
+ from dex.communication import Communication
18
+ from dex.communication_schema import CommunicationMethod, CommunicationSchema
19
+ from dex.dex_api.models import ChannelRequestStatus
20
+ from dex.persistence import Persistence
21
+ from dex.state_decision import StateDecision
22
+ from dex.state_schema import StateSchema
23
+ from dex.workflow import ObjectWorkflow
24
+ from dex.workflow_context import WorkflowContext
25
+ from dex.workflow_state import T, WorkflowState
26
+
27
+ test_channel_name1 = "test-internal-channel-1"
28
+ test_channel_name2 = "test-internal-channel-2"
29
+
30
+ test_channel_name3 = "test-internal-channel-3"
31
+ test_channel_name4 = "test-internal-channel-4"
32
+
33
+ test_channel_name_prefix = "test-internal-channel-prefix-"
34
+
35
+
36
+ class InitState(WorkflowState[None]):
37
+ def execute(
38
+ self,
39
+ ctx: WorkflowContext,
40
+ input: T,
41
+ command_results: CommandResults,
42
+ persistence: Persistence,
43
+ communication: Communication,
44
+ ) -> StateDecision:
45
+ return StateDecision.multi_next_states(
46
+ WaitAnyWithPublishState, WaitAllThenPublishState
47
+ )
48
+
49
+
50
+ class WaitAnyWithPublishState(WorkflowState[None]):
51
+ def wait_until(
52
+ self,
53
+ ctx: WorkflowContext,
54
+ input: T,
55
+ persistence: Persistence,
56
+ communication: Communication,
57
+ ) -> CommandRequest:
58
+ communication.publish_to_internal_channel(test_channel_name3, 123)
59
+ communication.publish_to_internal_channel(test_channel_name4, "str-value")
60
+ communication.publish_to_internal_channel(
61
+ test_channel_name_prefix + "abc", "str-value-for-prefix"
62
+ )
63
+ return CommandRequest.for_any_command_completed(
64
+ InternalChannelCommand.by_name(test_channel_name1),
65
+ InternalChannelCommand.by_name(test_channel_name2),
66
+ )
67
+
68
+ def execute(
69
+ self,
70
+ ctx: WorkflowContext,
71
+ input: T,
72
+ command_results: CommandResults,
73
+ persistence: Persistence,
74
+ communication: Communication,
75
+ ) -> StateDecision:
76
+ assert len(command_results.internal_channel_commands) == 2
77
+ assert command_results.internal_channel_commands[
78
+ 0
79
+ ] == InternalChannelCommandResult(
80
+ channel_name=test_channel_name1,
81
+ command_id="",
82
+ status=ChannelRequestStatus.WAITING,
83
+ value=None,
84
+ )
85
+ assert command_results.internal_channel_commands[
86
+ 1
87
+ ] == InternalChannelCommandResult(
88
+ channel_name=test_channel_name2,
89
+ command_id="",
90
+ status=ChannelRequestStatus.RECEIVED,
91
+ value=None,
92
+ )
93
+ return StateDecision.graceful_complete_workflow()
94
+
95
+
96
+ class WaitAllThenPublishState(WorkflowState[None]):
97
+ def wait_until(
98
+ self,
99
+ ctx: WorkflowContext,
100
+ input: T,
101
+ persistence: Persistence,
102
+ communication: Communication,
103
+ ) -> CommandRequest:
104
+ return CommandRequest.for_all_command_completed(
105
+ InternalChannelCommand.by_name(test_channel_name3),
106
+ InternalChannelCommand.by_name(test_channel_name4),
107
+ InternalChannelCommand.by_name(test_channel_name_prefix + "abc"),
108
+ )
109
+
110
+ def execute(
111
+ self,
112
+ ctx: WorkflowContext,
113
+ input: T,
114
+ command_results: CommandResults,
115
+ persistence: Persistence,
116
+ communication: Communication,
117
+ ) -> StateDecision:
118
+ communication.publish_to_internal_channel(test_channel_name2, None)
119
+ return StateDecision.dead_end
120
+
121
+
122
+ class InternalChannelWorkflow(ObjectWorkflow):
123
+ def get_workflow_states(self) -> StateSchema:
124
+ return StateSchema.with_starting_state(
125
+ InitState(), WaitAnyWithPublishState(), WaitAllThenPublishState()
126
+ )
127
+
128
+ def get_communication_schema(self) -> CommunicationSchema:
129
+ return CommunicationSchema.create(
130
+ CommunicationMethod.internal_channel_def(test_channel_name1, int),
131
+ CommunicationMethod.internal_channel_def(test_channel_name2, None),
132
+ CommunicationMethod.internal_channel_def(test_channel_name3, int),
133
+ CommunicationMethod.internal_channel_def(test_channel_name4, str),
134
+ CommunicationMethod.internal_channel_def_by_prefix(
135
+ test_channel_name_prefix, str
136
+ ),
137
+ )
@@ -0,0 +1,108 @@
1
+ # Copyright (c) 2022-2026 Super Durable, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from dex.command_request import CommandRequest, InternalChannelCommand
16
+ from dex.command_results import CommandResults
17
+ from dex.communication import Communication
18
+ from dex.communication_schema import CommunicationMethod, CommunicationSchema
19
+ from dex.persistence import Persistence
20
+ from dex.state_decision import StateDecision
21
+ from dex.state_schema import StateSchema
22
+ from dex.workflow import ObjectWorkflow
23
+ from dex.workflow_context import WorkflowContext
24
+ from dex.workflow_state import T, WorkflowState
25
+
26
+ internal_channel_name = "internal-channel-1"
27
+
28
+ test_non_prefix_channel_name = "test-channel-"
29
+ test_non_prefix_channel_name_with_suffix = test_non_prefix_channel_name + "abc"
30
+
31
+
32
+ class InitState(WorkflowState[None]):
33
+ def execute(
34
+ self,
35
+ ctx: WorkflowContext,
36
+ input: T,
37
+ command_results: CommandResults,
38
+ persistence: Persistence,
39
+ communication: Communication,
40
+ ) -> StateDecision:
41
+ return StateDecision.multi_next_states(
42
+ WaitAnyWithPublishState, WaitAllThenPublishState
43
+ )
44
+
45
+
46
+ class WaitAnyWithPublishState(WorkflowState[None]):
47
+ def wait_until(
48
+ self,
49
+ ctx: WorkflowContext,
50
+ input: T,
51
+ persistence: Persistence,
52
+ communication: Communication,
53
+ ) -> CommandRequest:
54
+ # Trying to publish to a non-existing channel; this would only work if test_channel_name_non_prefix was defined as a prefix channel
55
+ communication.publish_to_internal_channel(
56
+ test_non_prefix_channel_name_with_suffix, "str-value-for-prefix"
57
+ )
58
+ return CommandRequest.for_any_command_completed(
59
+ InternalChannelCommand.by_name(internal_channel_name),
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
+ return StateDecision.graceful_complete_workflow()
71
+
72
+
73
+ class WaitAllThenPublishState(WorkflowState[None]):
74
+ def wait_until(
75
+ self,
76
+ ctx: WorkflowContext,
77
+ input: T,
78
+ persistence: Persistence,
79
+ communication: Communication,
80
+ ) -> CommandRequest:
81
+ return CommandRequest.for_all_command_completed(
82
+ InternalChannelCommand.by_name(test_non_prefix_channel_name),
83
+ )
84
+
85
+ def execute(
86
+ self,
87
+ ctx: WorkflowContext,
88
+ input: T,
89
+ command_results: CommandResults,
90
+ persistence: Persistence,
91
+ communication: Communication,
92
+ ) -> StateDecision:
93
+ communication.publish_to_internal_channel(internal_channel_name, None)
94
+ return StateDecision.dead_end
95
+
96
+
97
+ class InternalChannelWorkflowWithNoPrefixChannel(ObjectWorkflow):
98
+ def get_workflow_states(self) -> StateSchema:
99
+ return StateSchema.with_starting_state(
100
+ InitState(), WaitAnyWithPublishState(), WaitAllThenPublishState()
101
+ )
102
+
103
+ def get_communication_schema(self) -> CommunicationSchema:
104
+ return CommunicationSchema.create(
105
+ CommunicationMethod.internal_channel_def(internal_channel_name, None),
106
+ # Defining a standard channel (non-prefix) to make sure messages to the channel with a suffix added will not be accepted
107
+ CommunicationMethod.internal_channel_def(test_non_prefix_channel_name, str),
108
+ )