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.
- dex/__init__.py +14 -0
- dex/client.py +568 -0
- dex/client_options.py +34 -0
- dex/command_request.py +130 -0
- dex/command_results.py +116 -0
- dex/communication.py +141 -0
- dex/communication_schema.py +68 -0
- dex/data_attributes.py +75 -0
- dex/dexpb/__init__.py +1 -0
- dex/dexpb/dex_pb2.py +305 -0
- dex/dexpb/dex_pb2.pyi +1273 -0
- dex/dexpb/dex_pb2_grpc.py +1126 -0
- dex/errors.py +132 -0
- dex/object_encoder.py +818 -0
- dex/persistence.py +94 -0
- dex/persistence_options.py +24 -0
- dex/persistence_schema.py +58 -0
- dex/py.typed +1 -0
- dex/registry.py +209 -0
- dex/reset_workflow_type_and_options.py +77 -0
- dex/rpc.py +100 -0
- dex/search_attributes.py +189 -0
- dex/state_decision.py +161 -0
- dex/state_execution_locals.py +71 -0
- dex/state_movement.py +123 -0
- dex/state_schema.py +54 -0
- dex/stop_workflow_options.py +23 -0
- dex/tests/__init__.py +84 -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 +87 -0
- dex/tests/dex-service-env/dynamicconfig/README.md +39 -0
- dex/tests/dex-service-env/dynamicconfig/development-sql.yaml +6 -0
- dex/tests/dex-service-env/dynamicconfig/docker.yaml +0 -0
- dex/tests/test_abnormal_exit_workflow.py +48 -0
- dex/tests/test_basic_workflow.py +75 -0
- dex/tests/test_conditional_complete.py +56 -0
- dex/tests/test_describe_workflow.py +45 -0
- dex/tests/test_empty_data_decodes_properly.py +79 -0
- dex/tests/test_internal_channel.py +33 -0
- dex/tests/test_internal_channel_with_no_prefix_channel.py +46 -0
- dex/tests/test_persistence_data_attributes.py +67 -0
- dex/tests/test_persistence_search_attributes.py +132 -0
- dex/tests/test_persistence_state_execution_locals.py +43 -0
- dex/tests/test_rpc.py +69 -0
- dex/tests/test_rpc_with_memo.py +200 -0
- dex/tests/test_rpc_with_memo_duplicate_java_tests.py +122 -0
- dex/tests/test_signal.py +56 -0
- dex/tests/test_skip_wait_until.py +85 -0
- dex/tests/test_state_failure_recovery.py +33 -0
- dex/tests/test_timer.py +40 -0
- dex/tests/test_wait_for_state_execution_completion.py +58 -0
- dex/tests/test_workflow_errors.py +92 -0
- dex/tests/test_workflow_state_options.py +123 -0
- dex/tests/test_workflow_state_options_override.py +49 -0
- dex/tests/worker_server.py +74 -0
- dex/tests/workflows/abnormal_exit_workflow.py +48 -0
- dex/tests/workflows/basic_workflow.py +69 -0
- dex/tests/workflows/conditional_complete_workflow.py +101 -0
- dex/tests/workflows/describe_workflow.py +52 -0
- dex/tests/workflows/empty_data_workflow.py +51 -0
- dex/tests/workflows/internal_channel_workflow.py +137 -0
- dex/tests/workflows/internal_channel_workflow_with_no_prefix_channel.py +108 -0
- dex/tests/workflows/java_duplicate_rpc_memo_workflow.py +285 -0
- dex/tests/workflows/persistence_data_attributes_workflow.py +104 -0
- dex/tests/workflows/persistence_search_attributes_workflow.py +167 -0
- dex/tests/workflows/persistence_state_execution_local_workflow.py +69 -0
- dex/tests/workflows/recovery_workflow.py +90 -0
- dex/tests/workflows/rpc_memo_workflow.py +238 -0
- dex/tests/workflows/rpc_workflow.py +125 -0
- dex/tests/workflows/state_options_override_workflow.py +100 -0
- dex/tests/workflows/state_options_workflow.py +92 -0
- dex/tests/workflows/timer_workflow.py +52 -0
- dex/tests/workflows/wait_for_state_with_state_execution_id_workflow.py +77 -0
- dex/tests/workflows/wait_for_state_with_wait_for_key_workflow.py +78 -0
- dex/tests/workflows/wait_internal_channel_workflow.py +53 -0
- dex/tests/workflows/wait_signal_workflow.py +154 -0
- dex/type_store.py +105 -0
- dex/unregistered_client.py +595 -0
- dex/utils/__init__.py +14 -0
- dex/utils/dex_typing.py +31 -0
- dex/utils/persistence_utils.py +37 -0
- dex/worker_service.py +441 -0
- dex/workflow.py +86 -0
- dex/workflow_context.py +50 -0
- dex/workflow_info.py +21 -0
- dex/workflow_options.py +79 -0
- dex/workflow_state.py +134 -0
- dex/workflow_state_options.py +160 -0
- dex_python_sdk-0.0.1.dist-info/METADATA +140 -0
- dex_python_sdk-0.0.1.dist-info/RECORD +93 -0
- dex_python_sdk-0.0.1.dist-info/WHEEL +4 -0
- 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
|