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/tests/__init__.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from dex.registry import Registry
|
|
16
|
+
from dex.tests.workflows.abnormal_exit_workflow import AbnormalExitWorkflow
|
|
17
|
+
from dex.tests.workflows.basic_workflow import BasicWorkflow
|
|
18
|
+
from dex.tests.workflows.conditional_complete_workflow import (
|
|
19
|
+
ConditionalCompleteWorkflow,
|
|
20
|
+
)
|
|
21
|
+
from dex.tests.workflows.describe_workflow import DescribeWorkflow
|
|
22
|
+
from dex.tests.workflows.empty_data_workflow import EmptyDataWorkflow
|
|
23
|
+
from dex.tests.workflows.internal_channel_workflow import InternalChannelWorkflow
|
|
24
|
+
from dex.tests.workflows.internal_channel_workflow_with_no_prefix_channel import (
|
|
25
|
+
InternalChannelWorkflowWithNoPrefixChannel,
|
|
26
|
+
)
|
|
27
|
+
from dex.tests.workflows.java_duplicate_rpc_memo_workflow import (
|
|
28
|
+
JavaDuplicateRpcMemoWorkflow,
|
|
29
|
+
)
|
|
30
|
+
from dex.tests.workflows.persistence_data_attributes_workflow import (
|
|
31
|
+
PersistenceDataAttributesWorkflow,
|
|
32
|
+
)
|
|
33
|
+
from dex.tests.workflows.persistence_search_attributes_workflow import (
|
|
34
|
+
PersistenceSearchAttributesWorkflow,
|
|
35
|
+
)
|
|
36
|
+
from dex.tests.workflows.persistence_state_execution_local_workflow import (
|
|
37
|
+
PersistenceStateExecutionLocalWorkflow,
|
|
38
|
+
)
|
|
39
|
+
from dex.tests.workflows.recovery_workflow import RecoveryWorkflow
|
|
40
|
+
from dex.tests.workflows.rpc_memo_workflow import RpcMemoWorkflow
|
|
41
|
+
from dex.tests.workflows.rpc_workflow import RPCWorkflow
|
|
42
|
+
from dex.tests.workflows.state_options_override_workflow import (
|
|
43
|
+
StateOptionsOverrideWorkflow,
|
|
44
|
+
)
|
|
45
|
+
from dex.tests.workflows.state_options_workflow import (
|
|
46
|
+
StateOptionsWorkflow1,
|
|
47
|
+
StateOptionsWorkflow2,
|
|
48
|
+
)
|
|
49
|
+
from dex.tests.workflows.timer_workflow import TimerWorkflow
|
|
50
|
+
from dex.tests.workflows.wait_for_state_with_state_execution_id_workflow import (
|
|
51
|
+
WaitForStateWithStateExecutionIdWorkflow,
|
|
52
|
+
)
|
|
53
|
+
from dex.tests.workflows.wait_for_state_with_wait_for_key_workflow import (
|
|
54
|
+
WaitForStateWithWaitForKeyWorkflow,
|
|
55
|
+
)
|
|
56
|
+
from dex.tests.workflows.wait_internal_channel_workflow import (
|
|
57
|
+
WaitInternalChannelWorkflow,
|
|
58
|
+
)
|
|
59
|
+
from dex.tests.workflows.wait_signal_workflow import WaitSignalWorkflow
|
|
60
|
+
|
|
61
|
+
registry = Registry()
|
|
62
|
+
|
|
63
|
+
registry.add_workflow(AbnormalExitWorkflow())
|
|
64
|
+
registry.add_workflow(BasicWorkflow())
|
|
65
|
+
registry.add_workflow(ConditionalCompleteWorkflow())
|
|
66
|
+
registry.add_workflow(DescribeWorkflow())
|
|
67
|
+
registry.add_workflow(EmptyDataWorkflow())
|
|
68
|
+
registry.add_workflow(InternalChannelWorkflow())
|
|
69
|
+
registry.add_workflow(InternalChannelWorkflowWithNoPrefixChannel())
|
|
70
|
+
registry.add_workflow(JavaDuplicateRpcMemoWorkflow())
|
|
71
|
+
registry.add_workflow(PersistenceDataAttributesWorkflow())
|
|
72
|
+
registry.add_workflow(PersistenceSearchAttributesWorkflow())
|
|
73
|
+
registry.add_workflow(PersistenceStateExecutionLocalWorkflow())
|
|
74
|
+
registry.add_workflow(RecoveryWorkflow())
|
|
75
|
+
registry.add_workflow(RpcMemoWorkflow())
|
|
76
|
+
registry.add_workflow(RPCWorkflow())
|
|
77
|
+
registry.add_workflow(StateOptionsOverrideWorkflow())
|
|
78
|
+
registry.add_workflow(StateOptionsWorkflow1())
|
|
79
|
+
registry.add_workflow(StateOptionsWorkflow2())
|
|
80
|
+
registry.add_workflow(TimerWorkflow())
|
|
81
|
+
registry.add_workflow(WaitForStateWithStateExecutionIdWorkflow())
|
|
82
|
+
registry.add_workflow(WaitForStateWithWaitForKeyWorkflow())
|
|
83
|
+
registry.add_workflow(WaitInternalChannelWorkflow())
|
|
84
|
+
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,87 @@
|
|
|
1
|
+
version: '3.9'
|
|
2
|
+
|
|
3
|
+
# see .env file for the default value of the environment variables
|
|
4
|
+
services:
|
|
5
|
+
elasticsearch:
|
|
6
|
+
container_name: temporal-elasticsearch-dex
|
|
7
|
+
environment:
|
|
8
|
+
- cluster.routing.allocation.disk.threshold_enabled=true
|
|
9
|
+
- cluster.routing.allocation.disk.watermark.low=512mb
|
|
10
|
+
- cluster.routing.allocation.disk.watermark.high=256mb
|
|
11
|
+
- cluster.routing.allocation.disk.watermark.flood_stage=128mb
|
|
12
|
+
- discovery.type=single-node
|
|
13
|
+
- ES_JAVA_OPTS=-Xms512m -Xmx512m
|
|
14
|
+
- xpack.security.enabled=false
|
|
15
|
+
image: elasticsearch:${ELASTICSEARCH_VERSION}
|
|
16
|
+
networks:
|
|
17
|
+
- temporal-network
|
|
18
|
+
expose:
|
|
19
|
+
- 9200
|
|
20
|
+
postgresql:
|
|
21
|
+
container_name: temporal-postgresql-dex
|
|
22
|
+
environment:
|
|
23
|
+
POSTGRES_PASSWORD: temporal
|
|
24
|
+
POSTGRES_USER: temporal
|
|
25
|
+
image: postgres:${POSTGRESQL_VERSION}
|
|
26
|
+
networks:
|
|
27
|
+
- temporal-network
|
|
28
|
+
expose:
|
|
29
|
+
- 5432
|
|
30
|
+
temporal:
|
|
31
|
+
container_name: temporal-dex
|
|
32
|
+
depends_on:
|
|
33
|
+
- postgresql
|
|
34
|
+
- elasticsearch
|
|
35
|
+
environment:
|
|
36
|
+
- DB=postgres12
|
|
37
|
+
- DB_PORT=5432
|
|
38
|
+
- POSTGRES_USER=temporal
|
|
39
|
+
- POSTGRES_PWD=temporal
|
|
40
|
+
- POSTGRES_SEEDS=postgresql
|
|
41
|
+
- DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml
|
|
42
|
+
- ENABLE_ES=true
|
|
43
|
+
- ES_SEEDS=elasticsearch
|
|
44
|
+
- ES_VERSION=v7
|
|
45
|
+
image: temporalio/auto-setup:${TEMPORAL_VERSION}
|
|
46
|
+
networks:
|
|
47
|
+
- temporal-network
|
|
48
|
+
ports:
|
|
49
|
+
- 7233:7233
|
|
50
|
+
labels:
|
|
51
|
+
kompose.volume.type: configMap
|
|
52
|
+
volumes:
|
|
53
|
+
- ./dynamicconfig:/etc/temporal/config/dynamicconfig
|
|
54
|
+
temporal-admin-tools:
|
|
55
|
+
container_name: temporal-admin-tools-dex
|
|
56
|
+
depends_on:
|
|
57
|
+
- temporal
|
|
58
|
+
environment:
|
|
59
|
+
- TEMPORAL_ADDRESS=temporal:7233
|
|
60
|
+
- TEMPORAL_CLI_ADDRESS=temporal:7233
|
|
61
|
+
image: temporalio/admin-tools:${TEMPORAL_ADMIN_TOOLS_VERSION}
|
|
62
|
+
networks:
|
|
63
|
+
- temporal-network
|
|
64
|
+
stdin_open: true
|
|
65
|
+
tty: true
|
|
66
|
+
volumes:
|
|
67
|
+
- ./docker-compose-init.sh:/etc/temporal/init.sh
|
|
68
|
+
entrypoint: sh -c "/etc/temporal/init.sh"
|
|
69
|
+
dex-server:
|
|
70
|
+
container_name: dex-server
|
|
71
|
+
image: ${DEX_SERVER_IMAGE:-superdurable/dex-server:latest}
|
|
72
|
+
environment:
|
|
73
|
+
# Replace localhost/127.0.0.1 worker URLs so the container can reach the host worker.
|
|
74
|
+
# host.docker.internal works on Docker Desktop and on Linux with extra_hosts below.
|
|
75
|
+
- AUTO_FIX_WORKER_URL=host.docker.internal
|
|
76
|
+
networks:
|
|
77
|
+
- temporal-network
|
|
78
|
+
ports:
|
|
79
|
+
- 8801:8801
|
|
80
|
+
depends_on:
|
|
81
|
+
- temporal
|
|
82
|
+
extra_hosts:
|
|
83
|
+
- host.docker.internal:host-gateway
|
|
84
|
+
networks:
|
|
85
|
+
temporal-network:
|
|
86
|
+
driver: bridge
|
|
87
|
+
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
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import inspect
|
|
16
|
+
import time
|
|
17
|
+
import unittest
|
|
18
|
+
|
|
19
|
+
from dex.client import Client
|
|
20
|
+
from dex.errors import WorkflowFailed
|
|
21
|
+
from dex.dex_api.models.id_reuse_policy import IDReusePolicy
|
|
22
|
+
from dex.tests.worker_server import registry
|
|
23
|
+
from dex.tests.workflows.abnormal_exit_workflow import AbnormalExitWorkflow
|
|
24
|
+
from dex.tests.workflows.basic_workflow import BasicWorkflow
|
|
25
|
+
from dex.workflow_options import WorkflowOptions
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestAbnormalWorkflow(unittest.TestCase):
|
|
29
|
+
@classmethod
|
|
30
|
+
def setUpClass(cls):
|
|
31
|
+
cls.client = Client(registry)
|
|
32
|
+
|
|
33
|
+
def test_abnormal_exit_workflow(self):
|
|
34
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
35
|
+
start_options = WorkflowOptions(
|
|
36
|
+
workflow_id_reuse_policy=IDReusePolicy.ALLOW_IF_PREVIOUS_EXITS_ABNORMALLY
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
self.client.start_workflow(
|
|
40
|
+
AbnormalExitWorkflow, wf_id, 100, "input", start_options
|
|
41
|
+
)
|
|
42
|
+
with self.assertRaises(WorkflowFailed):
|
|
43
|
+
self.client.wait_for_workflow_completion(wf_id, str)
|
|
44
|
+
|
|
45
|
+
# Starting a workflow with the same ID should be allowed since the previous failed abnormally
|
|
46
|
+
self.client.start_workflow(BasicWorkflow, wf_id, 100, "input", start_options)
|
|
47
|
+
res = self.client.wait_for_workflow_completion(wf_id, str)
|
|
48
|
+
assert res == "done"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import inspect
|
|
16
|
+
import time
|
|
17
|
+
import unittest
|
|
18
|
+
|
|
19
|
+
from dex.client import Client
|
|
20
|
+
from dex.errors import WorkflowAlreadyStartedError
|
|
21
|
+
from dex.dex_api.models import WorkflowAlreadyStartedOptions
|
|
22
|
+
from dex.tests.worker_server import registry
|
|
23
|
+
from dex.tests.workflows.basic_workflow import BasicWorkflow
|
|
24
|
+
from dex.workflow_options import WorkflowOptions
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestWorkflowErrors(unittest.TestCase):
|
|
28
|
+
@classmethod
|
|
29
|
+
def setUpClass(cls):
|
|
30
|
+
cls.client = Client(registry)
|
|
31
|
+
|
|
32
|
+
def test_basic_workflow(self):
|
|
33
|
+
original_request_id = "1"
|
|
34
|
+
later_request_id = "2"
|
|
35
|
+
|
|
36
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
37
|
+
|
|
38
|
+
workflow_already_started_options_1 = WorkflowAlreadyStartedOptions(
|
|
39
|
+
ignore_already_started_error=True
|
|
40
|
+
)
|
|
41
|
+
workflow_already_started_options_1.request_id = original_request_id
|
|
42
|
+
|
|
43
|
+
start_options_1 = WorkflowOptions()
|
|
44
|
+
start_options_1.workflow_already_started_options = (
|
|
45
|
+
workflow_already_started_options_1
|
|
46
|
+
)
|
|
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
|
+
wf_run_id = self.client.start_workflow(
|
|
54
|
+
BasicWorkflow, wf_id, 100, "input", start_options_1
|
|
55
|
+
)
|
|
56
|
+
assert wf_run_id
|
|
57
|
+
|
|
58
|
+
workflow_already_started_options_2 = WorkflowAlreadyStartedOptions(
|
|
59
|
+
ignore_already_started_error=True
|
|
60
|
+
)
|
|
61
|
+
workflow_already_started_options_2.request_id = later_request_id
|
|
62
|
+
|
|
63
|
+
start_options_2 = WorkflowOptions()
|
|
64
|
+
start_options_2.workflow_already_started_option = (
|
|
65
|
+
workflow_already_started_options_2
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
with self.assertRaises(WorkflowAlreadyStartedError):
|
|
69
|
+
wf_run_id = self.client.start_workflow(
|
|
70
|
+
BasicWorkflow, wf_id, 100, "input", start_options_2
|
|
71
|
+
)
|
|
72
|
+
assert wf_run_id
|
|
73
|
+
|
|
74
|
+
res = self.client.wait_for_workflow_completion(wf_id, str)
|
|
75
|
+
assert res == "done"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import inspect
|
|
16
|
+
import time
|
|
17
|
+
import unittest
|
|
18
|
+
|
|
19
|
+
from dex.client import Client
|
|
20
|
+
from dex.tests.worker_server import registry
|
|
21
|
+
from dex.tests.workflows.conditional_complete_workflow import (
|
|
22
|
+
ConditionalCompleteWorkflow,
|
|
23
|
+
test_signal_channel,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestConditionalComplete(unittest.TestCase):
|
|
28
|
+
@classmethod
|
|
29
|
+
def setUpClass(cls):
|
|
30
|
+
cls.client = Client(registry)
|
|
31
|
+
|
|
32
|
+
def test_internal_channel_conditional_complete(self):
|
|
33
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
34
|
+
do_test_conditional_workflow(self.client, wf_id, False)
|
|
35
|
+
|
|
36
|
+
def test_signal_channel_conditional_complete(self):
|
|
37
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
38
|
+
do_test_conditional_workflow(self.client, wf_id, True)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def do_test_conditional_workflow(client: Client, wf_id: str, use_signal: bool):
|
|
42
|
+
client.start_workflow(ConditionalCompleteWorkflow, wf_id, 10, use_signal)
|
|
43
|
+
|
|
44
|
+
for x in range(3):
|
|
45
|
+
if use_signal:
|
|
46
|
+
client.signal_workflow(wf_id, test_signal_channel, 123)
|
|
47
|
+
else:
|
|
48
|
+
client.invoke_rpc(
|
|
49
|
+
wf_id, ConditionalCompleteWorkflow.test_rpc_publish_channel
|
|
50
|
+
)
|
|
51
|
+
if x == 0:
|
|
52
|
+
# wait for a second so that the workflow is in execute state
|
|
53
|
+
time.sleep(1)
|
|
54
|
+
|
|
55
|
+
res = client.wait_for_workflow_completion(wf_id)
|
|
56
|
+
assert res == 3
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import inspect
|
|
16
|
+
import time
|
|
17
|
+
import unittest
|
|
18
|
+
|
|
19
|
+
from dex.client import Client
|
|
20
|
+
from dex.errors import WorkflowNotExistsError
|
|
21
|
+
from dex.dex_api.models import WorkflowStatus
|
|
22
|
+
from dex.tests.workflows.describe_workflow import DescribeWorkflow
|
|
23
|
+
from dex.tests.worker_server import registry
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestDescribeWorkflow(unittest.TestCase):
|
|
27
|
+
@classmethod
|
|
28
|
+
def setUpClass(cls):
|
|
29
|
+
cls.client = Client(registry)
|
|
30
|
+
|
|
31
|
+
def test_describe_workflow(self):
|
|
32
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
33
|
+
|
|
34
|
+
self.client.start_workflow(DescribeWorkflow, wf_id, 100)
|
|
35
|
+
workflow_info = self.client.describe_workflow(wf_id)
|
|
36
|
+
assert workflow_info.workflow_status == WorkflowStatus.RUNNING
|
|
37
|
+
|
|
38
|
+
# Stop the workflow
|
|
39
|
+
self.client.stop_workflow(wf_id)
|
|
40
|
+
|
|
41
|
+
def test_describe_workflow_when_workflow_not_exists(self):
|
|
42
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
43
|
+
|
|
44
|
+
with self.assertRaises(WorkflowNotExistsError):
|
|
45
|
+
self.client.describe_workflow(wf_id)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import inspect
|
|
16
|
+
import time
|
|
17
|
+
import unittest
|
|
18
|
+
|
|
19
|
+
import httpx
|
|
20
|
+
|
|
21
|
+
from dex.client import Client
|
|
22
|
+
from dex.tests.worker_server import registry
|
|
23
|
+
from dex.worker_service import WorkerService
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestBinaryNullDecodesCorrectly(unittest.TestCase):
|
|
27
|
+
@classmethod
|
|
28
|
+
def setUpClass(cls):
|
|
29
|
+
cls.client = Client(registry)
|
|
30
|
+
|
|
31
|
+
def test_binary_null_input_decodes_correctly(self):
|
|
32
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
33
|
+
|
|
34
|
+
response = httpx.post(
|
|
35
|
+
f"http://0.0.0.0:8802/{WorkerService.api_path_workflow_state_execute}",
|
|
36
|
+
json={
|
|
37
|
+
"DataObjects": [
|
|
38
|
+
{"key": "test-da", "value": {"encoding": "binary/null"}}
|
|
39
|
+
],
|
|
40
|
+
"commandResults": {
|
|
41
|
+
"interStateChannelResults": [],
|
|
42
|
+
"stateStartApiSucceeded": True,
|
|
43
|
+
},
|
|
44
|
+
"context": {
|
|
45
|
+
"attempt": 1,
|
|
46
|
+
"firstAttemptTimestamp": 1747935829,
|
|
47
|
+
"stateExecutionId": "State1-1",
|
|
48
|
+
"workflowId": wf_id,
|
|
49
|
+
"workflowRunId": "0196f734-d037-7432-bd63-e1136cd34dbd",
|
|
50
|
+
"workflowStartedTimestamp": 1747904155,
|
|
51
|
+
},
|
|
52
|
+
"stateInput": {"encoding": "binary/null"},
|
|
53
|
+
"stateLocals": [],
|
|
54
|
+
"workflowStateId": "State1",
|
|
55
|
+
"workflowType": "EmptyDataWorkflow",
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
assert response.is_success
|
|
59
|
+
response_json = response.json()
|
|
60
|
+
self.assertEqual(
|
|
61
|
+
response_json,
|
|
62
|
+
{
|
|
63
|
+
"publishToInterStateChannel": [],
|
|
64
|
+
"recordEvents": [],
|
|
65
|
+
"stateDecision": {
|
|
66
|
+
"nextStates": [
|
|
67
|
+
{
|
|
68
|
+
"stateId": "_SYS_GRACEFUL_COMPLETING_WORKFLOW",
|
|
69
|
+
"stateInput": {
|
|
70
|
+
"data": '"success"',
|
|
71
|
+
"encoding": "json/plain",
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"upsertDataObjects": [],
|
|
77
|
+
"upsertStateLocals": [],
|
|
78
|
+
},
|
|
79
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import inspect
|
|
16
|
+
import time
|
|
17
|
+
import unittest
|
|
18
|
+
|
|
19
|
+
from dex.client import Client
|
|
20
|
+
from dex.tests.worker_server import registry
|
|
21
|
+
from dex.tests.workflows.internal_channel_workflow import InternalChannelWorkflow
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestConditionalComplete(unittest.TestCase):
|
|
25
|
+
@classmethod
|
|
26
|
+
def setUpClass(cls):
|
|
27
|
+
cls.client = Client(registry)
|
|
28
|
+
|
|
29
|
+
def test_internal_channel_workflow(self):
|
|
30
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
31
|
+
|
|
32
|
+
self.client.start_workflow(InternalChannelWorkflow, wf_id, 100, None)
|
|
33
|
+
self.client.wait_for_workflow_completion(wf_id, None)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Copyright (c) 2022-2026 Super Durable, Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import inspect
|
|
16
|
+
import time
|
|
17
|
+
import unittest
|
|
18
|
+
|
|
19
|
+
from dex.client import Client
|
|
20
|
+
from dex.tests.worker_server import registry
|
|
21
|
+
from dex.tests.workflows.internal_channel_workflow_with_no_prefix_channel import (
|
|
22
|
+
InternalChannelWorkflowWithNoPrefixChannel,
|
|
23
|
+
test_non_prefix_channel_name_with_suffix,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestInternalChannelWithNoPrefix(unittest.TestCase):
|
|
28
|
+
@classmethod
|
|
29
|
+
def setUpClass(cls):
|
|
30
|
+
cls.client = Client(registry)
|
|
31
|
+
|
|
32
|
+
def test_internal_channel_workflow_with_no_prefix_channel(self):
|
|
33
|
+
wf_id = f"{inspect.currentframe().f_code.co_name}-{time.time_ns()}"
|
|
34
|
+
|
|
35
|
+
self.client.start_workflow(
|
|
36
|
+
InternalChannelWorkflowWithNoPrefixChannel, wf_id, 5, None
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
with self.assertRaises(Exception) as context:
|
|
40
|
+
self.client.wait_for_workflow_completion(wf_id, None)
|
|
41
|
+
|
|
42
|
+
self.assertIn("FAILED", context.exception.workflow_status)
|
|
43
|
+
self.assertIn(
|
|
44
|
+
f"WorkerExecutionError: InternalChannel channel_name is not defined {test_non_prefix_channel_name_with_suffix}",
|
|
45
|
+
context.exception.error_message,
|
|
46
|
+
)
|