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
cadence/client.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import socket
|
|
3
|
+
import uuid
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
from typing import TypedDict, Unpack, Any, cast, Union
|
|
6
|
+
|
|
7
|
+
from grpc import ChannelCredentials, Compression
|
|
8
|
+
from google.protobuf.duration_pb2 import Duration
|
|
9
|
+
|
|
10
|
+
from cadence._internal.rpc.error import CadenceErrorInterceptor
|
|
11
|
+
from cadence._internal.rpc.retry import RetryInterceptor
|
|
12
|
+
from cadence._internal.rpc.yarpc import YarpcMetadataInterceptor
|
|
13
|
+
from cadence.api.v1.service_domain_pb2_grpc import DomainAPIStub
|
|
14
|
+
from cadence.api.v1.service_worker_pb2_grpc import WorkerAPIStub
|
|
15
|
+
from grpc.aio import Channel, ClientInterceptor, secure_channel, insecure_channel
|
|
16
|
+
from cadence.api.v1.service_workflow_pb2_grpc import WorkflowAPIStub
|
|
17
|
+
from cadence.api.v1.service_workflow_pb2 import (
|
|
18
|
+
SignalWorkflowExecutionRequest,
|
|
19
|
+
StartWorkflowExecutionRequest,
|
|
20
|
+
StartWorkflowExecutionResponse,
|
|
21
|
+
SignalWithStartWorkflowExecutionRequest,
|
|
22
|
+
SignalWithStartWorkflowExecutionResponse,
|
|
23
|
+
)
|
|
24
|
+
from cadence.api.v1.common_pb2 import WorkflowType, WorkflowExecution
|
|
25
|
+
from cadence.api.v1.tasklist_pb2 import TaskList
|
|
26
|
+
from cadence.data_converter import DataConverter, DefaultDataConverter
|
|
27
|
+
from cadence.metrics import MetricsEmitter, NoOpMetricsEmitter
|
|
28
|
+
from cadence.workflow import WorkflowDefinition
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class StartWorkflowOptions(TypedDict, total=False):
|
|
32
|
+
"""Options for starting a workflow execution."""
|
|
33
|
+
|
|
34
|
+
task_list: str
|
|
35
|
+
execution_start_to_close_timeout: timedelta
|
|
36
|
+
workflow_id: str
|
|
37
|
+
task_start_to_close_timeout: timedelta
|
|
38
|
+
cron_schedule: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _validate_and_apply_defaults(options: StartWorkflowOptions) -> StartWorkflowOptions:
|
|
42
|
+
"""Validate required fields and apply defaults to StartWorkflowOptions."""
|
|
43
|
+
if not options.get("task_list"):
|
|
44
|
+
raise ValueError("task_list is required")
|
|
45
|
+
|
|
46
|
+
execution_timeout = options.get("execution_start_to_close_timeout")
|
|
47
|
+
if not execution_timeout:
|
|
48
|
+
raise ValueError("execution_start_to_close_timeout is required")
|
|
49
|
+
if execution_timeout <= timedelta(0):
|
|
50
|
+
raise ValueError("execution_start_to_close_timeout must be greater than 0")
|
|
51
|
+
|
|
52
|
+
# Apply default for task_start_to_close_timeout if not provided (matching Go/Java clients)
|
|
53
|
+
task_timeout = options.get("task_start_to_close_timeout")
|
|
54
|
+
if task_timeout is None:
|
|
55
|
+
options["task_start_to_close_timeout"] = timedelta(seconds=10)
|
|
56
|
+
elif task_timeout <= timedelta(0):
|
|
57
|
+
raise ValueError("task_start_to_close_timeout must be greater than 0")
|
|
58
|
+
|
|
59
|
+
return options
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ClientOptions(TypedDict, total=False):
|
|
63
|
+
domain: str
|
|
64
|
+
target: str
|
|
65
|
+
data_converter: DataConverter
|
|
66
|
+
identity: str
|
|
67
|
+
service_name: str
|
|
68
|
+
caller_name: str
|
|
69
|
+
channel_arguments: dict[str, Any]
|
|
70
|
+
credentials: ChannelCredentials | None
|
|
71
|
+
compression: Compression
|
|
72
|
+
metrics_emitter: MetricsEmitter
|
|
73
|
+
interceptors: list[ClientInterceptor]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
_DEFAULT_OPTIONS: ClientOptions = {
|
|
77
|
+
"data_converter": DefaultDataConverter(),
|
|
78
|
+
"identity": f"{os.getpid()}@{socket.gethostname()}",
|
|
79
|
+
"service_name": "cadence-frontend",
|
|
80
|
+
"caller_name": "cadence-client",
|
|
81
|
+
"channel_arguments": {},
|
|
82
|
+
"credentials": None,
|
|
83
|
+
"compression": Compression.NoCompression,
|
|
84
|
+
"metrics_emitter": NoOpMetricsEmitter(),
|
|
85
|
+
"interceptors": [],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Client:
|
|
90
|
+
def __init__(self, **kwargs: Unpack[ClientOptions]) -> None:
|
|
91
|
+
self._options = _validate_and_copy_defaults(ClientOptions(**kwargs))
|
|
92
|
+
self._channel = _create_channel(self._options)
|
|
93
|
+
self._worker_stub = WorkerAPIStub(self._channel)
|
|
94
|
+
self._domain_stub = DomainAPIStub(self._channel)
|
|
95
|
+
self._workflow_stub = WorkflowAPIStub(self._channel)
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def data_converter(self) -> DataConverter:
|
|
99
|
+
return self._options["data_converter"]
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def domain(self) -> str:
|
|
103
|
+
return self._options["domain"]
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def identity(self) -> str:
|
|
107
|
+
return self._options["identity"]
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def domain_stub(self) -> DomainAPIStub:
|
|
111
|
+
return self._domain_stub
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def worker_stub(self) -> WorkerAPIStub:
|
|
115
|
+
return self._worker_stub
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def workflow_stub(self) -> WorkflowAPIStub:
|
|
119
|
+
return self._workflow_stub
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def metrics_emitter(self) -> MetricsEmitter:
|
|
123
|
+
return self._options["metrics_emitter"]
|
|
124
|
+
|
|
125
|
+
async def ready(self) -> None:
|
|
126
|
+
await self._channel.channel_ready()
|
|
127
|
+
|
|
128
|
+
async def close(self) -> None:
|
|
129
|
+
await self._channel.close()
|
|
130
|
+
|
|
131
|
+
async def __aenter__(self) -> "Client":
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
135
|
+
await self.close()
|
|
136
|
+
|
|
137
|
+
def _build_start_workflow_request(
|
|
138
|
+
self,
|
|
139
|
+
workflow: Union[str, WorkflowDefinition],
|
|
140
|
+
args: tuple[Any, ...],
|
|
141
|
+
options: StartWorkflowOptions,
|
|
142
|
+
) -> StartWorkflowExecutionRequest:
|
|
143
|
+
"""Build a StartWorkflowExecutionRequest from parameters."""
|
|
144
|
+
# Generate workflow ID if not provided
|
|
145
|
+
workflow_id = options.get("workflow_id") or str(uuid.uuid4())
|
|
146
|
+
|
|
147
|
+
# Determine workflow type name
|
|
148
|
+
if isinstance(workflow, str):
|
|
149
|
+
workflow_type_name = workflow
|
|
150
|
+
else:
|
|
151
|
+
# For WorkflowDefinition, use the name property
|
|
152
|
+
workflow_type_name = workflow.name
|
|
153
|
+
|
|
154
|
+
# Encode input arguments
|
|
155
|
+
input_payload = None
|
|
156
|
+
if args:
|
|
157
|
+
try:
|
|
158
|
+
input_payload = self.data_converter.to_data(list(args))
|
|
159
|
+
except Exception as e:
|
|
160
|
+
raise ValueError(f"Failed to encode workflow arguments: {e}")
|
|
161
|
+
|
|
162
|
+
# Convert timedelta to protobuf Duration
|
|
163
|
+
execution_timeout = Duration()
|
|
164
|
+
execution_timeout.FromTimedelta(options["execution_start_to_close_timeout"])
|
|
165
|
+
|
|
166
|
+
task_timeout = Duration()
|
|
167
|
+
task_timeout.FromTimedelta(options["task_start_to_close_timeout"])
|
|
168
|
+
|
|
169
|
+
# Build the request
|
|
170
|
+
request = StartWorkflowExecutionRequest(
|
|
171
|
+
domain=self.domain,
|
|
172
|
+
workflow_id=workflow_id,
|
|
173
|
+
workflow_type=WorkflowType(name=workflow_type_name),
|
|
174
|
+
task_list=TaskList(name=options["task_list"]),
|
|
175
|
+
identity=self.identity,
|
|
176
|
+
request_id=str(uuid.uuid4()),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Set required timeout fields
|
|
180
|
+
request.execution_start_to_close_timeout.CopyFrom(execution_timeout)
|
|
181
|
+
request.task_start_to_close_timeout.CopyFrom(task_timeout)
|
|
182
|
+
|
|
183
|
+
# Set optional fields
|
|
184
|
+
if input_payload:
|
|
185
|
+
request.input.CopyFrom(input_payload)
|
|
186
|
+
if options.get("cron_schedule"):
|
|
187
|
+
request.cron_schedule = options["cron_schedule"]
|
|
188
|
+
|
|
189
|
+
return request
|
|
190
|
+
|
|
191
|
+
async def start_workflow(
|
|
192
|
+
self,
|
|
193
|
+
workflow: Union[str, WorkflowDefinition],
|
|
194
|
+
*args,
|
|
195
|
+
**options_kwargs: Unpack[StartWorkflowOptions],
|
|
196
|
+
) -> WorkflowExecution:
|
|
197
|
+
"""
|
|
198
|
+
Start a workflow execution asynchronously.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
workflow: WorkflowDefinition or workflow type name string
|
|
202
|
+
*args: Arguments to pass to the workflow
|
|
203
|
+
**options_kwargs: StartWorkflowOptions as keyword arguments
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
WorkflowExecution with workflow_id and run_id
|
|
207
|
+
|
|
208
|
+
Raises:
|
|
209
|
+
ValueError: If required parameters are missing or invalid
|
|
210
|
+
Exception: If the gRPC call fails
|
|
211
|
+
"""
|
|
212
|
+
# Convert kwargs to StartWorkflowOptions and validate
|
|
213
|
+
options = _validate_and_apply_defaults(StartWorkflowOptions(**options_kwargs))
|
|
214
|
+
|
|
215
|
+
# Build the gRPC request
|
|
216
|
+
request = self._build_start_workflow_request(workflow, args, options)
|
|
217
|
+
|
|
218
|
+
# Execute the gRPC call
|
|
219
|
+
try:
|
|
220
|
+
response: StartWorkflowExecutionResponse = (
|
|
221
|
+
await self.workflow_stub.StartWorkflowExecution(request)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Emit metrics if available
|
|
225
|
+
if self.metrics_emitter:
|
|
226
|
+
# TODO: Add workflow start metrics similar to Go client
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
execution = WorkflowExecution()
|
|
230
|
+
execution.workflow_id = request.workflow_id
|
|
231
|
+
execution.run_id = response.run_id
|
|
232
|
+
return execution
|
|
233
|
+
except Exception:
|
|
234
|
+
raise
|
|
235
|
+
|
|
236
|
+
async def signal_workflow(
|
|
237
|
+
self,
|
|
238
|
+
workflow_id: str,
|
|
239
|
+
run_id: str,
|
|
240
|
+
signal_name: str,
|
|
241
|
+
*signal_args: Any,
|
|
242
|
+
) -> None:
|
|
243
|
+
"""
|
|
244
|
+
Send a signal to a running workflow execution.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
workflow_id: The workflow ID
|
|
248
|
+
run_id: The run ID (can be empty string to signal current run)
|
|
249
|
+
signal_name: Name of the signal
|
|
250
|
+
*signal_args: Arguments to pass to the signal handler
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
ValueError: If signal encoding fails
|
|
254
|
+
Exception: If the gRPC call fails
|
|
255
|
+
"""
|
|
256
|
+
signal_payload = None
|
|
257
|
+
if signal_args:
|
|
258
|
+
try:
|
|
259
|
+
signal_payload = self.data_converter.to_data(list[Any](signal_args))
|
|
260
|
+
except Exception as e:
|
|
261
|
+
raise ValueError(f"Failed to encode signal input: {e}")
|
|
262
|
+
|
|
263
|
+
workflow_execution = WorkflowExecution()
|
|
264
|
+
workflow_execution.workflow_id = workflow_id
|
|
265
|
+
if run_id:
|
|
266
|
+
workflow_execution.run_id = run_id
|
|
267
|
+
|
|
268
|
+
signal_request = SignalWorkflowExecutionRequest(
|
|
269
|
+
domain=self.domain,
|
|
270
|
+
workflow_execution=workflow_execution,
|
|
271
|
+
identity=self.identity,
|
|
272
|
+
request_id=str(uuid.uuid4()),
|
|
273
|
+
signal_name=signal_name,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
if signal_payload:
|
|
277
|
+
signal_request.signal_input.CopyFrom(signal_payload)
|
|
278
|
+
|
|
279
|
+
await self.workflow_stub.SignalWorkflowExecution(signal_request)
|
|
280
|
+
|
|
281
|
+
async def signal_with_start_workflow(
|
|
282
|
+
self,
|
|
283
|
+
workflow: Union[str, WorkflowDefinition],
|
|
284
|
+
signal_name: str,
|
|
285
|
+
signal_args: list[Any],
|
|
286
|
+
*workflow_args: Any,
|
|
287
|
+
**options_kwargs: Unpack[StartWorkflowOptions],
|
|
288
|
+
) -> WorkflowExecution:
|
|
289
|
+
"""
|
|
290
|
+
Signal a workflow execution, starting it if it is not already running.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
workflow: WorkflowDefinition or workflow type name string
|
|
294
|
+
signal_name: Name of the signal
|
|
295
|
+
signal_args: List of arguments to pass to the signal handler
|
|
296
|
+
*workflow_args: Arguments to pass to the workflow if it needs to be started
|
|
297
|
+
**options_kwargs: StartWorkflowOptions as keyword arguments
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
WorkflowExecution with workflow_id and run_id
|
|
301
|
+
|
|
302
|
+
Raises:
|
|
303
|
+
ValueError: If required parameters are missing or invalid
|
|
304
|
+
Exception: If the gRPC call fails
|
|
305
|
+
"""
|
|
306
|
+
# Convert kwargs to StartWorkflowOptions and validate
|
|
307
|
+
options = _validate_and_apply_defaults(StartWorkflowOptions(**options_kwargs))
|
|
308
|
+
|
|
309
|
+
# Build the start workflow request
|
|
310
|
+
start_request = self._build_start_workflow_request(
|
|
311
|
+
workflow, workflow_args, options
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Encode signal input
|
|
315
|
+
signal_payload = None
|
|
316
|
+
if signal_args:
|
|
317
|
+
try:
|
|
318
|
+
signal_payload = self.data_converter.to_data(signal_args)
|
|
319
|
+
except Exception as e:
|
|
320
|
+
raise ValueError(f"Failed to encode signal input: {e}")
|
|
321
|
+
|
|
322
|
+
# Build the SignalWithStartWorkflowExecution request
|
|
323
|
+
request = SignalWithStartWorkflowExecutionRequest(
|
|
324
|
+
start_request=start_request,
|
|
325
|
+
signal_name=signal_name,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if signal_payload:
|
|
329
|
+
request.signal_input.CopyFrom(signal_payload)
|
|
330
|
+
|
|
331
|
+
# Execute the gRPC call
|
|
332
|
+
try:
|
|
333
|
+
response: SignalWithStartWorkflowExecutionResponse = (
|
|
334
|
+
await self.workflow_stub.SignalWithStartWorkflowExecution(request)
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
execution = WorkflowExecution()
|
|
338
|
+
execution.workflow_id = start_request.workflow_id
|
|
339
|
+
execution.run_id = response.run_id
|
|
340
|
+
return execution
|
|
341
|
+
except Exception:
|
|
342
|
+
raise
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _validate_and_copy_defaults(options: ClientOptions) -> ClientOptions:
|
|
346
|
+
if "target" not in options:
|
|
347
|
+
raise ValueError("target must be specified")
|
|
348
|
+
|
|
349
|
+
if "domain" not in options:
|
|
350
|
+
raise ValueError("domain must be specified")
|
|
351
|
+
|
|
352
|
+
# Set default values for missing options
|
|
353
|
+
for key, value in _DEFAULT_OPTIONS.items():
|
|
354
|
+
if key not in options:
|
|
355
|
+
cast(dict, options)[key] = value
|
|
356
|
+
|
|
357
|
+
return options
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _create_channel(options: ClientOptions) -> Channel:
|
|
361
|
+
interceptors = list(options["interceptors"])
|
|
362
|
+
interceptors.append(
|
|
363
|
+
YarpcMetadataInterceptor(options["service_name"], options["caller_name"])
|
|
364
|
+
)
|
|
365
|
+
interceptors.append(RetryInterceptor())
|
|
366
|
+
interceptors.append(CadenceErrorInterceptor())
|
|
367
|
+
|
|
368
|
+
if options["credentials"]:
|
|
369
|
+
return secure_channel(
|
|
370
|
+
options["target"],
|
|
371
|
+
options["credentials"],
|
|
372
|
+
options["channel_arguments"],
|
|
373
|
+
options["compression"],
|
|
374
|
+
interceptors,
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
return insecure_channel(
|
|
378
|
+
options["target"],
|
|
379
|
+
options["channel_arguments"],
|
|
380
|
+
options["compression"],
|
|
381
|
+
interceptors,
|
|
382
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from typing import Protocol, List, Type, Any
|
|
3
|
+
|
|
4
|
+
from cadence.api.v1.common_pb2 import Payload
|
|
5
|
+
from json import JSONDecoder
|
|
6
|
+
from msgspec import json, convert
|
|
7
|
+
|
|
8
|
+
_SPACE = " ".encode()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DataConverter(Protocol):
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def from_data(self, payload: Payload, type_hints: List[Type | None]) -> List[Any]:
|
|
14
|
+
raise NotImplementedError()
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def to_data(self, values: List[Any]) -> Payload:
|
|
18
|
+
raise NotImplementedError()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DefaultDataConverter(DataConverter):
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
self._encoder = json.Encoder()
|
|
24
|
+
# Need to use std lib decoder in order to decode the custom whitespace delimited data format
|
|
25
|
+
self._decoder = JSONDecoder(strict=False)
|
|
26
|
+
|
|
27
|
+
def from_data(self, payload: Payload, type_hints: List[Type | None]) -> List[Any]:
|
|
28
|
+
if not payload.data:
|
|
29
|
+
return DefaultDataConverter._convert_into([], type_hints)
|
|
30
|
+
|
|
31
|
+
payload_str = payload.data.decode()
|
|
32
|
+
|
|
33
|
+
return self._decode_whitespace_delimited(payload_str, type_hints)
|
|
34
|
+
|
|
35
|
+
def _decode_whitespace_delimited(
|
|
36
|
+
self, payload: str, type_hints: List[Type | None]
|
|
37
|
+
) -> List[Any]:
|
|
38
|
+
results: List[Any] = []
|
|
39
|
+
start, end = 0, len(payload)
|
|
40
|
+
while start < end and len(results) < len(type_hints):
|
|
41
|
+
remaining = payload[start:end]
|
|
42
|
+
(value, value_end) = self._decoder.raw_decode(remaining)
|
|
43
|
+
start += value_end + 1
|
|
44
|
+
results.append(value)
|
|
45
|
+
|
|
46
|
+
return DefaultDataConverter._convert_into(results, type_hints)
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def _convert_into(values: List[Any], type_hints: List[Type | None]) -> List[Any]:
|
|
50
|
+
results: List[Any] = []
|
|
51
|
+
for i, type_hint in enumerate(type_hints):
|
|
52
|
+
if not type_hint:
|
|
53
|
+
value = values[i]
|
|
54
|
+
elif i < len(values):
|
|
55
|
+
value = convert(values[i], type_hint)
|
|
56
|
+
else:
|
|
57
|
+
value = DefaultDataConverter._get_default(type_hint)
|
|
58
|
+
|
|
59
|
+
results.append(value)
|
|
60
|
+
|
|
61
|
+
return results
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def _get_default(type_hint: Type) -> Any:
|
|
65
|
+
if type_hint in (int, float):
|
|
66
|
+
return 0
|
|
67
|
+
if type_hint is bool:
|
|
68
|
+
return False
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
def to_data(self, values: List[Any]) -> Payload:
|
|
72
|
+
result = bytearray()
|
|
73
|
+
for index, value in enumerate(values):
|
|
74
|
+
self._encoder.encode_into(value, result, -1)
|
|
75
|
+
if index < len(values) - 1:
|
|
76
|
+
result += _SPACE
|
|
77
|
+
|
|
78
|
+
return Payload(data=bytes(result))
|
cadence/error.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import grpc
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ActivityFailure(Exception):
|
|
5
|
+
def __init__(self, message: str) -> None:
|
|
6
|
+
super().__init__(message)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WorkflowFailure(Exception):
|
|
10
|
+
def __init__(self, message: str) -> None:
|
|
11
|
+
super().__init__(message)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CadenceRpcError(Exception):
|
|
15
|
+
def __init__(self, message: str, code: grpc.StatusCode, *args):
|
|
16
|
+
super().__init__(message, code, *args)
|
|
17
|
+
self.code = code
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WorkflowExecutionAlreadyStartedError(CadenceRpcError):
|
|
21
|
+
def __init__(
|
|
22
|
+
self, message: str, code: grpc.StatusCode, start_request_id: str, run_id: str
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__(message, code, start_request_id, run_id)
|
|
25
|
+
self.start_request_id = start_request_id
|
|
26
|
+
self.run_id = run_id
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class EntityNotExistsError(CadenceRpcError):
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
message: str,
|
|
33
|
+
code: grpc.StatusCode,
|
|
34
|
+
current_cluster: str,
|
|
35
|
+
active_cluster: str,
|
|
36
|
+
active_clusters: list[str],
|
|
37
|
+
) -> None:
|
|
38
|
+
super().__init__(
|
|
39
|
+
message, code, current_cluster, active_cluster, active_clusters
|
|
40
|
+
)
|
|
41
|
+
self.current_cluster = current_cluster
|
|
42
|
+
self.active_cluster = active_cluster
|
|
43
|
+
self.active_clusters = active_clusters
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class WorkflowExecutionAlreadyCompletedError(CadenceRpcError):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DomainNotActiveError(CadenceRpcError):
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
message: str,
|
|
54
|
+
code: grpc.StatusCode,
|
|
55
|
+
domain: str,
|
|
56
|
+
current_cluster: str,
|
|
57
|
+
active_cluster: str,
|
|
58
|
+
active_clusters: list[str],
|
|
59
|
+
) -> None:
|
|
60
|
+
super().__init__(
|
|
61
|
+
message, code, domain, current_cluster, active_cluster, active_clusters
|
|
62
|
+
)
|
|
63
|
+
self.domain = domain
|
|
64
|
+
self.current_cluster = current_cluster
|
|
65
|
+
self.active_cluster = active_cluster
|
|
66
|
+
self.active_clusters = active_clusters
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ClientVersionNotSupportedError(CadenceRpcError):
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
message: str,
|
|
73
|
+
code: grpc.StatusCode,
|
|
74
|
+
feature_version: str,
|
|
75
|
+
client_impl: str,
|
|
76
|
+
supported_versions: str,
|
|
77
|
+
) -> None:
|
|
78
|
+
super().__init__(
|
|
79
|
+
message, code, feature_version, client_impl, supported_versions
|
|
80
|
+
)
|
|
81
|
+
self.feature_version = feature_version
|
|
82
|
+
self.client_impl = client_impl
|
|
83
|
+
self.supported_versions = supported_versions
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class FeatureNotEnabledError(CadenceRpcError):
|
|
87
|
+
def __init__(self, message: str, code: grpc.StatusCode, feature_flag: str) -> None:
|
|
88
|
+
super().__init__(message, code, feature_flag)
|
|
89
|
+
self.feature_flag = feature_flag
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class CancellationAlreadyRequestedError(CadenceRpcError):
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class DomainAlreadyExistsError(CadenceRpcError):
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class LimitExceededError(CadenceRpcError):
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class QueryFailedError(CadenceRpcError):
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ServiceBusyError(CadenceRpcError):
|
|
109
|
+
def __init__(self, message: str, code: grpc.StatusCode, reason: str) -> None:
|
|
110
|
+
super().__init__(message, code, reason)
|
|
111
|
+
self.reason = reason
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Metrics collection components for Cadence client."""
|
|
2
|
+
|
|
3
|
+
from .metrics import MetricsEmitter, NoOpMetricsEmitter, MetricType
|
|
4
|
+
from .prometheus import PrometheusMetrics, PrometheusConfig
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"MetricsEmitter",
|
|
8
|
+
"NoOpMetricsEmitter",
|
|
9
|
+
"MetricType",
|
|
10
|
+
"PrometheusMetrics",
|
|
11
|
+
"PrometheusConfig",
|
|
12
|
+
]
|