cadence-python-client 0.1.0__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.
- cadence/__init__.py +18 -0
- cadence/_internal/__init__.py +8 -0
- cadence/_internal/activity/__init__.py +5 -0
- cadence/_internal/activity/_activity_executor.py +113 -0
- cadence/_internal/activity/_context.py +58 -0
- cadence/_internal/rpc/__init__.py +0 -0
- cadence/_internal/rpc/error.py +148 -0
- cadence/_internal/rpc/retry.py +104 -0
- cadence/_internal/rpc/yarpc.py +42 -0
- cadence/_internal/workflow/__init__.py +0 -0
- cadence/_internal/workflow/context.py +121 -0
- cadence/_internal/workflow/decision_events_iterator.py +161 -0
- cadence/_internal/workflow/decisions_helper.py +312 -0
- cadence/_internal/workflow/deterministic_event_loop.py +498 -0
- cadence/_internal/workflow/history_event_iterator.py +58 -0
- cadence/_internal/workflow/statemachine/__init__.py +0 -0
- cadence/_internal/workflow/statemachine/activity_state_machine.py +106 -0
- cadence/_internal/workflow/statemachine/decision_manager.py +157 -0
- cadence/_internal/workflow/statemachine/decision_state_machine.py +87 -0
- cadence/_internal/workflow/statemachine/event_dispatcher.py +76 -0
- cadence/_internal/workflow/statemachine/timer_state_machine.py +73 -0
- cadence/_internal/workflow/workflow_engine.py +245 -0
- cadence/_internal/workflow/workflow_intance.py +44 -0
- cadence/activity.py +255 -0
- cadence/api/v1/__init__.py +92 -0
- cadence/api/v1/common_pb2.py +90 -0
- cadence/api/v1/common_pb2.pyi +200 -0
- cadence/api/v1/common_pb2_grpc.py +24 -0
- cadence/api/v1/decision_pb2.py +67 -0
- cadence/api/v1/decision_pb2.pyi +225 -0
- cadence/api/v1/decision_pb2_grpc.py +24 -0
- cadence/api/v1/domain_pb2.py +68 -0
- cadence/api/v1/domain_pb2.pyi +145 -0
- cadence/api/v1/domain_pb2_grpc.py +24 -0
- cadence/api/v1/error_pb2.py +59 -0
- cadence/api/v1/error_pb2.pyi +82 -0
- cadence/api/v1/error_pb2_grpc.py +24 -0
- cadence/api/v1/history_pb2.py +134 -0
- cadence/api/v1/history_pb2.pyi +780 -0
- cadence/api/v1/history_pb2_grpc.py +24 -0
- cadence/api/v1/query_pb2.py +49 -0
- cadence/api/v1/query_pb2.pyi +59 -0
- cadence/api/v1/query_pb2_grpc.py +24 -0
- cadence/api/v1/service_domain_pb2.py +76 -0
- cadence/api/v1/service_domain_pb2.pyi +164 -0
- cadence/api/v1/service_domain_pb2_grpc.py +327 -0
- cadence/api/v1/service_meta_pb2.py +41 -0
- cadence/api/v1/service_meta_pb2.pyi +17 -0
- cadence/api/v1/service_meta_pb2_grpc.py +97 -0
- cadence/api/v1/service_visibility_pb2.py +71 -0
- cadence/api/v1/service_visibility_pb2.pyi +149 -0
- cadence/api/v1/service_visibility_pb2_grpc.py +362 -0
- cadence/api/v1/service_worker_pb2.py +116 -0
- cadence/api/v1/service_worker_pb2.pyi +350 -0
- cadence/api/v1/service_worker_pb2_grpc.py +743 -0
- cadence/api/v1/service_workflow_pb2.py +126 -0
- cadence/api/v1/service_workflow_pb2.pyi +395 -0
- cadence/api/v1/service_workflow_pb2_grpc.py +861 -0
- cadence/api/v1/tasklist_pb2.py +78 -0
- cadence/api/v1/tasklist_pb2.pyi +147 -0
- cadence/api/v1/tasklist_pb2_grpc.py +24 -0
- cadence/api/v1/visibility_pb2.py +47 -0
- cadence/api/v1/visibility_pb2.pyi +53 -0
- cadence/api/v1/visibility_pb2_grpc.py +24 -0
- cadence/api/v1/workflow_pb2.py +89 -0
- cadence/api/v1/workflow_pb2.pyi +365 -0
- cadence/api/v1/workflow_pb2_grpc.py +24 -0
- cadence/client.py +382 -0
- cadence/data_converter.py +78 -0
- cadence/error.py +111 -0
- cadence/metrics/__init__.py +12 -0
- cadence/metrics/constants.py +136 -0
- cadence/metrics/metrics.py +56 -0
- cadence/metrics/prometheus.py +165 -0
- cadence/sample/__init__.py +1 -0
- cadence/sample/client_example.py +15 -0
- cadence/sample/grpc_usage_example.py +230 -0
- cadence/sample/simple_usage_example.py +155 -0
- cadence/signal.py +174 -0
- cadence/worker/__init__.py +13 -0
- cadence/worker/_activity.py +60 -0
- cadence/worker/_base_task_handler.py +71 -0
- cadence/worker/_decision.py +62 -0
- cadence/worker/_decision_task_handler.py +285 -0
- cadence/worker/_poller.py +64 -0
- cadence/worker/_registry.py +245 -0
- cadence/worker/_types.py +26 -0
- cadence/worker/_worker.py +56 -0
- cadence/workflow.py +271 -0
- cadence_python_client-0.1.0.dist-info/METADATA +180 -0
- cadence_python_client-0.1.0.dist-info/RECORD +95 -0
- cadence_python_client-0.1.0.dist-info/WHEEL +5 -0
- cadence_python_client-0.1.0.dist-info/licenses/LICENSE +201 -0
- cadence_python_client-0.1.0.dist-info/licenses/NOTICE +19 -0
- cadence_python_client-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Simple usage example for cadence protobuf modules.
|
|
4
|
+
This demonstrates basic usage patterns for the generated protobuf classes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
# Add the project root to the path so we can import cadence modules
|
|
11
|
+
project_root = os.path.dirname(
|
|
12
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
13
|
+
)
|
|
14
|
+
sys.path.insert(0, project_root)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def example_workflow_execution():
|
|
18
|
+
"""Example of creating and using WorkflowExecution objects."""
|
|
19
|
+
print("=== Workflow Execution Example ===")
|
|
20
|
+
|
|
21
|
+
from cadence.api.v1 import common, workflow
|
|
22
|
+
|
|
23
|
+
# Create a workflow execution
|
|
24
|
+
wf_exec = common.WorkflowExecution()
|
|
25
|
+
wf_exec.workflow_id = "my-workflow-123"
|
|
26
|
+
wf_exec.run_id = "run-456"
|
|
27
|
+
|
|
28
|
+
print("Created workflow execution:")
|
|
29
|
+
print(f" - Workflow ID: {wf_exec.workflow_id}")
|
|
30
|
+
print(f" - Run ID: {wf_exec.run_id}")
|
|
31
|
+
|
|
32
|
+
# Create workflow execution info
|
|
33
|
+
wf_info = workflow.WorkflowExecutionInfo()
|
|
34
|
+
wf_info.workflow_execution.CopyFrom(wf_exec)
|
|
35
|
+
wf_info.type.name = "MyWorkflowType"
|
|
36
|
+
wf_info.start_time.seconds = 1234567890
|
|
37
|
+
wf_info.close_time.seconds = 1234567990
|
|
38
|
+
|
|
39
|
+
print("Created workflow execution info:")
|
|
40
|
+
print(f" - Type: {wf_info.type.name}")
|
|
41
|
+
print(f" - Start Time: {wf_info.start_time.seconds}")
|
|
42
|
+
print(f" - Close Time: {wf_info.close_time.seconds}")
|
|
43
|
+
|
|
44
|
+
return wf_exec, wf_info
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def example_domain_operations():
|
|
48
|
+
"""Example of creating and using Domain objects."""
|
|
49
|
+
print("\n=== Domain Operations Example ===")
|
|
50
|
+
|
|
51
|
+
from cadence.api.v1 import domain
|
|
52
|
+
|
|
53
|
+
# Create a domain
|
|
54
|
+
domain_obj = domain.Domain()
|
|
55
|
+
domain_obj.name = "my-domain"
|
|
56
|
+
domain_obj.status = domain.DOMAIN_STATUS_REGISTERED
|
|
57
|
+
domain_obj.description = "My test domain"
|
|
58
|
+
|
|
59
|
+
print("Created domain:")
|
|
60
|
+
print(f" - Name: {domain_obj.name}")
|
|
61
|
+
print(f" - Status: {domain_obj.status}")
|
|
62
|
+
print(f" - Description: {domain_obj.description}")
|
|
63
|
+
|
|
64
|
+
return domain_obj
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def example_enum_usage():
|
|
68
|
+
"""Example of using enum values."""
|
|
69
|
+
print("\n=== Enum Usage Example ===")
|
|
70
|
+
|
|
71
|
+
from cadence.api.v1 import workflow
|
|
72
|
+
|
|
73
|
+
# Workflow execution close status
|
|
74
|
+
print("Workflow Execution Close Status:")
|
|
75
|
+
print(f" - COMPLETED: {workflow.WORKFLOW_EXECUTION_CLOSE_STATUS_COMPLETED}")
|
|
76
|
+
print(f" - FAILED: {workflow.WORKFLOW_EXECUTION_CLOSE_STATUS_FAILED}")
|
|
77
|
+
print(f" - CANCELED: {workflow.WORKFLOW_EXECUTION_CLOSE_STATUS_CANCELED}")
|
|
78
|
+
print(f" - TERMINATED: {workflow.WORKFLOW_EXECUTION_CLOSE_STATUS_TERMINATED}")
|
|
79
|
+
print(f" - TIMED_OUT: {workflow.WORKFLOW_EXECUTION_CLOSE_STATUS_TIMED_OUT}")
|
|
80
|
+
|
|
81
|
+
# Timeout types
|
|
82
|
+
print("\nTimeout Types:")
|
|
83
|
+
print(f" - START_TO_CLOSE: {workflow.TIMEOUT_TYPE_START_TO_CLOSE}")
|
|
84
|
+
print(f" - SCHEDULE_TO_CLOSE: {workflow.TIMEOUT_TYPE_SCHEDULE_TO_CLOSE}")
|
|
85
|
+
print(f" - SCHEDULE_TO_START: {workflow.TIMEOUT_TYPE_SCHEDULE_TO_START}")
|
|
86
|
+
print(f" - HEARTBEAT: {workflow.TIMEOUT_TYPE_HEARTBEAT}")
|
|
87
|
+
|
|
88
|
+
# Parent close policies
|
|
89
|
+
print("\nParent Close Policies:")
|
|
90
|
+
print(f" - TERMINATE: {workflow.PARENT_CLOSE_POLICY_TERMINATE}")
|
|
91
|
+
print(f" - ABANDON: {workflow.PARENT_CLOSE_POLICY_ABANDON}")
|
|
92
|
+
print(f" - REQUEST_CANCEL: {workflow.PARENT_CLOSE_POLICY_REQUEST_CANCEL}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def example_serialization():
|
|
96
|
+
"""Example of serializing and deserializing protobuf objects."""
|
|
97
|
+
print("\n=== Serialization Example ===")
|
|
98
|
+
|
|
99
|
+
from cadence.api.v1 import common
|
|
100
|
+
|
|
101
|
+
# Create a workflow execution
|
|
102
|
+
wf_exec = common.WorkflowExecution()
|
|
103
|
+
wf_exec.workflow_id = "serialization-test"
|
|
104
|
+
wf_exec.run_id = "run-789"
|
|
105
|
+
|
|
106
|
+
# Serialize to bytes
|
|
107
|
+
serialized = wf_exec.SerializeToString()
|
|
108
|
+
print(f"Serialized size: {len(serialized)} bytes")
|
|
109
|
+
|
|
110
|
+
# Deserialize from bytes
|
|
111
|
+
new_wf_exec = common.WorkflowExecution()
|
|
112
|
+
new_wf_exec.ParseFromString(serialized)
|
|
113
|
+
|
|
114
|
+
print("Deserialized workflow execution:")
|
|
115
|
+
print(f" - Workflow ID: {new_wf_exec.workflow_id}")
|
|
116
|
+
print(f" - Run ID: {new_wf_exec.run_id}")
|
|
117
|
+
|
|
118
|
+
# Verify they're equal
|
|
119
|
+
if (
|
|
120
|
+
wf_exec.workflow_id == new_wf_exec.workflow_id
|
|
121
|
+
and wf_exec.run_id == new_wf_exec.run_id
|
|
122
|
+
):
|
|
123
|
+
print("✓ Serialization/deserialization successful!")
|
|
124
|
+
else:
|
|
125
|
+
print("✗ Serialization/deserialization failed!")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def main():
|
|
129
|
+
"""Main example function."""
|
|
130
|
+
print("🚀 Cadence Protobuf Usage Examples")
|
|
131
|
+
print("=" * 50)
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
# Run all examples
|
|
135
|
+
example_workflow_execution()
|
|
136
|
+
example_domain_operations()
|
|
137
|
+
example_enum_usage()
|
|
138
|
+
example_serialization()
|
|
139
|
+
|
|
140
|
+
print("\n" + "=" * 50)
|
|
141
|
+
print("✅ All examples completed successfully!")
|
|
142
|
+
print("The protobuf modules are working correctly and ready for use.")
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
print(f"\n❌ Example failed: {e}")
|
|
146
|
+
import traceback
|
|
147
|
+
|
|
148
|
+
traceback.print_exc()
|
|
149
|
+
return 1
|
|
150
|
+
|
|
151
|
+
return 0
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
exit(main())
|
cadence/signal.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Signal definition for Cadence workflows.
|
|
3
|
+
|
|
4
|
+
This module provides the SignalDefinition class used internally by WorkflowDefinition
|
|
5
|
+
to track signal handler metadata.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import inspect
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from functools import update_wrapper
|
|
11
|
+
from inspect import Parameter, signature
|
|
12
|
+
from typing import (
|
|
13
|
+
Callable,
|
|
14
|
+
Generic,
|
|
15
|
+
ParamSpec,
|
|
16
|
+
Type,
|
|
17
|
+
TypeVar,
|
|
18
|
+
TypedDict,
|
|
19
|
+
get_type_hints,
|
|
20
|
+
Any,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
P = ParamSpec("P")
|
|
24
|
+
T = TypeVar("T")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class SignalParameter:
|
|
29
|
+
"""Parameter metadata for a signal handler."""
|
|
30
|
+
|
|
31
|
+
name: str
|
|
32
|
+
type_hint: Type | None
|
|
33
|
+
has_default: bool
|
|
34
|
+
default_value: Any
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SignalDefinitionOptions(TypedDict, total=False):
|
|
38
|
+
"""Options for defining a signal."""
|
|
39
|
+
|
|
40
|
+
name: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SignalDefinition(Generic[P, T]):
|
|
44
|
+
"""
|
|
45
|
+
Definition of a signal handler with metadata.
|
|
46
|
+
|
|
47
|
+
Similar to ActivityDefinition but for signal handlers.
|
|
48
|
+
Provides type safety and metadata for signal handlers.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
wrapped: Callable[P, T],
|
|
54
|
+
name: str,
|
|
55
|
+
params: list[SignalParameter],
|
|
56
|
+
is_async: bool,
|
|
57
|
+
):
|
|
58
|
+
self._wrapped = wrapped
|
|
59
|
+
self._name = name
|
|
60
|
+
self._params = params
|
|
61
|
+
self._is_async = is_async
|
|
62
|
+
update_wrapper(self, wrapped)
|
|
63
|
+
|
|
64
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
|
|
65
|
+
"""Call the wrapped signal handler function."""
|
|
66
|
+
return self._wrapped(*args, **kwargs)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def name(self) -> str:
|
|
70
|
+
"""Get the signal name."""
|
|
71
|
+
return self._name
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def params(self) -> list[SignalParameter]:
|
|
75
|
+
"""Get the signal parameters."""
|
|
76
|
+
return self._params
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def is_async(self) -> bool:
|
|
80
|
+
"""Check if the signal handler is async."""
|
|
81
|
+
return self._is_async
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def wrapped(self) -> Callable[P, T]:
|
|
85
|
+
"""Get the wrapped signal handler function."""
|
|
86
|
+
return self._wrapped
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def wrap(
|
|
90
|
+
fn: Callable[P, T], opts: SignalDefinitionOptions
|
|
91
|
+
) -> "SignalDefinition[P, T]":
|
|
92
|
+
"""
|
|
93
|
+
Wrap a function as a SignalDefinition.
|
|
94
|
+
|
|
95
|
+
This is an internal method used by WorkflowDefinition to create signal definitions
|
|
96
|
+
from methods decorated with @workflow.signal.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
fn: The signal handler function to wrap
|
|
100
|
+
opts: Options for the signal definition
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
A SignalDefinition instance
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
ValueError: If return type is not None
|
|
107
|
+
"""
|
|
108
|
+
name = opts.get("name") or fn.__qualname__
|
|
109
|
+
is_async = inspect.iscoroutinefunction(fn)
|
|
110
|
+
params = _get_signal_signature(fn)
|
|
111
|
+
_validate_signal_return_type(fn)
|
|
112
|
+
|
|
113
|
+
return SignalDefinition[P, T](fn, name, params, is_async)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _validate_signal_return_type(fn: Callable) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Validate that signal handler returns None.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
fn: The signal handler function
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
ValueError: If return type is not None
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
hints = get_type_hints(fn)
|
|
128
|
+
ret_type = hints.get("return", inspect.Signature.empty)
|
|
129
|
+
|
|
130
|
+
if ret_type is not None and ret_type is not inspect.Signature.empty:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
f"Signal handler '{fn.__qualname__}' must return None "
|
|
133
|
+
f"(signals cannot return values), got {ret_type}"
|
|
134
|
+
)
|
|
135
|
+
except NameError:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _get_signal_signature(fn: Callable[P, T]) -> list[SignalParameter]:
|
|
140
|
+
"""
|
|
141
|
+
Extract parameter information from a signal handler function.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
fn: The signal handler function
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
List of SignalParameter objects
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
ValueError: If parameters are not positional
|
|
151
|
+
"""
|
|
152
|
+
sig = signature(fn)
|
|
153
|
+
args = sig.parameters
|
|
154
|
+
hints = get_type_hints(fn)
|
|
155
|
+
params = []
|
|
156
|
+
|
|
157
|
+
for name, param in args.items():
|
|
158
|
+
# Filter out the self parameter for instance methods
|
|
159
|
+
if param.name == "self":
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
has_default = param.default != Parameter.empty
|
|
163
|
+
default = param.default if has_default else None
|
|
164
|
+
|
|
165
|
+
if param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD):
|
|
166
|
+
type_hint = hints.get(name, None)
|
|
167
|
+
params.append(SignalParameter(name, type_hint, has_default, default))
|
|
168
|
+
else:
|
|
169
|
+
raise ValueError(
|
|
170
|
+
f"Signal handler '{fn.__qualname__}' parameter '{name}' must be positional, "
|
|
171
|
+
f"got {param.kind.name}"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return params
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from cadence._internal.activity import ActivityExecutor
|
|
5
|
+
from cadence.api.v1.service_worker_pb2 import (
|
|
6
|
+
PollForActivityTaskResponse,
|
|
7
|
+
PollForActivityTaskRequest,
|
|
8
|
+
)
|
|
9
|
+
from cadence.api.v1.tasklist_pb2 import TaskList, TaskListKind
|
|
10
|
+
from cadence.client import Client
|
|
11
|
+
from cadence.worker._registry import Registry
|
|
12
|
+
from cadence.worker._types import WorkerOptions, _LONG_POLL_TIMEOUT
|
|
13
|
+
from cadence.worker._poller import Poller
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ActivityWorker:
|
|
17
|
+
def __init__(
|
|
18
|
+
self, client: Client, task_list: str, registry: Registry, options: WorkerOptions
|
|
19
|
+
) -> None:
|
|
20
|
+
self._client = client
|
|
21
|
+
self._task_list = task_list
|
|
22
|
+
self._identity = options["identity"]
|
|
23
|
+
max_concurrent = options["max_concurrent_activity_execution_size"]
|
|
24
|
+
permits = asyncio.Semaphore(max_concurrent)
|
|
25
|
+
self._executor = ActivityExecutor(
|
|
26
|
+
self._client,
|
|
27
|
+
self._task_list,
|
|
28
|
+
options["identity"],
|
|
29
|
+
max_concurrent,
|
|
30
|
+
registry.get_activity,
|
|
31
|
+
)
|
|
32
|
+
self._poller = Poller[PollForActivityTaskResponse](
|
|
33
|
+
options["activity_task_pollers"], permits, self._poll, self._execute
|
|
34
|
+
)
|
|
35
|
+
# TODO: Local dispatch, local activities, actually running activities, etc
|
|
36
|
+
|
|
37
|
+
async def run(self) -> None:
|
|
38
|
+
await self._poller.run()
|
|
39
|
+
|
|
40
|
+
async def _poll(self) -> Optional[PollForActivityTaskResponse]:
|
|
41
|
+
task: PollForActivityTaskResponse = (
|
|
42
|
+
await self._client.worker_stub.PollForActivityTask(
|
|
43
|
+
PollForActivityTaskRequest(
|
|
44
|
+
domain=self._client.domain,
|
|
45
|
+
task_list=TaskList(
|
|
46
|
+
name=self._task_list, kind=TaskListKind.TASK_LIST_KIND_NORMAL
|
|
47
|
+
),
|
|
48
|
+
identity=self._identity,
|
|
49
|
+
),
|
|
50
|
+
timeout=_LONG_POLL_TIMEOUT,
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if task.task_token:
|
|
55
|
+
return task
|
|
56
|
+
else:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
async def _execute(self, task: PollForActivityTaskResponse) -> None:
|
|
60
|
+
await self._executor.execute(task)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import TypeVar, Generic
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseTaskHandler(ABC, Generic[T]):
|
|
11
|
+
"""
|
|
12
|
+
Base task handler that provides common functionality for processing tasks.
|
|
13
|
+
|
|
14
|
+
This abstract class defines the interface and common behavior for task handlers
|
|
15
|
+
that process different types of tasks (workflow decisions, activities, etc.).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, client, task_list: str, identity: str, **options):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the base task handler.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
client: The Cadence client instance
|
|
24
|
+
task_list: The task list name
|
|
25
|
+
identity: Worker identity
|
|
26
|
+
**options: Additional options for the handler
|
|
27
|
+
"""
|
|
28
|
+
self._client = client
|
|
29
|
+
self.task_list = task_list
|
|
30
|
+
self._identity = identity
|
|
31
|
+
self._options = options
|
|
32
|
+
|
|
33
|
+
async def handle_task(self, task: T) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Handle a single task.
|
|
36
|
+
|
|
37
|
+
This method provides the base implementation for task handling that includes:
|
|
38
|
+
- Error handling
|
|
39
|
+
- Cleanup
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
task: The task to handle
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
# Handle the task implementation
|
|
46
|
+
await self._handle_task_implementation(task)
|
|
47
|
+
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.exception(f"Error handling task: {e}")
|
|
50
|
+
await self.handle_task_failure(task, e)
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
async def _handle_task_implementation(self, task: T) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Handle the actual task implementation.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
task: The task to handle
|
|
59
|
+
"""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
async def handle_task_failure(self, task: T, error: Exception) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Handle task processing failure.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
task: The task that failed
|
|
69
|
+
error: The exception that occurred
|
|
70
|
+
"""
|
|
71
|
+
pass
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from cadence.api.v1.service_worker_pb2 import (
|
|
6
|
+
PollForDecisionTaskRequest,
|
|
7
|
+
PollForDecisionTaskResponse,
|
|
8
|
+
)
|
|
9
|
+
from cadence.api.v1.tasklist_pb2 import TaskList, TaskListKind
|
|
10
|
+
from cadence.client import Client
|
|
11
|
+
from cadence.worker._decision_task_handler import DecisionTaskHandler
|
|
12
|
+
from cadence.worker._poller import Poller
|
|
13
|
+
from cadence.worker._registry import Registry
|
|
14
|
+
from cadence.worker._types import _LONG_POLL_TIMEOUT, WorkerOptions
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DecisionWorker:
|
|
18
|
+
def __init__(
|
|
19
|
+
self, client: Client, task_list: str, registry: Registry, options: WorkerOptions
|
|
20
|
+
) -> None:
|
|
21
|
+
self._client = client
|
|
22
|
+
self._task_list = task_list
|
|
23
|
+
self._registry = registry
|
|
24
|
+
self._identity = options["identity"]
|
|
25
|
+
permits = asyncio.Semaphore(
|
|
26
|
+
options["max_concurrent_decision_task_execution_size"]
|
|
27
|
+
)
|
|
28
|
+
executor = ThreadPoolExecutor(
|
|
29
|
+
max_workers=options["max_concurrent_decision_task_execution_size"]
|
|
30
|
+
)
|
|
31
|
+
self._decision_handler = DecisionTaskHandler(
|
|
32
|
+
client, task_list, registry, executor=executor, **options
|
|
33
|
+
)
|
|
34
|
+
self._poller = Poller[PollForDecisionTaskResponse](
|
|
35
|
+
options["decision_task_pollers"], permits, self._poll, self._execute
|
|
36
|
+
)
|
|
37
|
+
# TODO: Sticky poller, actually running workflows, etc.
|
|
38
|
+
|
|
39
|
+
async def run(self) -> None:
|
|
40
|
+
await self._poller.run()
|
|
41
|
+
|
|
42
|
+
async def _poll(self) -> Optional[PollForDecisionTaskResponse]:
|
|
43
|
+
task: PollForDecisionTaskResponse = (
|
|
44
|
+
await self._client.worker_stub.PollForDecisionTask(
|
|
45
|
+
PollForDecisionTaskRequest(
|
|
46
|
+
domain=self._client.domain,
|
|
47
|
+
task_list=TaskList(
|
|
48
|
+
name=self._task_list, kind=TaskListKind.TASK_LIST_KIND_NORMAL
|
|
49
|
+
),
|
|
50
|
+
identity=self._identity,
|
|
51
|
+
),
|
|
52
|
+
timeout=_LONG_POLL_TIMEOUT,
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if task and task.task_token:
|
|
57
|
+
return task
|
|
58
|
+
else:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
async def _execute(self, task: PollForDecisionTaskResponse) -> None:
|
|
62
|
+
await self._decision_handler.handle_task(task)
|