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/workflow_state.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
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 abc import ABC
|
|
16
|
+
from typing import Generic, TypeVar, get_args
|
|
17
|
+
|
|
18
|
+
from dex.command_request import CommandRequest
|
|
19
|
+
from dex.command_results import CommandResults
|
|
20
|
+
from dex.communication import Communication
|
|
21
|
+
from dex.persistence import Persistence
|
|
22
|
+
from dex.state_decision import StateDecision
|
|
23
|
+
from dex.workflow_context import WorkflowContext
|
|
24
|
+
from dex.workflow_state_options import WorkflowStateOptions
|
|
25
|
+
|
|
26
|
+
T = TypeVar("T")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
not_implemented_error_msg = "This implementation shouldn't be invoked"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class WorkflowState(ABC, Generic[T]):
|
|
33
|
+
"""WorkflowState is the interface to define a workflow state."""
|
|
34
|
+
|
|
35
|
+
def wait_until(
|
|
36
|
+
self,
|
|
37
|
+
ctx: WorkflowContext,
|
|
38
|
+
input: T,
|
|
39
|
+
persistence: Persistence,
|
|
40
|
+
communication: Communication,
|
|
41
|
+
) -> CommandRequest:
|
|
42
|
+
"""
|
|
43
|
+
WaitUntil is the method to set up commands set up to wait for, before `execute` API is invoked.
|
|
44
|
+
It's optional -- execute will be invoked instead if this is not implemented.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
ctx: the context info of this API invocation, like workflow start time, workflowId, etc
|
|
48
|
+
input: input: the state input
|
|
49
|
+
persistence: the API for
|
|
50
|
+
1) data attributes: defined by ObjectWorkflow interface
|
|
51
|
+
2) search attributes: defined by ObjectWorkflow interface
|
|
52
|
+
3) stateExecutionLocals: for passing data within the state execution
|
|
53
|
+
4) recordEvent: for storing some tracking info(e.g. RPC call input/output) when executing the API.
|
|
54
|
+
Note that any write API will be recorded to server after the whole waitUntil API response is accepted
|
|
55
|
+
communication: the API right now only for publishing value to internalChannel
|
|
56
|
+
Note that any write API will be recorded to server after the whole waitUntil API response is accepted.
|
|
57
|
+
|
|
58
|
+
Returns: the requested command
|
|
59
|
+
"""
|
|
60
|
+
raise NotImplementedError(not_implemented_error_msg)
|
|
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
|
+
"""
|
|
71
|
+
Execute is the method to execute and decide what to do next. Invoke after commands from WaitUntil are completed, or there is WaitUntil is not implemented for the state.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
ctx: the context info of this API invocation, like workflow start time, workflowId, etc
|
|
75
|
+
input: the state input
|
|
76
|
+
command_results: the results of the command that executed by WaitUntil
|
|
77
|
+
persistence: the API for
|
|
78
|
+
1) data attributes: defined by ObjectWorkflow interface
|
|
79
|
+
2) search attributes: defined by ObjectWorkflow interface
|
|
80
|
+
3) stateExecutionLocals: for passing data within the state execution
|
|
81
|
+
4) recordEvent: for storing some tracking info(e.g. RPC call input/output) when executing the API.
|
|
82
|
+
Note that any write API will be recorded to server after the whole waitUntil API response is accepted
|
|
83
|
+
communication: the API right now only for publishing value to internalChannel.
|
|
84
|
+
Note that any write API will be recorded to server after the whole execute API response is accepted.
|
|
85
|
+
|
|
86
|
+
Returns: the decision of what to do next(e.g. transition to next states or closing workflow)
|
|
87
|
+
"""
|
|
88
|
+
raise NotImplementedError(not_implemented_error_msg)
|
|
89
|
+
|
|
90
|
+
def get_state_options(self) -> WorkflowStateOptions:
|
|
91
|
+
"""GetStateOptions can just return nil to use the default Options
|
|
92
|
+
StateOptions is optional configuration to adjust the state behaviors. Default values:
|
|
93
|
+
StateId: name of the implementation class
|
|
94
|
+
waitUntilApiFailurePolicy: FAIL_WORKFLOW_ON_FAILURE
|
|
95
|
+
PersistenceLoadingPolicy for dataAttributes/searchAttributes: LOAD_ALL_WITHOUT_LOCKING
|
|
96
|
+
waitUntil/execute API:
|
|
97
|
+
timeout: 30s
|
|
98
|
+
retryPolicy:
|
|
99
|
+
InitialIntervalSeconds: 1
|
|
100
|
+
MaxInternalSeconds:100
|
|
101
|
+
MaximumAttempts: 0
|
|
102
|
+
BackoffCoefficient: 2
|
|
103
|
+
Returns: WorkflowStateOptions
|
|
104
|
+
"""
|
|
105
|
+
return WorkflowStateOptions()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_state_id(state: WorkflowState) -> str:
|
|
109
|
+
options = state.get_state_options()
|
|
110
|
+
if options is None or options.state_id is None:
|
|
111
|
+
return state.__class__.__name__
|
|
112
|
+
return options.state_id
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def get_state_id_by_class(state: type[WorkflowState]) -> str:
|
|
116
|
+
return state.__name__
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def should_skip_wait_until(state: WorkflowState) -> bool:
|
|
120
|
+
func_name = state.wait_until.__name__
|
|
121
|
+
parent_method = getattr(super(type(state), state), func_name)
|
|
122
|
+
return parent_method == state.wait_until
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_input_type(state):
|
|
126
|
+
bases = state.__orig_bases__
|
|
127
|
+
for b in bases:
|
|
128
|
+
if b.__origin__ == WorkflowState:
|
|
129
|
+
return get_args(b)[0]
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_state_execution_id(state: type[WorkflowState], number: int):
|
|
134
|
+
return f"{get_state_id_by_class(state)}-{number}"
|
|
@@ -0,0 +1,160 @@
|
|
|
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 Any, Optional
|
|
17
|
+
|
|
18
|
+
from dex.errors import WorkflowDefinitionError
|
|
19
|
+
from dex.dex_api.models import (
|
|
20
|
+
ExecuteApiFailurePolicy,
|
|
21
|
+
PersistenceLoadingPolicy,
|
|
22
|
+
RetryPolicy,
|
|
23
|
+
WaitUntilApiFailurePolicy,
|
|
24
|
+
WorkflowStateOptions as IdlWorkflowStateOptions,
|
|
25
|
+
)
|
|
26
|
+
from dex.dex_api.types import Unset
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class WorkflowStateOptions:
|
|
31
|
+
state_id: Optional[str] = None
|
|
32
|
+
# apply for both waitUntil and execute API
|
|
33
|
+
data_attributes_loading_policy: Optional[PersistenceLoadingPolicy] = None
|
|
34
|
+
search_attributes_loading_policy: Optional[PersistenceLoadingPolicy] = None
|
|
35
|
+
# below are wait_until API specific options:
|
|
36
|
+
wait_until_api_timeout_seconds: Optional[int] = None
|
|
37
|
+
wait_until_api_retry_policy: Optional[RetryPolicy] = None
|
|
38
|
+
"""
|
|
39
|
+
By default, workflow would fail after waitUntil API retry exhausted.
|
|
40
|
+
This policy to allow proceeding to the execute API after waitUntil API exhausted all retries.
|
|
41
|
+
This is useful for some advanced use cases like SAGA pattern.
|
|
42
|
+
RetryPolicy is required to be set with maximumAttempts or maximumAttemptsDurationSeconds for waitUntil API.
|
|
43
|
+
NOTE: execute API will use commandResults to check whether the waitUntil has succeeded or not.
|
|
44
|
+
See more in <a href="https://github.com/superdurable/dex/wiki/WorkflowStateOptions">wiki</a>
|
|
45
|
+
"""
|
|
46
|
+
proceed_to_execute_when_wait_until_retry_exhausted: Optional[
|
|
47
|
+
WaitUntilApiFailurePolicy
|
|
48
|
+
] = None
|
|
49
|
+
wait_until_api_data_attributes_loading_policy: Optional[
|
|
50
|
+
PersistenceLoadingPolicy
|
|
51
|
+
] = None
|
|
52
|
+
wait_until_api_search_attributes_loading_policy: Optional[
|
|
53
|
+
PersistenceLoadingPolicy
|
|
54
|
+
] = None
|
|
55
|
+
# below are execute API specific options:
|
|
56
|
+
execute_api_timeout_seconds: Optional[int] = None
|
|
57
|
+
execute_api_retry_policy: Optional[RetryPolicy] = None
|
|
58
|
+
"""
|
|
59
|
+
By default, workflow would fail after execute API retry exhausted.
|
|
60
|
+
Set the state to proceed to the specified state after the execute API exhausted all retries
|
|
61
|
+
This is useful for some advanced use cases like SAGA pattern.
|
|
62
|
+
RetryPolicy is required to be set with maximumAttempts or maximumAttemptsDurationSeconds for execute API.
|
|
63
|
+
Note that the failure handling state will take the same input as the failed from state.
|
|
64
|
+
TODO the type should be the type is Optional[type[WorkflowState]] but -- there is an issue with circular import...
|
|
65
|
+
"""
|
|
66
|
+
proceed_to_state_when_execute_retry_exhausted: Optional[type] = None
|
|
67
|
+
execute_api_data_attributes_loading_policy: Optional[PersistenceLoadingPolicy] = (
|
|
68
|
+
None
|
|
69
|
+
)
|
|
70
|
+
execute_api_search_attributes_loading_policy: Optional[PersistenceLoadingPolicy] = (
|
|
71
|
+
None
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _to_idl_state_options(
|
|
76
|
+
skip_wait_until: bool,
|
|
77
|
+
options: Any, # TODO this type was Optional[WorkflowStateOptions],
|
|
78
|
+
# however, type hint is not working with recursive call...
|
|
79
|
+
state_store: dict[str, Any], # TODO this type should be dict[str, WorkflowState]
|
|
80
|
+
) -> IdlWorkflowStateOptions:
|
|
81
|
+
res = IdlWorkflowStateOptions()
|
|
82
|
+
if skip_wait_until:
|
|
83
|
+
res.skip_wait_until = True
|
|
84
|
+
|
|
85
|
+
if options is None:
|
|
86
|
+
return res
|
|
87
|
+
assert isinstance(options, WorkflowStateOptions)
|
|
88
|
+
|
|
89
|
+
if options.wait_until_api_search_attributes_loading_policy is not None:
|
|
90
|
+
res.wait_until_api_search_attributes_loading_policy = (
|
|
91
|
+
options.wait_until_api_search_attributes_loading_policy
|
|
92
|
+
)
|
|
93
|
+
if options.execute_api_search_attributes_loading_policy is not None:
|
|
94
|
+
res.execute_api_search_attributes_loading_policy = (
|
|
95
|
+
options.execute_api_search_attributes_loading_policy
|
|
96
|
+
)
|
|
97
|
+
if options.search_attributes_loading_policy is not None:
|
|
98
|
+
res.search_attributes_loading_policy = options.search_attributes_loading_policy
|
|
99
|
+
if options.wait_until_api_data_attributes_loading_policy is not None:
|
|
100
|
+
res.wait_until_api_data_attributes_loading_policy = (
|
|
101
|
+
options.wait_until_api_data_attributes_loading_policy
|
|
102
|
+
)
|
|
103
|
+
if options.execute_api_data_attributes_loading_policy is not None:
|
|
104
|
+
res.execute_api_data_attributes_loading_policy = (
|
|
105
|
+
options.execute_api_data_attributes_loading_policy
|
|
106
|
+
)
|
|
107
|
+
if options.data_attributes_loading_policy is not None:
|
|
108
|
+
res.data_attributes_loading_policy = options.data_attributes_loading_policy
|
|
109
|
+
if options.proceed_to_execute_when_wait_until_retry_exhausted is not None:
|
|
110
|
+
res.wait_until_api_failure_policy = (
|
|
111
|
+
options.proceed_to_execute_when_wait_until_retry_exhausted
|
|
112
|
+
)
|
|
113
|
+
if options.wait_until_api_retry_policy is None:
|
|
114
|
+
raise WorkflowDefinitionError("wait_until API retry policy must be set")
|
|
115
|
+
if isinstance(
|
|
116
|
+
options.wait_until_api_retry_policy.maximum_attempts, Unset
|
|
117
|
+
) and isinstance(
|
|
118
|
+
options.wait_until_api_retry_policy.maximum_attempts_duration_seconds, Unset
|
|
119
|
+
):
|
|
120
|
+
raise WorkflowDefinitionError(
|
|
121
|
+
"wait_until API retry policy must be set with maximum_attempts or maximum_attempts_duration_seconds"
|
|
122
|
+
)
|
|
123
|
+
if options.wait_until_api_retry_policy is not None:
|
|
124
|
+
res.wait_until_api_retry_policy = options.wait_until_api_retry_policy
|
|
125
|
+
if options.wait_until_api_timeout_seconds is not None:
|
|
126
|
+
res.wait_until_api_timeout_seconds = options.wait_until_api_timeout_seconds
|
|
127
|
+
if options.execute_api_retry_policy is not None:
|
|
128
|
+
res.execute_api_retry_policy = options.execute_api_retry_policy
|
|
129
|
+
if options.execute_api_timeout_seconds is not None:
|
|
130
|
+
res.execute_api_timeout_seconds = options.execute_api_timeout_seconds
|
|
131
|
+
if options.proceed_to_state_when_execute_retry_exhausted is not None:
|
|
132
|
+
res.execute_api_failure_policy = (
|
|
133
|
+
ExecuteApiFailurePolicy.PROCEED_TO_CONFIGURED_STATE
|
|
134
|
+
)
|
|
135
|
+
if options.execute_api_retry_policy is None:
|
|
136
|
+
raise WorkflowDefinitionError("execute API retry policy must be set")
|
|
137
|
+
if isinstance(
|
|
138
|
+
options.execute_api_retry_policy.maximum_attempts, Unset
|
|
139
|
+
) and isinstance(
|
|
140
|
+
options.execute_api_retry_policy.maximum_attempts_duration_seconds, Unset
|
|
141
|
+
):
|
|
142
|
+
raise WorkflowDefinitionError(
|
|
143
|
+
"execute API retry policy must be set with maximum_attempts or maximum_attempts_duration_seconds"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
from dex.workflow_state import get_state_id_by_class
|
|
147
|
+
|
|
148
|
+
res.execute_api_failure_proceed_state_id = get_state_id_by_class(
|
|
149
|
+
options.proceed_to_state_when_execute_retry_exhausted
|
|
150
|
+
)
|
|
151
|
+
state = state_store[res.execute_api_failure_proceed_state_id]
|
|
152
|
+
proceed_state_options = state.get_state_options()
|
|
153
|
+
|
|
154
|
+
from dex.workflow_state import should_skip_wait_until
|
|
155
|
+
|
|
156
|
+
proceed_state_idl_options = _to_idl_state_options(
|
|
157
|
+
should_skip_wait_until(state), proceed_state_options, state_store
|
|
158
|
+
)
|
|
159
|
+
res.execute_api_failure_proceed_state_options = proceed_state_idl_options
|
|
160
|
+
return res
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dex-python-sdk
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python SDK for the Dex workflow engine
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: Super Durable
|
|
7
|
+
Requires-Python: >=3.9,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: grpcio (>=1.60)
|
|
16
|
+
Requires-Dist: httpx (==0.28.1)
|
|
17
|
+
Requires-Dist: protobuf (>=4.25)
|
|
18
|
+
Requires-Dist: python-dateutil (>=2.8.2,<3.0.0) ; python_version < "3.11"
|
|
19
|
+
Project-URL: Homepage, https://github.com/superdurable/dex/tree/main/sdk-python
|
|
20
|
+
Project-URL: Repository, https://github.com/superdurable/dex
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# dex-python-sdk
|
|
25
|
+
|
|
26
|
+
Python SDK for [Dex workflow engine](https://github.com/superdurable/dex)
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
pip install dex-python-sdk==0.0.1
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
See [samples](../examples/python) for use case examples.
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
- Python 3.9+
|
|
37
|
+
- [Dex server](https://github.com/superdurable/dex#how-to-use)
|
|
38
|
+
|
|
39
|
+
## Concepts
|
|
40
|
+
|
|
41
|
+
To implement a workflow, the two most core interfaces are
|
|
42
|
+
|
|
43
|
+
* [Workflow interface](https://github.com/superdurable/dex/blob/main/sdk-python/dex/workflow.py)
|
|
44
|
+
defines the workflow definition
|
|
45
|
+
|
|
46
|
+
* [WorkflowState interface](https://github.com/superdurable/dex/blob/main/sdk-python/dex/workflow_state.py)
|
|
47
|
+
defines the workflow states for workflow definitions
|
|
48
|
+
|
|
49
|
+
A workflow can contain any number of WorkflowStates.
|
|
50
|
+
|
|
51
|
+
See more in https://github.com/superdurable/dex#what-is-dex
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Development Plan
|
|
55
|
+
|
|
56
|
+
## 1.0 -- the basic and most frequently needed features
|
|
57
|
+
- [x] Start workflow API
|
|
58
|
+
- [x] Executing `wait_until`/`execute` APIs and completing workflow
|
|
59
|
+
- [x] Parallel execution of multiple states
|
|
60
|
+
- [x] GetWorkflowResultsWithWait API
|
|
61
|
+
- [x] StateOption: WaitUntil(optional)/Execute API timeout and retry policy
|
|
62
|
+
- [x] Get workflow with wait API
|
|
63
|
+
- [x] Timer command
|
|
64
|
+
- [x] AnyCommandCompleted and AllCommandCompleted waitingType
|
|
65
|
+
- [x] InternalChannel command
|
|
66
|
+
- [x] DataAttribute
|
|
67
|
+
- [x] Stop workflow API
|
|
68
|
+
- [x] Improve workflow uncompleted error return(canceled, failed, timeout, terminated)
|
|
69
|
+
- [x] Support execute API failure policy
|
|
70
|
+
- [x] Support workflow RPC
|
|
71
|
+
- [x] Signal command
|
|
72
|
+
- [x] Reset workflow API
|
|
73
|
+
- [x] Skip timer API for testing/operation
|
|
74
|
+
|
|
75
|
+
### Running dex-server locally
|
|
76
|
+
|
|
77
|
+
#### Option 1: use docker compose
|
|
78
|
+
See [dex README](https://github.com/superdurable/dex#using-docker-image--docker-compose)
|
|
79
|
+
|
|
80
|
+
#### Option 2: VSCode Dev Container
|
|
81
|
+
|
|
82
|
+
Dev Container is an easy way to get dex-server running locally. Follow these steps to launch a dev container:
|
|
83
|
+
- Install Docker, VSCode, and [VSCode Dev Container plugin](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers).
|
|
84
|
+
- Open the project in VSCode.
|
|
85
|
+
```bash
|
|
86
|
+
cd dex-python-sdk
|
|
87
|
+
code .
|
|
88
|
+
```
|
|
89
|
+
- Launch the Remote-Containers: Reopen in Container command from Command Palette (Ctrl + Shift + P). You can also click in the bottom left corner to access the remote container menu.
|
|
90
|
+
- Once the dev container starts, dex-server will be listening on port 8801.
|
|
91
|
+
|
|
92
|
+
## How To Contribute
|
|
93
|
+
|
|
94
|
+
This project uses [Poetry](https://python-poetry.org/) as a dependency manager. Check out Poetry's [documentation on how to install it](https://python-poetry.org/docs/#installing-with-the-official-installer) on your system before proceeding.
|
|
95
|
+
|
|
96
|
+
> ❗Note: If you use Conda or Pyenv as your environment / package manager, avoid dependency conflicts by doing the following first:
|
|
97
|
+
1. Before installing Poetry, create and activate a new Conda env (e.g. conda create -n langchain python=3.9)
|
|
98
|
+
2. Install Poetry (see above)
|
|
99
|
+
3. Tell Poetry to use the virtualenv python environment (poetry config virtualenvs.prefer-active-python true)
|
|
100
|
+
4. Continue with the following steps.
|
|
101
|
+
|
|
102
|
+
To install requirements:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
poetry install
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Update IDL
|
|
109
|
+
|
|
110
|
+
Edit [`protos/dex.proto`](../protos/dex.proto). Rename catalog: [`docs/design/idl-renames.md`](../docs/design/idl-renames.md).
|
|
111
|
+
|
|
112
|
+
#### Generate stubs from IDL
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
make -C ../protos proto
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Checked-in Python stubs land in `dex/dexpb/`.
|
|
119
|
+
#### Linting
|
|
120
|
+
|
|
121
|
+
To run linting for this project:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
poetry run pre-commit run --show-diff-on-failure --color=always --all-files
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Code of Conduct
|
|
128
|
+
This project is governed by the [Contributor Covenant v 1.4.1](CODE_OF_CONDUCT.md). (Review the Code of Conduct and remove this sentence before publishing your project.)
|
|
129
|
+
|
|
130
|
+
## Publishing to PyPI
|
|
131
|
+
|
|
132
|
+
1. Bump `version` in `pyproject.toml` (and the `pip install` line above).
|
|
133
|
+
2. Create a GitHub Release with tag `sdk-python-vX.Y.Z` (for example `sdk-python-v0.0.1`), or run the **Publish Python SDK to PyPI** workflow manually.
|
|
134
|
+
3. CI runs `poetry publish --build` using the `PYPI_TOKEN` repository secret.
|
|
135
|
+
|
|
136
|
+
See [CONTRIBUTING.md](../CONTRIBUTING.md#releases-monorepo-tags) for monorepo tag conventions.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
This project uses the [Apache 2.0](LICENSE) license. (Update this and the LICENSE file if your project uses a different license.)
|
|
140
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
dex/__init__.py,sha256=JSqLdxV-BbOchWw40dm6Q_gTFs7Dxw0Um_covA_zvAI,593
|
|
2
|
+
dex/client.py,sha256=2sxUocj4Wtv4wWwG8w3FA2Q1nVfkFFBmuk14aknDuwA,22122
|
|
3
|
+
dex/client_options.py,sha256=s8rBPSTRyWxtrTXFsSq8sXMc8hjbtD53h9uYw2e5rmo,1089
|
|
4
|
+
dex/command_request.py,sha256=0mB5mDXune0Foogu4uZldlNnWZHl_st-Cpg4JHWkG60,4412
|
|
5
|
+
dex/command_results.py,sha256=V9X87kkopRJUDp0KU7uKDKlNjakS2t27_sQVZoLYLI0,3892
|
|
6
|
+
dex/communication.py,sha256=YaIOzTFxIB5Mgh9GTB9oi2Vw8iUn5IxCl4zPK_7CKcs,5524
|
|
7
|
+
dex/communication_schema.py,sha256=AxY6Uis8HvH9NoSIKTPl_auyettus9akvxQ49RkrULs,2095
|
|
8
|
+
dex/data_attributes.py,sha256=QDaTKB8JSxKWdz14DMi_qaPfW0JORb9lgxipy6VziKg,2827
|
|
9
|
+
dex/dexpb/__init__.py,sha256=yuJqMfBdU6TScp7CLIEeOWNEBFrfL2pcqsGzRawW-A0,44
|
|
10
|
+
dex/dexpb/dex_pb2.py,sha256=-Q4dieYhO55Z-HLE_Q-Xu4psNmYp0-_dZoHZxG3s6BQ,46361
|
|
11
|
+
dex/dexpb/dex_pb2.pyi,sha256=smmH3kPj1cYhZmu260hE1Bi2_baR9VCalkNnQ6-5aP0,70023
|
|
12
|
+
dex/dexpb/dex_pb2_grpc.py,sha256=H5YdOefAzW5cc01phqtHWsEi1yPWuyKxvhMmHis8y-E,45638
|
|
13
|
+
dex/errors.py,sha256=UkS3SKIEfuDnJPH65P3ggzuIjnOzf2F8a49NmaNqIoA,3613
|
|
14
|
+
dex/object_encoder.py,sha256=m6I7uCE8TA8Btk_se-bzzAJc39joMiPC7kGdFpcTEHI,27411
|
|
15
|
+
dex/persistence.py,sha256=LomP7lfCiNspDbdcI-OEkX4cwJRyk-Y3hBTizJRSVtA,4064
|
|
16
|
+
dex/persistence_options.py,sha256=ZWt4nY6RYX08j8wVT35A55Qh3DMtL27c4l_8shwPQws,776
|
|
17
|
+
dex/persistence_schema.py,sha256=sJgw5nNTP7DRGFFaIc9TpiDJMBpPORchaBnTScWuumI,1861
|
|
18
|
+
dex/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26
|
|
19
|
+
dex/registry.py,sha256=Z_Z9O0Hn1Ax5-FTTdQodADZA75qR3LcaOEqdI4TZ71Q,8813
|
|
20
|
+
dex/reset_workflow_type_and_options.py,sha256=V1bL6B0XHMnlTIZKiviYmx2Doxyq_Pzpp3ChfpCNvA0,2280
|
|
21
|
+
dex/rpc.py,sha256=K8EwgpvDMHxXT-IuA_IGIPHZ2Jhw4HtyerS8oJPTiFA,3553
|
|
22
|
+
dex/search_attributes.py,sha256=CSNNzDkNi7Yq_5wZ0NnPo_DA-dwSHZ4QTLfUh2MmA8Y,8550
|
|
23
|
+
dex/state_decision.py,sha256=P3kWpmqpTYd82k1zo1_-f8RcFZydvhNzEjRaOZFEGng,6501
|
|
24
|
+
dex/state_execution_locals.py,sha256=ON6CDJe4gi-RoUQL1wGRWUNjbbA3QWuop6vblG-v4hY,2889
|
|
25
|
+
dex/state_movement.py,sha256=CAxlxeijWo9LML2dSKGYEuhyPcsV1o7DdetfD-naPOY,4050
|
|
26
|
+
dex/state_schema.py,sha256=jimTJD6uNmTJBDxfICsIieo36tCKmJjZea1rnJQPbbY,1734
|
|
27
|
+
dex/stop_workflow_options.py,sha256=Cd_Hp4xpjwW-R0ouEqesYxfyuOLckekD6UFRkeQ-ojY,773
|
|
28
|
+
dex/tests/__init__.py,sha256=traifsy3IF7s41O3D9ywZ2I2F5UF7qgdItYeq2nLDIw,3687
|
|
29
|
+
dex/tests/dex-service-env/.env,sha256=43l3DVR4gBlAA179GXotv7ilxsYCJBCrTzqL3YVKRKs,200
|
|
30
|
+
dex/tests/dex-service-env/docker-compose-init.sh,sha256=Zv25Gb85j-VXzO8Jl-H2L9btLk0IF2T9DaMK4cjGZW8,1737
|
|
31
|
+
dex/tests/dex-service-env/docker-compose.yml,sha256=-JOqORirlaX3YYpGP9rSNjSKMwU-Cr6OUuR1fOVk-xc,2519
|
|
32
|
+
dex/tests/dex-service-env/dynamicconfig/README.md,sha256=33FXQSiJAt7gvxWG7pUL9EddwYgPLpsLZaOUMIL70P4,1132
|
|
33
|
+
dex/tests/dex-service-env/dynamicconfig/development-sql.yaml,sha256=NcBfj7uKr2_Is3I32eKkTNI_gG2xuIUdrtm7vJy-uCo,189
|
|
34
|
+
dex/tests/dex-service-env/dynamicconfig/docker.yaml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
+
dex/tests/test_abnormal_exit_workflow.py,sha256=Iu7TQKAgbVW6_jKdknq2-yZl7p-xMDbe0xPRRQ0n9Mw,1897
|
|
36
|
+
dex/tests/test_basic_workflow.py,sha256=eurJwEKaM4T-zYbB6eQYCmtoCrLJF1o1IB3SfF8gdj4,2597
|
|
37
|
+
dex/tests/test_conditional_complete.py,sha256=W26OPknuXETBvDX8xD5LUHGVgwBVUAju6iDXxPsyois,1965
|
|
38
|
+
dex/tests/test_describe_workflow.py,sha256=oUv6ZmQcYNmUZrEWLxkXc6w1emYxYceZhgbEC5VCWYY,1629
|
|
39
|
+
dex/tests/test_empty_data_decodes_properly.py,sha256=GjoIDZ404E3LhN3edXalsCjqMzVy8Tb7eOKh_tHpB2c,2813
|
|
40
|
+
dex/tests/test_internal_channel.py,sha256=9EhrXxtp9FeWxub6vGFvJKe7HTMgKVO-DbRq-SDatTQ,1190
|
|
41
|
+
dex/tests/test_internal_channel_with_no_prefix_channel.py,sha256=VOD0CTH_w7zb0dFrIOudRqRjV_QuK_ITFABBCCunn8E,1687
|
|
42
|
+
dex/tests/test_persistence_data_attributes.py,sha256=AQC6Fsyctq3RRpw4maqJuzefag8eLgUgnOWQ5c3m1Co,2222
|
|
43
|
+
dex/tests/test_persistence_search_attributes.py,sha256=sXX44F302umG18EzC8cC5OwvuKxwRi889IXVuG_IKIc,5212
|
|
44
|
+
dex/tests/test_persistence_state_execution_locals.py,sha256=MlD9rlWPGn9tLVyYDCa629mMVbSWb18w9edyzOX2dIQ,1597
|
|
45
|
+
dex/tests/test_rpc.py,sha256=d4R_G_xaoW2siML83i24MqG80EDLHFFqTmAS6IVSpUg,2576
|
|
46
|
+
dex/tests/test_rpc_with_memo.py,sha256=s1mZpZPdJrJEc-uqpYLFEtRGIUA4IhaDndSjxdjyX00,7881
|
|
47
|
+
dex/tests/test_rpc_with_memo_duplicate_java_tests.py,sha256=406yuj_hf8MMStTjsGhU0OyY-dEh1E429_IJvqDSzis,4371
|
|
48
|
+
dex/tests/test_signal.py,sha256=K71A0Lt3KLkYhpTEww-alz_ZA_Tss30dW3gtRPnuIm8,2068
|
|
49
|
+
dex/tests/test_skip_wait_until.py,sha256=amkRcZePE3fadyXwzq84UM_8Z3lvLIJjLnJ_dERqRRA,2584
|
|
50
|
+
dex/tests/test_state_failure_recovery.py,sha256=GsjWlHbMml5xO3Lsy94wv-tzDA682nGwMdLVi4e3PdE,1173
|
|
51
|
+
dex/tests/test_timer.py,sha256=T_PV1df4JvVE4aUbdarwnMbpFkzNP-NRQVZAmUmsKAU,1439
|
|
52
|
+
dex/tests/test_wait_for_state_execution_completion.py,sha256=HLA45OeVeq89TaxhbZoueloss-BHpGFKaShMdbZn2uY,2139
|
|
53
|
+
dex/tests/test_workflow_errors.py,sha256=uv57RK_u6wpQ1UvsUa_gZe2t5h9F0BrqKJm37OD1pkI,3684
|
|
54
|
+
dex/tests/test_workflow_state_options.py,sha256=_R9c5cvrIBi2wya5BKz43q2_kcVkyj-pJKrR7Na_3iQ,4777
|
|
55
|
+
dex/tests/test_workflow_state_options_override.py,sha256=-D5bzf247tlosVcOvD5e6KDEIV51kMZa2ETp2q2Y44k,1609
|
|
56
|
+
dex/tests/worker_server.py,sha256=piNxQVD5gFTUjtRgoPmuvhME9tdzDuhEsYeHo7NXCEY,2382
|
|
57
|
+
dex/tests/workflows/abnormal_exit_workflow.py,sha256=Qj-MSjAihdK-Zwyy2RR6vTNVZZYHusvg664RQbf1KO8,1741
|
|
58
|
+
dex/tests/workflows/basic_workflow.py,sha256=jJmFaGcheETabPI-Nm4ZmT5eZxZ4KQQWcrmCE2faRmk,2277
|
|
59
|
+
dex/tests/workflows/conditional_complete_workflow.py,sha256=XQCQB_vb526iDJHQM5nCdSyI6nVtlBpOmZgPOYRhs24,3574
|
|
60
|
+
dex/tests/workflows/describe_workflow.py,sha256=gecikkSYgYqFnuRBYSm2fVutKs1B3DsYHY-inNCrsZo,1763
|
|
61
|
+
dex/tests/workflows/empty_data_workflow.py,sha256=xA2Wc_AKU1HHEl_iKgZCoA4Ou1pC8etFWu9sw7WxfxI,1822
|
|
62
|
+
dex/tests/workflows/internal_channel_workflow.py,sha256=g0YGQGe7cK8KxrdRJituP47-OjOyv56rpbwf02wp0l0,5033
|
|
63
|
+
dex/tests/workflows/internal_channel_workflow_with_no_prefix_channel.py,sha256=HovlMxQic4V2gdDz0y2j3Y4zAimYRY9wmnyluEmK284,3949
|
|
64
|
+
dex/tests/workflows/java_duplicate_rpc_memo_workflow.py,sha256=dXYNXXxpLyGaW6d9A5dyUgjVMoJ4VPvp0D2pQ4M393A,9993
|
|
65
|
+
dex/tests/workflows/persistence_data_attributes_workflow.py,sha256=dkHWhDQs1fNHyMNWqjpiGdcK3VMJ6LJNxc6X-nmw7tI,3914
|
|
66
|
+
dex/tests/workflows/persistence_search_attributes_workflow.py,sha256=b6I-s82WJ6rWa9PjY5rUTRI-N6Ven2A_9e5VT1VO7Aw,6073
|
|
67
|
+
dex/tests/workflows/persistence_state_execution_local_workflow.py,sha256=KlCrbKoGdLqvDiB_99fyyK_4XAk5yR1SZfTcQACOnNQ,2601
|
|
68
|
+
dex/tests/workflows/recovery_workflow.py,sha256=0gKl6gRE0NMb8CGzOyrEAABzhjSbgI8_48YSNJwza8o,3135
|
|
69
|
+
dex/tests/workflows/rpc_memo_workflow.py,sha256=7_q_2X6N6SC7RXS-TQ1ve_ibdypz2n8-bwsHy6_7slk,8500
|
|
70
|
+
dex/tests/workflows/rpc_workflow.py,sha256=KfnLaejPNA8yPN1w7cDvfNdamemriT2SYWNia157C0I,4081
|
|
71
|
+
dex/tests/workflows/state_options_override_workflow.py,sha256=i6XLw_e0gDafOi7xZUjl7-XEXbSro4jb4G0cJTlBp8w,3512
|
|
72
|
+
dex/tests/workflows/state_options_workflow.py,sha256=sjPRdA9qCY8mlrzgowfCKB2OYp-KqGdvDT4Fexn4J1E,3195
|
|
73
|
+
dex/tests/workflows/timer_workflow.py,sha256=I8bIUcPyBKp9lKc3OZsRdfyOE-hCrYfaTUfc-iz5fUY,1814
|
|
74
|
+
dex/tests/workflows/wait_for_state_with_state_execution_id_workflow.py,sha256=lqtvsjxmdWBER-vjVk8QkhkgjA9VsgpujvrxqeY2iqo,2579
|
|
75
|
+
dex/tests/workflows/wait_for_state_with_wait_for_key_workflow.py,sha256=1WaDpViPmfGZndJnJ3ZWjR_z_L3k-La-jlFhsK89brk,2575
|
|
76
|
+
dex/tests/workflows/wait_internal_channel_workflow.py,sha256=10oAJDszGbwZGp9QTO0q0wSrtXQwqfOMN70-5d8ywXI,1837
|
|
77
|
+
dex/tests/workflows/wait_signal_workflow.py,sha256=TrkrN7qqJKy5Jw5pjOwLOk7mOt1bQULdtd7HYHXzeao,5515
|
|
78
|
+
dex/type_store.py,sha256=DRfCSgHLY-DzBP_5IEJg5hlMZA46jUAstdk312e6nnI,3506
|
|
79
|
+
dex/unregistered_client.py,sha256=vX6TDNEuBfV3UAM5uURA8CqU9eEXxkuDLmW18RkUNh0,22210
|
|
80
|
+
dex/utils/__init__.py,sha256=JSqLdxV-BbOchWw40dm6Q_gTFs7Dxw0Um_covA_zvAI,593
|
|
81
|
+
dex/utils/dex_typing.py,sha256=aOlVmpBJqIHMbUt-0xOWJgnG2n7YAlS5gAOgrhRKudc,984
|
|
82
|
+
dex/utils/persistence_utils.py,sha256=8L8aa3Fn-f_7kuwn7BYFSdX42NMQZY_TUsEoQOSFtDc,1568
|
|
83
|
+
dex/worker_service.py,sha256=CT6m-ukPCFj78rblIHqnj335rIQveTcWOStGpkwD1Ag,16451
|
|
84
|
+
dex/workflow.py,sha256=rGOReD_OljAZgjngJJbTt-DnAW4FlWRNjGCEk245SD4,3769
|
|
85
|
+
dex/workflow_context.py,sha256=j8URlU56x2a6_i-LKOVhLfuMdU1pg8azttMrqVMxETE,1810
|
|
86
|
+
dex/workflow_info.py,sha256=0QCvmPpD9moWyDYOn7OsaP6YNfI8F74pB7EQ18BG9Ls,758
|
|
87
|
+
dex/workflow_options.py,sha256=zbUZGbmIANX6owNskCkMRUdKGW7oyWts-_zEV2Ohfe4,3096
|
|
88
|
+
dex/workflow_state.py,sha256=Dlhs11-v9L8lV9m6LKILLw2H_V73hBfJFPkMNy8NiDc,5715
|
|
89
|
+
dex/workflow_state_options.py,sha256=o8I5p_qSch8WdSrChhVWbxhRyxq07_R5ZV_EJO-VqsU,7606
|
|
90
|
+
dex_python_sdk-0.0.1.dist-info/METADATA,sha256=kDsZCd_ank2JRvFX18Kb1K317K_P_jM_pRDipn2iLxc,5103
|
|
91
|
+
dex_python_sdk-0.0.1.dist-info/WHEEL,sha256=EGEvSphFYqXKs23-kQBeyNoJP1nrT8ZJKQoi5p5DYL8,88
|
|
92
|
+
dex_python_sdk-0.0.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
93
|
+
dex_python_sdk-0.0.1.dist-info/RECORD,,
|