dex-python-sdk 0.0.2__cp311-abi3-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dex/__init__.py +146 -0
- dex/_grpc_errors.py +73 -0
- dex/_invocation_context.py +269 -0
- dex/_native.pyd +0 -0
- dex/_native.pyi +20 -0
- dex/_utils.py +18 -0
- dex/_value_hydrator.py +223 -0
- dex/_value_mapper.py +174 -0
- dex/_worker_dispatcher.py +451 -0
- dex/_worker_service.py +60 -0
- dex/attribute.py +87 -0
- dex/blob_cache.py +73 -0
- dex/channel.py +165 -0
- dex/client.py +715 -0
- dex/client_options.py +19 -0
- dex/codec.py +286 -0
- dex/command_request.py +120 -0
- dex/command_results.py +107 -0
- dex/communication.py +136 -0
- dex/communication_schema.py +54 -0
- dex/condition.py +74 -0
- dex/context.py +86 -0
- dex/data_attributes.py +70 -0
- dex/dexpb/__init__.py +1 -0
- dex/dexpb/dex_pb2.py +381 -0
- dex/dexpb/dex_pb2.pyi +1734 -0
- dex/dexpb/dex_pb2_grpc.py +1298 -0
- dex/errors.py +109 -0
- dex/flow.py +456 -0
- dex/flow_config.py +29 -0
- dex/flow_info.py +51 -0
- dex/flow_options.py +122 -0
- dex/object_encoder.py +799 -0
- dex/persistence.py +89 -0
- dex/persistence_options.py +12 -0
- dex/persistence_schema.py +51 -0
- dex/py.typed +1 -0
- dex/registry.py +204 -0
- dex/reset_workflow_type_and_options.py +67 -0
- dex/rpc.py +93 -0
- dex/runtime_errors.py +81 -0
- dex/search_attributes.py +184 -0
- dex/state_decision.py +153 -0
- dex/state_execution_locals.py +66 -0
- dex/state_movement.py +115 -0
- dex/state_schema.py +48 -0
- dex/step.py +194 -0
- dex/step_execution.py +42 -0
- dex/stop_workflow_options.py +18 -0
- dex/tests/__init__.py +80 -0
- dex/tests/dex-service-env/.env +7 -0
- dex/tests/dex-service-env/docker-compose-init.sh +44 -0
- dex/tests/dex-service-env/docker-compose.yml +97 -0
- dex/tests/dex-service-env/dynamicconfig/README.md +39 -0
- dex/tests/dex-service-env/dynamicconfig/development-sql.yaml +9 -0
- dex/tests/dex-service-env/dynamicconfig/docker.yaml +2 -0
- dex/tests/test_abnormal_exit_workflow.py +43 -0
- dex/tests/test_basic_workflow.py +70 -0
- dex/tests/test_conditional_complete.py +50 -0
- dex/tests/test_describe_workflow.py +40 -0
- dex/tests/test_empty_data_decodes_properly.py +74 -0
- dex/tests/test_internal_channel.py +28 -0
- dex/tests/test_internal_channel_with_no_prefix_channel.py +41 -0
- dex/tests/test_persistence_data_attributes.py +62 -0
- dex/tests/test_persistence_search_attributes.py +127 -0
- dex/tests/test_persistence_state_execution_locals.py +38 -0
- dex/tests/test_rpc.py +64 -0
- dex/tests/test_rpc_with_memo.py +195 -0
- dex/tests/test_rpc_with_memo_duplicate_java_tests.py +117 -0
- dex/tests/test_signal.py +51 -0
- dex/tests/test_skip_wait_until.py +76 -0
- dex/tests/test_state_failure_recovery.py +28 -0
- dex/tests/test_timer.py +35 -0
- dex/tests/test_wait_for_state_execution_completion.py +53 -0
- dex/tests/test_workflow_errors.py +87 -0
- dex/tests/test_workflow_state_options.py +118 -0
- dex/tests/test_workflow_state_options_override.py +44 -0
- dex/tests/worker_server.py +64 -0
- dex/tests/workflows/abnormal_exit_workflow.py +42 -0
- dex/tests/workflows/basic_workflow.py +62 -0
- dex/tests/workflows/conditional_complete_workflow.py +95 -0
- dex/tests/workflows/describe_workflow.py +46 -0
- dex/tests/workflows/empty_data_workflow.py +45 -0
- dex/tests/workflows/internal_channel_workflow.py +129 -0
- dex/tests/workflows/internal_channel_workflow_with_no_prefix_channel.py +100 -0
- dex/tests/workflows/java_duplicate_rpc_memo_workflow.py +276 -0
- dex/tests/workflows/persistence_data_attributes_workflow.py +98 -0
- dex/tests/workflows/persistence_search_attributes_workflow.py +159 -0
- dex/tests/workflows/persistence_state_execution_local_workflow.py +63 -0
- dex/tests/workflows/recovery_workflow.py +82 -0
- dex/tests/workflows/rpc_memo_workflow.py +231 -0
- dex/tests/workflows/rpc_workflow.py +117 -0
- dex/tests/workflows/state_options_override_workflow.py +93 -0
- dex/tests/workflows/state_options_workflow.py +84 -0
- dex/tests/workflows/timer_workflow.py +46 -0
- dex/tests/workflows/wait_for_state_with_state_execution_id_workflow.py +70 -0
- dex/tests/workflows/wait_for_state_with_wait_for_key_workflow.py +71 -0
- dex/tests/workflows/wait_internal_channel_workflow.py +47 -0
- dex/tests/workflows/wait_signal_workflow.py +147 -0
- dex/timer.py +21 -0
- dex/type_store.py +99 -0
- dex/unregistered_client.py +585 -0
- dex/utils/__init__.py +3 -0
- dex/utils/dex_typing.py +25 -0
- dex/utils/persistence_utils.py +32 -0
- dex/wait.py +49 -0
- dex/worker.py +121 -0
- dex/worker_options.py +22 -0
- dex/worker_service.py +432 -0
- dex/workflow.py +79 -0
- dex/workflow_context.py +44 -0
- dex/workflow_info.py +16 -0
- dex/workflow_options.py +74 -0
- dex/workflow_state.py +123 -0
- dex/workflow_state_options.py +154 -0
- dex_python_sdk-0.0.2.dist-info/METADATA +202 -0
- dex_python_sdk-0.0.2.dist-info/RECORD +121 -0
- dex_python_sdk-0.0.2.dist-info/WHEEL +4 -0
- dex_python_sdk-0.0.2.dist-info/licenses/LEGACY_NOTICES.md +61 -0
- dex_python_sdk-0.0.2.dist-info/licenses/LICENSE +192 -0
- dex_python_sdk-0.0.2.dist-info/sboms/dex-blob-cache-python.cyclonedx.json +2406 -0
dex/step.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
from dataclasses import dataclass, replace
|
|
15
|
+
from datetime import timedelta
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import Any, Generic, Iterator, TypeVar
|
|
18
|
+
|
|
19
|
+
from dex.attribute import AttributeLock
|
|
20
|
+
from dex.channel import Channel, ChannelMap
|
|
21
|
+
from dex.context import Context
|
|
22
|
+
from dex.wait import Wait
|
|
23
|
+
|
|
24
|
+
InputT = TypeVar("InputT")
|
|
25
|
+
StartT = TypeVar("StartT")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StepDurability(Enum):
|
|
29
|
+
DEFAULT = "default"
|
|
30
|
+
SYNC = "sync"
|
|
31
|
+
ASYNC = "async"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class WaitForFailurePolicy(Enum):
|
|
35
|
+
FAIL_FLOW = "fail_flow"
|
|
36
|
+
PROCEED = "proceed"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class RetryPolicy:
|
|
41
|
+
initial_interval: timedelta | None = None
|
|
42
|
+
backoff_coefficient: float = 0.0
|
|
43
|
+
maximum_interval: timedelta | None = None
|
|
44
|
+
maximum_attempts: int = 0
|
|
45
|
+
total_duration: timedelta | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class StepOptions:
|
|
50
|
+
wait_for_method_timeout: timedelta | None = None
|
|
51
|
+
execute_method_timeout: timedelta | None = None
|
|
52
|
+
wait_for_retry: RetryPolicy | None = None
|
|
53
|
+
execute_retry: RetryPolicy | None = None
|
|
54
|
+
wait_for_failure: WaitForFailurePolicy = WaitForFailurePolicy.FAIL_FLOW
|
|
55
|
+
wait_for_durability: StepDurability = StepDurability.DEFAULT
|
|
56
|
+
execute_durability: StepDurability = StepDurability.DEFAULT
|
|
57
|
+
wait_for_lock_attributes: tuple[AttributeLock, ...] = ()
|
|
58
|
+
execute_lock_attributes: tuple[AttributeLock, ...] = ()
|
|
59
|
+
_execute_failure_target: Step[Any] | None = None
|
|
60
|
+
_execute_failure_options: StepOptions | None = None
|
|
61
|
+
|
|
62
|
+
def on_execute_failure_proceed_to(
|
|
63
|
+
self,
|
|
64
|
+
step: Step[Any],
|
|
65
|
+
options: StepOptions | None = None,
|
|
66
|
+
) -> StepOptions:
|
|
67
|
+
return replace(
|
|
68
|
+
self,
|
|
69
|
+
_execute_failure_target=step,
|
|
70
|
+
_execute_failure_options=options,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Step(Generic[InputT], ABC):
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def execute(
|
|
77
|
+
self,
|
|
78
|
+
context: Context,
|
|
79
|
+
input: InputT,
|
|
80
|
+
) -> StepDecision:
|
|
81
|
+
raise NotImplementedError
|
|
82
|
+
|
|
83
|
+
def wait_for(self, context: Context, input: InputT) -> Wait:
|
|
84
|
+
del context, input
|
|
85
|
+
raise RuntimeError("framework must skip the default wait_for")
|
|
86
|
+
|
|
87
|
+
def get_step_type(self) -> str:
|
|
88
|
+
return type(self).__name__
|
|
89
|
+
|
|
90
|
+
def get_step_options(self) -> StepOptions | None:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class _StepDef:
|
|
96
|
+
step: Step[Any]
|
|
97
|
+
is_start_step: bool
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True)
|
|
101
|
+
class StepList(Generic[StartT]):
|
|
102
|
+
_definitions: tuple[_StepDef, ...]
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def empty(cls) -> StepList[StartT]:
|
|
106
|
+
return cls(())
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def start_step(step: Step[StartT]) -> StepList[StartT]:
|
|
110
|
+
return StepList((_StepDef(step, True),))
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def without_start_step(cls, *steps: Step[Any]) -> StepList[StartT]:
|
|
114
|
+
return cls(tuple(_StepDef(step, False) for step in steps))
|
|
115
|
+
|
|
116
|
+
def other_steps(self, *steps: Step[Any]) -> StepList[StartT]:
|
|
117
|
+
return StepList(
|
|
118
|
+
self._definitions + tuple(_StepDef(step, False) for step in steps)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def __iter__(self) -> Iterator[_StepDef]:
|
|
122
|
+
return iter(self._definitions)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class StepMovement(Generic[InputT]):
|
|
127
|
+
step: Step[InputT]
|
|
128
|
+
input: InputT
|
|
129
|
+
options: StepOptions | None = None
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def of(
|
|
133
|
+
step: Step[InputT],
|
|
134
|
+
input: InputT,
|
|
135
|
+
*,
|
|
136
|
+
options: StepOptions | None = None,
|
|
137
|
+
) -> StepMovement[InputT]:
|
|
138
|
+
return StepMovement(step, input, options)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class DecisionKind(Enum):
|
|
142
|
+
NEXT = "next"
|
|
143
|
+
GRACEFUL_COMPLETE = "graceful_complete"
|
|
144
|
+
FORCE_COMPLETE = "force_complete"
|
|
145
|
+
FORCE_COMPLETE_IF_CHANNELS_EMPTY = "force_complete_if_channels_empty"
|
|
146
|
+
FORCE_FAIL = "force_fail"
|
|
147
|
+
DEAD_END = "dead_end"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class StepDecision:
|
|
152
|
+
kind: DecisionKind
|
|
153
|
+
movements: tuple[StepMovement[Any], ...] = ()
|
|
154
|
+
output: object | None = None
|
|
155
|
+
reason: str = ""
|
|
156
|
+
empty_channels: tuple[Channel[Any] | ChannelMap[Any], ...] = ()
|
|
157
|
+
fallback: StepMovement[Any] | None = None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def go_to(step: Step[InputT], input: InputT) -> StepDecision:
|
|
161
|
+
return go_to_multi(StepMovement.of(step, input))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def go_to_multi(*movements: StepMovement[Any]) -> StepDecision:
|
|
165
|
+
return StepDecision(DecisionKind.NEXT, movements=movements)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def graceful_complete(output: object | None = None) -> StepDecision:
|
|
169
|
+
return StepDecision(DecisionKind.GRACEFUL_COMPLETE, output=output)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def force_complete(output: object | None = None) -> StepDecision:
|
|
173
|
+
return StepDecision(DecisionKind.FORCE_COMPLETE, output=output)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def force_complete_when_channels_empty(
|
|
177
|
+
output: object,
|
|
178
|
+
fallback: StepMovement[Any],
|
|
179
|
+
*channels: Channel[Any] | ChannelMap[Any],
|
|
180
|
+
) -> StepDecision:
|
|
181
|
+
return StepDecision(
|
|
182
|
+
DecisionKind.FORCE_COMPLETE_IF_CHANNELS_EMPTY,
|
|
183
|
+
output=output,
|
|
184
|
+
empty_channels=channels,
|
|
185
|
+
fallback=fallback,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def force_fail(reason: str) -> StepDecision:
|
|
190
|
+
return StepDecision(DecisionKind.FORCE_FAIL, reason=reason)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def dead_end() -> StepDecision:
|
|
194
|
+
return StepDecision(DecisionKind.DEAD_END)
|
dex/step_execution.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
from dex._utils import require_name
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class StepExecutionId:
|
|
20
|
+
step_type: str
|
|
21
|
+
number: int = 1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class TimerId:
|
|
26
|
+
condition_id: str | None = None
|
|
27
|
+
condition_index: int | None = None
|
|
28
|
+
|
|
29
|
+
def __post_init__(self) -> None:
|
|
30
|
+
if (self.condition_id is None) == (self.condition_index is None):
|
|
31
|
+
raise ValueError("TimerId requires exactly one selector")
|
|
32
|
+
if self.condition_index is not None and self.condition_index < 0:
|
|
33
|
+
raise ValueError("timer condition index must not be negative")
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def by_condition_id(condition_id: str) -> TimerId:
|
|
37
|
+
require_name(condition_id)
|
|
38
|
+
return TimerId(condition_id=condition_id)
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def by_condition_index(condition_index: int) -> TimerId:
|
|
42
|
+
return TimerId(condition_index=condition_index)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from dex.dex_api.models import WorkflowStopType
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class StopWorkflowOptions:
|
|
17
|
+
workflow_stop_type: WorkflowStopType
|
|
18
|
+
reason: str
|
dex/tests/__init__.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
from dex.registry import Registry
|
|
12
|
+
from dex.tests.workflows.abnormal_exit_workflow import AbnormalExitWorkflow
|
|
13
|
+
from dex.tests.workflows.basic_workflow import BasicWorkflow
|
|
14
|
+
from dex.tests.workflows.conditional_complete_workflow import (
|
|
15
|
+
ConditionalCompleteWorkflow,
|
|
16
|
+
)
|
|
17
|
+
from dex.tests.workflows.describe_workflow import DescribeWorkflow
|
|
18
|
+
from dex.tests.workflows.empty_data_workflow import EmptyDataWorkflow
|
|
19
|
+
from dex.tests.workflows.internal_channel_workflow import InternalChannelWorkflow
|
|
20
|
+
from dex.tests.workflows.internal_channel_workflow_with_no_prefix_channel import (
|
|
21
|
+
InternalChannelWorkflowWithNoPrefixChannel,
|
|
22
|
+
)
|
|
23
|
+
from dex.tests.workflows.java_duplicate_rpc_memo_workflow import (
|
|
24
|
+
JavaDuplicateRpcMemoWorkflow,
|
|
25
|
+
)
|
|
26
|
+
from dex.tests.workflows.persistence_data_attributes_workflow import (
|
|
27
|
+
PersistenceDataAttributesWorkflow,
|
|
28
|
+
)
|
|
29
|
+
from dex.tests.workflows.persistence_search_attributes_workflow import (
|
|
30
|
+
PersistenceSearchAttributesWorkflow,
|
|
31
|
+
)
|
|
32
|
+
from dex.tests.workflows.persistence_state_execution_local_workflow import (
|
|
33
|
+
PersistenceStateExecutionLocalWorkflow,
|
|
34
|
+
)
|
|
35
|
+
from dex.tests.workflows.recovery_workflow import RecoveryWorkflow
|
|
36
|
+
from dex.tests.workflows.rpc_memo_workflow import RpcMemoWorkflow
|
|
37
|
+
from dex.tests.workflows.rpc_workflow import RPCWorkflow
|
|
38
|
+
from dex.tests.workflows.state_options_override_workflow import (
|
|
39
|
+
StateOptionsOverrideWorkflow,
|
|
40
|
+
)
|
|
41
|
+
from dex.tests.workflows.state_options_workflow import (
|
|
42
|
+
StateOptionsWorkflow1,
|
|
43
|
+
StateOptionsWorkflow2,
|
|
44
|
+
)
|
|
45
|
+
from dex.tests.workflows.timer_workflow import TimerWorkflow
|
|
46
|
+
from dex.tests.workflows.wait_for_state_with_state_execution_id_workflow import (
|
|
47
|
+
WaitForStateWithStateExecutionIdWorkflow,
|
|
48
|
+
)
|
|
49
|
+
from dex.tests.workflows.wait_for_state_with_wait_for_key_workflow import (
|
|
50
|
+
WaitForStateWithWaitForKeyWorkflow,
|
|
51
|
+
)
|
|
52
|
+
from dex.tests.workflows.wait_internal_channel_workflow import (
|
|
53
|
+
WaitInternalChannelWorkflow,
|
|
54
|
+
)
|
|
55
|
+
from dex.tests.workflows.wait_signal_workflow import WaitSignalWorkflow
|
|
56
|
+
|
|
57
|
+
registry = Registry()
|
|
58
|
+
|
|
59
|
+
registry.add_workflow(AbnormalExitWorkflow())
|
|
60
|
+
registry.add_workflow(BasicWorkflow())
|
|
61
|
+
registry.add_workflow(ConditionalCompleteWorkflow())
|
|
62
|
+
registry.add_workflow(DescribeWorkflow())
|
|
63
|
+
registry.add_workflow(EmptyDataWorkflow())
|
|
64
|
+
registry.add_workflow(InternalChannelWorkflow())
|
|
65
|
+
registry.add_workflow(InternalChannelWorkflowWithNoPrefixChannel())
|
|
66
|
+
registry.add_workflow(JavaDuplicateRpcMemoWorkflow())
|
|
67
|
+
registry.add_workflow(PersistenceDataAttributesWorkflow())
|
|
68
|
+
registry.add_workflow(PersistenceSearchAttributesWorkflow())
|
|
69
|
+
registry.add_workflow(PersistenceStateExecutionLocalWorkflow())
|
|
70
|
+
registry.add_workflow(RecoveryWorkflow())
|
|
71
|
+
registry.add_workflow(RpcMemoWorkflow())
|
|
72
|
+
registry.add_workflow(RPCWorkflow())
|
|
73
|
+
registry.add_workflow(StateOptionsOverrideWorkflow())
|
|
74
|
+
registry.add_workflow(StateOptionsWorkflow1())
|
|
75
|
+
registry.add_workflow(StateOptionsWorkflow2())
|
|
76
|
+
registry.add_workflow(TimerWorkflow())
|
|
77
|
+
registry.add_workflow(WaitForStateWithStateExecutionIdWorkflow())
|
|
78
|
+
registry.add_workflow(WaitForStateWithWaitForKeyWorkflow())
|
|
79
|
+
registry.add_workflow(WaitInternalChannelWorkflow())
|
|
80
|
+
registry.add_workflow(WaitSignalWorkflow())
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
checkExists () {
|
|
4
|
+
# see https://github.com/temporalio/temporal/issues/4160
|
|
5
|
+
if temporal operator search-attribute list | grep -q "$1"; then
|
|
6
|
+
return 0
|
|
7
|
+
else
|
|
8
|
+
return 1
|
|
9
|
+
fi
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
echo "now trying to register Dex system search attributes..."
|
|
13
|
+
for run in {1..120}; do
|
|
14
|
+
sleep 1
|
|
15
|
+
temporal operator search-attribute create --name DexWorkflowType --type Keyword
|
|
16
|
+
sleep 0.1
|
|
17
|
+
temporal operator search-attribute create --name DexGlobalWorkflowVersion --type Int
|
|
18
|
+
sleep 0.1
|
|
19
|
+
temporal operator search-attribute create --name DexExecutingStateIds --type KeywordList
|
|
20
|
+
sleep 0.1
|
|
21
|
+
temporal operator search-attribute create --name CustomKeywordField --type Keyword
|
|
22
|
+
sleep 0.1
|
|
23
|
+
temporal operator search-attribute create --name CustomIntField --type Int
|
|
24
|
+
sleep 0.1
|
|
25
|
+
temporal operator search-attribute create --name CustomBoolField --type Bool
|
|
26
|
+
sleep 0.1
|
|
27
|
+
temporal operator search-attribute create --name CustomDoubleField --type Double
|
|
28
|
+
sleep 0.1
|
|
29
|
+
temporal operator search-attribute create --name CustomDatetimeField --type Datetime
|
|
30
|
+
sleep 0.1
|
|
31
|
+
temporal operator search-attribute create --name CustomStringField --type Text
|
|
32
|
+
sleep 0.1
|
|
33
|
+
temporal operator search-attribute create --name CustomKeywordArrayField --type KeywordList
|
|
34
|
+
sleep 0.1
|
|
35
|
+
|
|
36
|
+
if checkExists "DexWorkflowType" && checkExists "DexGlobalWorkflowVersion" && checkExists "DexExecutingStateIds" && checkExists "CustomKeywordField" && checkExists "CustomIntField" && checkExists "CustomBoolField" && checkExists "CustomDoubleField" && checkExists "CustomDatetimeField" && checkExists "CustomStringField" && checkExists "CustomKeywordArrayField"; then
|
|
37
|
+
echo "All search attributes are registered"
|
|
38
|
+
break
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
tail -f /dev/null
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
version: '3.9'
|
|
12
|
+
|
|
13
|
+
# see .env file for the default value of the environment variables
|
|
14
|
+
services:
|
|
15
|
+
elasticsearch:
|
|
16
|
+
container_name: temporal-elasticsearch-dex
|
|
17
|
+
environment:
|
|
18
|
+
- cluster.routing.allocation.disk.threshold_enabled=true
|
|
19
|
+
- cluster.routing.allocation.disk.watermark.low=512mb
|
|
20
|
+
- cluster.routing.allocation.disk.watermark.high=256mb
|
|
21
|
+
- cluster.routing.allocation.disk.watermark.flood_stage=128mb
|
|
22
|
+
- discovery.type=single-node
|
|
23
|
+
- ES_JAVA_OPTS=-Xms512m -Xmx512m
|
|
24
|
+
- xpack.security.enabled=false
|
|
25
|
+
image: elasticsearch:${ELASTICSEARCH_VERSION}
|
|
26
|
+
networks:
|
|
27
|
+
- temporal-network
|
|
28
|
+
expose:
|
|
29
|
+
- 9200
|
|
30
|
+
postgresql:
|
|
31
|
+
container_name: temporal-postgresql-dex
|
|
32
|
+
environment:
|
|
33
|
+
POSTGRES_PASSWORD: temporal
|
|
34
|
+
POSTGRES_USER: temporal
|
|
35
|
+
image: postgres:${POSTGRESQL_VERSION}
|
|
36
|
+
networks:
|
|
37
|
+
- temporal-network
|
|
38
|
+
expose:
|
|
39
|
+
- 5432
|
|
40
|
+
temporal:
|
|
41
|
+
container_name: temporal-dex
|
|
42
|
+
depends_on:
|
|
43
|
+
- postgresql
|
|
44
|
+
- elasticsearch
|
|
45
|
+
environment:
|
|
46
|
+
- DB=postgres12
|
|
47
|
+
- DB_PORT=5432
|
|
48
|
+
- POSTGRES_USER=temporal
|
|
49
|
+
- POSTGRES_PWD=temporal
|
|
50
|
+
- POSTGRES_SEEDS=postgresql
|
|
51
|
+
- DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml
|
|
52
|
+
- ENABLE_ES=true
|
|
53
|
+
- ES_SEEDS=elasticsearch
|
|
54
|
+
- ES_VERSION=v7
|
|
55
|
+
image: temporalio/auto-setup:${TEMPORAL_VERSION}
|
|
56
|
+
networks:
|
|
57
|
+
- temporal-network
|
|
58
|
+
ports:
|
|
59
|
+
- 7233:7233
|
|
60
|
+
labels:
|
|
61
|
+
kompose.volume.type: configMap
|
|
62
|
+
volumes:
|
|
63
|
+
- ./dynamicconfig:/etc/temporal/config/dynamicconfig
|
|
64
|
+
temporal-admin-tools:
|
|
65
|
+
container_name: temporal-admin-tools-dex
|
|
66
|
+
depends_on:
|
|
67
|
+
- temporal
|
|
68
|
+
environment:
|
|
69
|
+
- TEMPORAL_ADDRESS=temporal:7233
|
|
70
|
+
- TEMPORAL_CLI_ADDRESS=temporal:7233
|
|
71
|
+
image: temporalio/admin-tools:${TEMPORAL_ADMIN_TOOLS_VERSION}
|
|
72
|
+
networks:
|
|
73
|
+
- temporal-network
|
|
74
|
+
stdin_open: true
|
|
75
|
+
tty: true
|
|
76
|
+
volumes:
|
|
77
|
+
- ./docker-compose-init.sh:/etc/temporal/init.sh
|
|
78
|
+
entrypoint: sh -c "/etc/temporal/init.sh"
|
|
79
|
+
dex-server:
|
|
80
|
+
container_name: dex-server
|
|
81
|
+
image: ${DEX_SERVER_IMAGE:-superdurable/dex-server:latest}
|
|
82
|
+
environment:
|
|
83
|
+
# Replace localhost/127.0.0.1 worker URLs so the container can reach the host worker.
|
|
84
|
+
# host.docker.internal works on Docker Desktop and on Linux with extra_hosts below.
|
|
85
|
+
- AUTO_FIX_WORKER_URL=host.docker.internal
|
|
86
|
+
networks:
|
|
87
|
+
- temporal-network
|
|
88
|
+
ports:
|
|
89
|
+
- 8801:8801
|
|
90
|
+
depends_on:
|
|
91
|
+
- temporal
|
|
92
|
+
extra_hosts:
|
|
93
|
+
- host.docker.internal:host-gateway
|
|
94
|
+
networks:
|
|
95
|
+
temporal-network:
|
|
96
|
+
driver: bridge
|
|
97
|
+
name: temporal-network
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Use `docker.yaml` file to override the default dynamic config value (they are specified
|
|
2
|
+
when creating the service config).
|
|
3
|
+
|
|
4
|
+
Each key can have zero or more values and each value can have zero or more
|
|
5
|
+
constraints. There are only three types of constraint:
|
|
6
|
+
1. `namespace`: `string`
|
|
7
|
+
2. `taskQueueName`: `string`
|
|
8
|
+
3. `taskType`: `int` (`1`:`Workflow`, `2`:`Activity`)
|
|
9
|
+
A value will be selected and returned if all its has exactly the same constraints
|
|
10
|
+
as the ones specified in query filters (including the number of constraints).
|
|
11
|
+
|
|
12
|
+
Please use the following format:
|
|
13
|
+
```
|
|
14
|
+
testGetBoolPropertyKey:
|
|
15
|
+
- value: false
|
|
16
|
+
- value: true
|
|
17
|
+
constraints:
|
|
18
|
+
namespace: "global-samples-namespace"
|
|
19
|
+
- value: false
|
|
20
|
+
constraints:
|
|
21
|
+
namespace: "samples-namespace"
|
|
22
|
+
testGetDurationPropertyKey:
|
|
23
|
+
- value: "1m"
|
|
24
|
+
constraints:
|
|
25
|
+
namespace: "samples-namespace"
|
|
26
|
+
taskQueueName: "longIdleTimeTaskqueue"
|
|
27
|
+
testGetFloat64PropertyKey:
|
|
28
|
+
- value: 12.0
|
|
29
|
+
constraints:
|
|
30
|
+
namespace: "samples-namespace"
|
|
31
|
+
testGetMapPropertyKey:
|
|
32
|
+
- value:
|
|
33
|
+
key1: 1
|
|
34
|
+
key2: "value 2"
|
|
35
|
+
key3:
|
|
36
|
+
- false
|
|
37
|
+
- key4: true
|
|
38
|
+
key5: 2.0
|
|
39
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
limit.maxIDLength:
|
|
5
|
+
- value: 255
|
|
6
|
+
constraints: {}
|
|
7
|
+
system.forceSearchAttributesCacheRefreshOnRead:
|
|
8
|
+
- value: true # Dev setup only. Please don't turn this on in production.
|
|
9
|
+
constraints: {}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
import inspect
|
|
12
|
+
import time
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
from dex.client import Client
|
|
16
|
+
from dex.errors import WorkflowFailed
|
|
17
|
+
from dex.dex_api.models.id_reuse_policy import IDReusePolicy
|
|
18
|
+
from dex.tests.worker_server import registry
|
|
19
|
+
from dex.tests.workflows.abnormal_exit_workflow import AbnormalExitWorkflow
|
|
20
|
+
from dex.tests.workflows.basic_workflow import BasicWorkflow
|
|
21
|
+
from dex.workflow_options import WorkflowOptions
|
|
22
|
+
|
|
23
|
+
class TestAbnormalWorkflow(unittest.TestCase):
|
|
24
|
+
@classmethod
|
|
25
|
+
def setUpClass(cls):
|
|
26
|
+
cls.client = Client(registry)
|
|
27
|
+
|
|
28
|
+
def test_abnormal_exit_workflow(self):
|
|
29
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
30
|
+
start_options = WorkflowOptions(
|
|
31
|
+
workflow_id_reuse_policy=IDReusePolicy.ALLOW_IF_PREVIOUS_EXITS_ABNORMALLY
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
self.client.start_workflow(
|
|
35
|
+
AbnormalExitWorkflow, wf_id, 100, "input", start_options
|
|
36
|
+
)
|
|
37
|
+
with self.assertRaises(WorkflowFailed):
|
|
38
|
+
self.client.wait_for_workflow_completion(wf_id, str)
|
|
39
|
+
|
|
40
|
+
# Starting a workflow with the same ID should be allowed since the previous failed abnormally
|
|
41
|
+
self.client.start_workflow(BasicWorkflow, wf_id, 100, "input", start_options)
|
|
42
|
+
res = self.client.wait_for_workflow_completion(wf_id, str)
|
|
43
|
+
assert res == "done"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Legacy Materials in this file remain under their original licenses.
|
|
2
|
+
# See LEGACY_NOTICES.md.
|
|
3
|
+
|
|
4
|
+
# Modifications Copyright (c) 2026 Super Durable, Inc.
|
|
5
|
+
#
|
|
6
|
+
# Modifications after the Legacy Cutoff are licensed under the
|
|
7
|
+
# Super Durable Source License 1.0.
|
|
8
|
+
# Legacy Materials remain under their original licenses.
|
|
9
|
+
# See LICENSE and LEGACY_NOTICES.md.
|
|
10
|
+
|
|
11
|
+
import inspect
|
|
12
|
+
import time
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
from dex.client import Client
|
|
16
|
+
from dex.errors import WorkflowAlreadyStartedError
|
|
17
|
+
from dex.dex_api.models import WorkflowAlreadyStartedOptions
|
|
18
|
+
from dex.tests.worker_server import registry
|
|
19
|
+
from dex.tests.workflows.basic_workflow import BasicWorkflow
|
|
20
|
+
from dex.workflow_options import WorkflowOptions
|
|
21
|
+
|
|
22
|
+
class TestWorkflowErrors(unittest.TestCase):
|
|
23
|
+
@classmethod
|
|
24
|
+
def setUpClass(cls):
|
|
25
|
+
cls.client = Client(registry)
|
|
26
|
+
|
|
27
|
+
def test_basic_workflow(self):
|
|
28
|
+
original_request_id = "1"
|
|
29
|
+
later_request_id = "2"
|
|
30
|
+
|
|
31
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
32
|
+
|
|
33
|
+
workflow_already_started_options_1 = WorkflowAlreadyStartedOptions(
|
|
34
|
+
ignore_already_started_error=True
|
|
35
|
+
)
|
|
36
|
+
workflow_already_started_options_1.request_id = original_request_id
|
|
37
|
+
|
|
38
|
+
start_options_1 = WorkflowOptions()
|
|
39
|
+
start_options_1.workflow_already_started_options = (
|
|
40
|
+
workflow_already_started_options_1
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
wf_run_id = self.client.start_workflow(
|
|
44
|
+
BasicWorkflow, wf_id, 100, "input", start_options_1
|
|
45
|
+
)
|
|
46
|
+
assert wf_run_id
|
|
47
|
+
|
|
48
|
+
wf_run_id = self.client.start_workflow(
|
|
49
|
+
BasicWorkflow, wf_id, 100, "input", start_options_1
|
|
50
|
+
)
|
|
51
|
+
assert wf_run_id
|
|
52
|
+
|
|
53
|
+
workflow_already_started_options_2 = WorkflowAlreadyStartedOptions(
|
|
54
|
+
ignore_already_started_error=True
|
|
55
|
+
)
|
|
56
|
+
workflow_already_started_options_2.request_id = later_request_id
|
|
57
|
+
|
|
58
|
+
start_options_2 = WorkflowOptions()
|
|
59
|
+
start_options_2.workflow_already_started_option = (
|
|
60
|
+
workflow_already_started_options_2
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
with self.assertRaises(WorkflowAlreadyStartedError):
|
|
64
|
+
wf_run_id = self.client.start_workflow(
|
|
65
|
+
BasicWorkflow, wf_id, 100, "input", start_options_2
|
|
66
|
+
)
|
|
67
|
+
assert wf_run_id
|
|
68
|
+
|
|
69
|
+
res = self.client.wait_for_workflow_completion(wf_id, str)
|
|
70
|
+
assert res == "done"
|