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
dex/persistence.py ADDED
@@ -0,0 +1,94 @@
1
+ # Copyright (c) 2022-2026 Super Durable, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import Any, Tuple, Union
16
+
17
+ from dex.data_attributes import DataAttributes
18
+ from dex.search_attributes import SearchAttributes
19
+ from dex.state_execution_locals import StateExecutionLocals
20
+
21
+
22
+ class Persistence:
23
+ _data_attributes: DataAttributes
24
+ _search_attributes: SearchAttributes
25
+ _state_execution_locals: StateExecutionLocals
26
+
27
+ def __init__(
28
+ self,
29
+ data_attributes: DataAttributes,
30
+ search_attributes: SearchAttributes,
31
+ state_execution_locals: StateExecutionLocals,
32
+ ):
33
+ self._data_attributes = data_attributes
34
+ self._search_attributes = search_attributes
35
+ self._state_execution_locals = state_execution_locals
36
+
37
+ def get_data_attribute(self, key: str) -> Any:
38
+ return self._data_attributes.get_data_attribute(key)
39
+
40
+ def set_data_attribute(self, key: str, value: Any):
41
+ self._data_attributes.set_data_attribute(key, value)
42
+
43
+ def get_search_attribute_int64(self, key: str) -> Union[None, int]:
44
+ return self._search_attributes.get_search_attribute_int64(key)
45
+
46
+ def set_search_attribute_int64(self, key: str, value: Union[None, int]):
47
+ self._search_attributes.set_search_attribute_int64(key, value)
48
+
49
+ def get_search_attribute_double(self, key: str) -> Union[None, float]:
50
+ return self._search_attributes.get_search_attribute_double(key)
51
+
52
+ def set_search_attribute_double(self, key: str, value: Union[None, float]):
53
+ self._search_attributes.set_search_attribute_double(key, value)
54
+
55
+ def get_search_attribute_boolean(self, key: str) -> Union[None, bool]:
56
+ return self._search_attributes.get_search_attribute_boolean(key)
57
+
58
+ def set_search_attribute_boolean(self, key: str, value: Union[None, bool]):
59
+ self._search_attributes.set_search_attribute_boolean(key, value)
60
+
61
+ def get_search_attribute_keyword(self, key: str) -> Union[None, str]:
62
+ return self._search_attributes.get_search_attribute_keyword(key)
63
+
64
+ def set_search_attribute_keyword(self, key: str, value: Union[None, str]):
65
+ self._search_attributes.set_search_attribute_keyword(key, value)
66
+
67
+ def get_search_attribute_text(self, key: str) -> Union[None, str]:
68
+ return self._search_attributes.get_search_attribute_text(key)
69
+
70
+ def set_search_attribute_text(self, key: str, value: Union[None, str]):
71
+ self._search_attributes.set_search_attribute_text(key, value)
72
+
73
+ def get_search_attribute_datetime(self, key: str) -> Union[None, str]:
74
+ return self._search_attributes.get_search_attribute_datetime(key)
75
+
76
+ def set_search_attribute_datetime(self, key: str, value: Union[None, str]):
77
+ self._search_attributes.set_search_attribute_datetime(key, value)
78
+
79
+ def get_search_attribute_keyword_array(self, key: str) -> Union[None, list[str]]:
80
+ return self._search_attributes.get_search_attribute_keyword_array(key)
81
+
82
+ def set_search_attribute_keyword_array(
83
+ self, key: str, value: Union[None, list[str]]
84
+ ):
85
+ self._search_attributes.set_search_attribute_keyword_array(key, value)
86
+
87
+ def get_state_execution_local(self, key: str) -> Any:
88
+ return self._state_execution_locals.get_state_execution_local(key)
89
+
90
+ def set_state_execution_local(self, key: str, value: Any):
91
+ self._state_execution_locals.set_state_execution_local(key, value)
92
+
93
+ def record_event(self, key: str, *event_data: Tuple[Any, ...]):
94
+ self._state_execution_locals.record_event(key, *event_data)
@@ -0,0 +1,24 @@
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 dataclasses import dataclass
16
+
17
+
18
+ @dataclass
19
+ class PersistenceOptions:
20
+ enable_caching: bool
21
+
22
+ @classmethod
23
+ def get_default(cls):
24
+ return PersistenceOptions(False)
@@ -0,0 +1,58 @@
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 dataclasses import dataclass, field
16
+ from enum import Enum
17
+ from typing import List, Optional
18
+
19
+ from dex.dex_api.models import SearchAttributeValueType
20
+
21
+
22
+ class PersistenceFieldType(Enum):
23
+ DataAttribute = 1
24
+ SearchAttribute = 2
25
+ DataAttributePrefix = 3
26
+
27
+
28
+ @dataclass
29
+ class PersistenceField:
30
+ key: str
31
+ field_type: PersistenceFieldType
32
+ value_type: Optional[type]
33
+ search_attribute_type: Optional[SearchAttributeValueType] = None
34
+
35
+ @classmethod
36
+ def data_attribute_def(cls, key: str, value_type: Optional[type]):
37
+ return PersistenceField(key, PersistenceFieldType.DataAttribute, value_type)
38
+
39
+ @classmethod
40
+ def search_attribute_def(cls, key: str, sa_type: SearchAttributeValueType):
41
+ return PersistenceField(
42
+ key, PersistenceFieldType.SearchAttribute, None, sa_type
43
+ )
44
+
45
+ @classmethod
46
+ def data_attribute_prefix_def(cls, key: str, value_type: Optional[type]):
47
+ return PersistenceField(
48
+ key, PersistenceFieldType.DataAttributePrefix, value_type
49
+ )
50
+
51
+
52
+ @dataclass
53
+ class PersistenceSchema:
54
+ persistence_fields: List[PersistenceField] = field(default_factory=list)
55
+
56
+ @classmethod
57
+ def create(cls, *args: PersistenceField):
58
+ return PersistenceSchema(list(args))
dex/py.typed ADDED
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561
dex/registry.py ADDED
@@ -0,0 +1,209 @@
1
+ # Copyright (c) 2022-2026 Super Durable, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import Callable, Optional
16
+
17
+ from dex.communication_schema import CommunicationMethodType
18
+ from dex.errors import InvalidArgumentError, WorkflowDefinitionError
19
+ from dex.dex_api.models import SearchAttributeValueType
20
+ from dex.persistence_options import PersistenceOptions
21
+ from dex.persistence_schema import PersistenceFieldType
22
+ from dex.rpc import RPCInfo
23
+ from dex.type_store import TypeStore, Type
24
+ from dex.workflow import ObjectWorkflow, get_workflow_type
25
+ from dex.workflow_state import WorkflowState, get_state_id
26
+
27
+
28
+ class Registry:
29
+ _workflow_store: dict[str, ObjectWorkflow]
30
+ _starting_state_store: dict[str, WorkflowState]
31
+ _state_store: dict[str, dict[str, WorkflowState]]
32
+ _internal_channel_type_store: dict[str, TypeStore]
33
+ _signal_channel_type_store: dict[str, dict[str, Optional[type]]]
34
+ _data_attribute_types: dict[str, TypeStore]
35
+ _search_attribute_types: dict[str, dict[str, SearchAttributeValueType]]
36
+ _persistence_options: dict[str, PersistenceOptions]
37
+ _rpc_infos: dict[str, dict[str, RPCInfo]]
38
+
39
+ def __init__(self):
40
+ self._workflow_store = dict()
41
+ self._starting_state_store = dict()
42
+ self._state_store = dict()
43
+ self._internal_channel_type_store = dict()
44
+ self._signal_channel_type_store = dict()
45
+ self._data_attribute_types = dict()
46
+ self._search_attribute_types = {}
47
+ self._persistence_options = {}
48
+ self._rpc_infos = dict()
49
+
50
+ def add_workflow(self, wf: ObjectWorkflow):
51
+ self._register_workflow_type(wf)
52
+ self._register_workflow_state(wf)
53
+ self._register_internal_channels(wf)
54
+ self._register_signal_channels(wf)
55
+ self._register_data_attributes(wf)
56
+ self._register_search_attributes(wf)
57
+ self._register_persistence_options(wf)
58
+ self._register_workflow_rpcs(wf)
59
+
60
+ def add_workflows(self, *wfs: ObjectWorkflow):
61
+ for wf in wfs:
62
+ self.add_workflow(wf)
63
+
64
+ def get_workflow_with_check(self, wf_type: str) -> ObjectWorkflow:
65
+ wf = self._workflow_store.get(wf_type)
66
+ if wf is None:
67
+ raise InvalidArgumentError(f"workflow {wf_type} is not registered")
68
+ return wf
69
+
70
+ def get_workflow_starting_state(self, wf_type: str) -> Optional[WorkflowState]:
71
+ return self._starting_state_store.get(wf_type)
72
+
73
+ def get_workflow_state_with_check(
74
+ self, wf_type: str, state_id: str
75
+ ) -> WorkflowState:
76
+ states = self._state_store.get(wf_type)
77
+ if states is None:
78
+ raise InvalidArgumentError(f"workflow {wf_type} is not registered")
79
+ state = states.get(state_id)
80
+ if state is None:
81
+ raise InvalidArgumentError(
82
+ f"workflow {wf_type} state {state_id} is not registered"
83
+ )
84
+ return state
85
+
86
+ def get_state_store(self, wf_type: str) -> dict[str, WorkflowState]:
87
+ return self._state_store[wf_type]
88
+
89
+ def get_internal_channel_type_store(self, wf_type: str) -> TypeStore:
90
+ return self._internal_channel_type_store[wf_type]
91
+
92
+ def get_signal_channel_types(self, wf_type: str) -> dict[str, Optional[type]]:
93
+ return self._signal_channel_type_store[wf_type]
94
+
95
+ def get_data_attribute_types(self, wf_type: str) -> TypeStore:
96
+ return self._data_attribute_types[wf_type]
97
+
98
+ def get_search_attribute_types(
99
+ self, wf_type: str
100
+ ) -> dict[str, SearchAttributeValueType]:
101
+ return self._search_attribute_types[wf_type]
102
+
103
+ def get_persistence_options(self, wf_type: str) -> PersistenceOptions:
104
+ return self._persistence_options[wf_type]
105
+
106
+ def get_rpc_infos(self, wf_type: str) -> dict[str, RPCInfo]:
107
+ return self._rpc_infos[wf_type]
108
+
109
+ def _register_workflow_type(self, wf: ObjectWorkflow):
110
+ wf_type = get_workflow_type(wf)
111
+ if wf_type in self._workflow_store:
112
+ raise WorkflowDefinitionError("workflow type conflict: ", wf_type)
113
+ self._workflow_store[wf_type] = wf
114
+
115
+ def _register_internal_channels(self, wf: ObjectWorkflow):
116
+ wf_type = get_workflow_type(wf)
117
+
118
+ if wf_type not in self._internal_channel_type_store:
119
+ self._internal_channel_type_store[wf_type] = TypeStore(
120
+ Type.INTERNAL_CHANNEL
121
+ )
122
+
123
+ for method in wf.get_communication_schema().communication_methods:
124
+ if method.method_type == CommunicationMethodType.InternalChannel:
125
+ self._internal_channel_type_store[wf_type].add_internal_channel_def(
126
+ method
127
+ )
128
+
129
+ def _register_signal_channels(self, wf: ObjectWorkflow):
130
+ wf_type = get_workflow_type(wf)
131
+ types: dict[str, Optional[type]] = {}
132
+ for method in wf.get_communication_schema().communication_methods:
133
+ if method.method_type == CommunicationMethodType.SignalChannel:
134
+ types[method.name] = method.value_type
135
+ self._signal_channel_type_store[wf_type] = types
136
+
137
+ def _register_data_attributes(self, wf: ObjectWorkflow):
138
+ wf_type = get_workflow_type(wf)
139
+ data_attribute_types: TypeStore = TypeStore(Type.DATA_ATTRIBUTE)
140
+ for field in wf.get_persistence_schema().persistence_fields:
141
+ if (
142
+ field.field_type == PersistenceFieldType.DataAttribute
143
+ or field.field_type == PersistenceFieldType.DataAttributePrefix
144
+ ):
145
+ data_attribute_types.add_data_attribute_def(field)
146
+ self._data_attribute_types[wf_type] = data_attribute_types
147
+
148
+ def _register_search_attributes(self, wf: ObjectWorkflow):
149
+ wf_type = get_workflow_type(wf)
150
+ types: dict[str, SearchAttributeValueType] = {}
151
+ for field in wf.get_persistence_schema().persistence_fields:
152
+ if field.field_type == PersistenceFieldType.SearchAttribute:
153
+ sa_type = field.search_attribute_type
154
+ if sa_type is None:
155
+ raise WorkflowDefinitionError(
156
+ f"Found search attribute {field.key} with no type set"
157
+ )
158
+ if field.key in types:
159
+ raise WorkflowDefinitionError(
160
+ f"Search attribute {field.key} already exists"
161
+ )
162
+ types[field.key] = sa_type
163
+ self._search_attribute_types[wf_type] = types
164
+
165
+ def _register_persistence_options(self, wf: ObjectWorkflow):
166
+ wf_type = get_workflow_type(wf)
167
+ self._persistence_options[wf_type] = wf.get_persistence_options()
168
+
169
+ def _register_workflow_state(self, wf):
170
+ wf_type = get_workflow_type(wf)
171
+ state_map = {}
172
+ starting_state = None
173
+ for state_def in wf.get_workflow_states().states:
174
+ state_id = get_state_id(state_def.state)
175
+ if state_id in state_map:
176
+ raise WorkflowDefinitionError(
177
+ f"Workflow {wf_type} cannot have duplicate stateId {state_id}"
178
+ )
179
+ state_map[state_id] = state_def.state
180
+ if state_def.can_start_workflow:
181
+ if starting_state is not None:
182
+ raise WorkflowDefinitionError(
183
+ f"Workflow {wf_type} cannot contain more than one starting "
184
+ f"state"
185
+ )
186
+ starting_state = state_def.state
187
+ self._state_store[wf_type] = state_map
188
+ self._starting_state_store[wf_type] = starting_state
189
+
190
+ @staticmethod
191
+ def _is_decorated_by_rpc(func: Callable):
192
+ return getattr(func, "_is_dex_rpc", False)
193
+
194
+ @staticmethod
195
+ def _get_rpc_info(func: Callable):
196
+ info = getattr(func, "_rpc_info")
197
+ assert isinstance(info, RPCInfo)
198
+ # NOTE: we have to override the method here so that it's associated the object
199
+ info.method_func = func
200
+ return info
201
+
202
+ def _register_workflow_rpcs(self, wf):
203
+ wf_type = get_workflow_type(wf)
204
+ rpc_infos = {}
205
+ for method_name in dir(wf):
206
+ method = getattr(wf, method_name)
207
+ if callable(method) and self._is_decorated_by_rpc(method):
208
+ rpc_infos[method_name] = self._get_rpc_info(method)
209
+ self._rpc_infos[wf_type] = rpc_infos
@@ -0,0 +1,77 @@
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 dataclasses import dataclass
16
+ from typing import Optional
17
+
18
+ from dex.dex_api.models import WorkflowResetType
19
+
20
+
21
+ @dataclass
22
+ class ResetWorkflowTypeAndOptions:
23
+ reset_type: WorkflowResetType
24
+ reason: str
25
+ history_event_id: Optional[int] = None
26
+ history_event_time: Optional[str] = None
27
+ state_id: Optional[str] = None
28
+ state_execution_id: Optional[str] = None
29
+ skip_signal_reapply: Optional[bool] = None
30
+
31
+
32
+ def reset_to_beginning(reason: str) -> ResetWorkflowTypeAndOptions:
33
+ return ResetWorkflowTypeAndOptions(
34
+ reset_type=WorkflowResetType.BEGINNING,
35
+ reason=reason,
36
+ )
37
+
38
+
39
+ def reset_to_history_event_id(
40
+ history_event_id: int,
41
+ reason: str,
42
+ ) -> ResetWorkflowTypeAndOptions:
43
+ return ResetWorkflowTypeAndOptions(
44
+ reset_type=WorkflowResetType.HISTORY_EVENT_ID,
45
+ history_event_id=history_event_id,
46
+ reason=reason,
47
+ )
48
+
49
+
50
+ def reset_to_history_event_time(
51
+ history_event_time: str,
52
+ reason: str,
53
+ ) -> ResetWorkflowTypeAndOptions:
54
+ return ResetWorkflowTypeAndOptions(
55
+ reset_type=WorkflowResetType.HISTORY_EVENT_TIME,
56
+ history_event_time=history_event_time,
57
+ reason=reason,
58
+ )
59
+
60
+
61
+ def reset_to_state_id(state_id: str, reason: str) -> ResetWorkflowTypeAndOptions:
62
+ return ResetWorkflowTypeAndOptions(
63
+ reset_type=WorkflowResetType.STATE_ID,
64
+ state_id=state_id,
65
+ reason=reason,
66
+ )
67
+
68
+
69
+ def reset_to_state_execution_id(
70
+ state_execution: str,
71
+ reason: str,
72
+ ) -> ResetWorkflowTypeAndOptions:
73
+ return ResetWorkflowTypeAndOptions(
74
+ reset_type=WorkflowResetType.STATE_EXECUTION_ID,
75
+ state_execution_id=state_execution,
76
+ reason=reason,
77
+ )
dex/rpc.py ADDED
@@ -0,0 +1,100 @@
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 dataclasses import dataclass
16
+ from functools import wraps
17
+ from inspect import signature
18
+ from typing import Callable, Optional
19
+
20
+ from dex.errors import WorkflowDefinitionError
21
+ from dex.dex_api.models import PersistenceLoadingPolicy, PersistenceLoadingType
22
+
23
+
24
+ @dataclass
25
+ class RPCInfo:
26
+ method_func: Callable
27
+ timeout_seconds: int
28
+ input_type: Optional[type] = None
29
+ data_attribute_loading_policy: Optional[PersistenceLoadingPolicy] = None
30
+ params_order: Optional[list] = (
31
+ None # store this so that the rpc can be invoked with correct parameters
32
+ )
33
+ bypass_caching_for_strong_consistency: bool = False
34
+
35
+
36
+ rpc_definition_err = WorkflowDefinitionError(
37
+ "an RPC must have at most 5 params: self, context:WorkflowContext, input:Any, persistence:Persistence, "
38
+ "communication:Communication, where input can be any type"
39
+ )
40
+
41
+
42
+ def rpc(
43
+ timeout_seconds: int = 10,
44
+ data_attribute_loading_policy: Optional[PersistenceLoadingPolicy] = None,
45
+ bypass_caching_for_strong_consistency: bool = False,
46
+ ):
47
+ def decorator(func):
48
+ # preserve the properties of the original function.
49
+ @wraps(func)
50
+ def wrapper(*args, **kwargs):
51
+ # TODO need to add type hint for decorated method
52
+ return func(*args, **kwargs)
53
+
54
+ wrapper._is_dex_rpc = True
55
+ rpc_info = RPCInfo(
56
+ method_func=func,
57
+ timeout_seconds=timeout_seconds,
58
+ data_attribute_loading_policy=data_attribute_loading_policy,
59
+ bypass_caching_for_strong_consistency=bypass_caching_for_strong_consistency,
60
+ )
61
+ params = signature(func).parameters
62
+
63
+ from inspect import _empty # ignored.
64
+ from dex.persistence import Persistence
65
+ from dex.workflow_context import WorkflowContext
66
+ from dex.communication import Communication
67
+
68
+ valid_param_types_exclude_input = {
69
+ _empty: True,
70
+ Persistence: True,
71
+ WorkflowContext: True,
72
+ Communication: True,
73
+ }
74
+ need_persistence = False
75
+ params_order = []
76
+ if len(params) > 5:
77
+ raise rpc_definition_err
78
+
79
+ has_input = False
80
+ for k, v in params.items():
81
+ if k != "self":
82
+ params_order.append(v.annotation)
83
+
84
+ if v.annotation == Persistence:
85
+ need_persistence = True
86
+ if v.annotation not in valid_param_types_exclude_input:
87
+ if not has_input:
88
+ has_input = True
89
+ rpc_info.input_type = v.annotation
90
+ else:
91
+ raise rpc_definition_err
92
+ if not need_persistence:
93
+ rpc_info.data_attribute_loading_policy = PersistenceLoadingPolicy(
94
+ persistence_loading_type=PersistenceLoadingType.LOAD_NONE
95
+ )
96
+ rpc_info.params_order = params_order
97
+ wrapper._rpc_info = rpc_info
98
+ return wrapper
99
+
100
+ return decorator