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,498 @@
|
|
|
1
|
+
from asyncio import AbstractEventLoop, Handle, TimerHandle, futures, tasks, Future, Task
|
|
2
|
+
from contextvars import Context
|
|
3
|
+
import logging
|
|
4
|
+
import collections
|
|
5
|
+
import asyncio.events as events
|
|
6
|
+
import threading
|
|
7
|
+
from typing import Callable, Any, TypeVar, Coroutine, Awaitable, Generator
|
|
8
|
+
from typing_extensions import Unpack, TypeVarTuple
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
_Ts = TypeVarTuple("_Ts")
|
|
13
|
+
_T = TypeVar("_T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DeterministicEventLoop(AbstractEventLoop):
|
|
17
|
+
"""
|
|
18
|
+
This is a basic FIFO implementation of event loop that does not allow I/O or timer operations.
|
|
19
|
+
As a result, it's theoretically deterministic. This event loop is not useful directly without async events processing inside the loop.
|
|
20
|
+
|
|
21
|
+
Code is mostly copied from asyncio.BaseEventLoop without I/O or timer operations.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self) -> None:
|
|
25
|
+
self._thread_id: int | None = None # indicate if the event loop is running
|
|
26
|
+
self._debug: bool = False
|
|
27
|
+
self._ready: collections.deque[events.Handle] = collections.deque()
|
|
28
|
+
self._stopping: bool = False
|
|
29
|
+
self._closed: bool = False
|
|
30
|
+
|
|
31
|
+
def run_until_yield(self):
|
|
32
|
+
"""Run until stop() is called."""
|
|
33
|
+
self._run_forever_setup()
|
|
34
|
+
try:
|
|
35
|
+
while self._ready:
|
|
36
|
+
self._run_once()
|
|
37
|
+
finally:
|
|
38
|
+
self._run_forever_cleanup()
|
|
39
|
+
|
|
40
|
+
# Event Loop APIs
|
|
41
|
+
def call_soon(
|
|
42
|
+
self,
|
|
43
|
+
callback: Callable[[Unpack[_Ts]], object],
|
|
44
|
+
*args: Unpack[_Ts],
|
|
45
|
+
context: Context | None = None,
|
|
46
|
+
) -> Handle:
|
|
47
|
+
return self._call_soon(callback, args, context)
|
|
48
|
+
|
|
49
|
+
def _call_soon(
|
|
50
|
+
self,
|
|
51
|
+
callback: Callable[..., Any],
|
|
52
|
+
args: tuple[Any, ...],
|
|
53
|
+
context: Context | None,
|
|
54
|
+
) -> Handle:
|
|
55
|
+
handle = events.Handle(callback, args, self, context)
|
|
56
|
+
self._ready.append(handle)
|
|
57
|
+
return handle
|
|
58
|
+
|
|
59
|
+
def get_debug(self) -> bool:
|
|
60
|
+
return self._debug
|
|
61
|
+
|
|
62
|
+
def set_debug(self, enabled: bool) -> None:
|
|
63
|
+
self._debug = enabled
|
|
64
|
+
|
|
65
|
+
def run_forever(self) -> None:
|
|
66
|
+
"""Run until stop() is called."""
|
|
67
|
+
self._run_forever_setup()
|
|
68
|
+
try:
|
|
69
|
+
while True:
|
|
70
|
+
self._run_once()
|
|
71
|
+
if self._stopping:
|
|
72
|
+
break
|
|
73
|
+
finally:
|
|
74
|
+
self._run_forever_cleanup()
|
|
75
|
+
|
|
76
|
+
def run_until_complete(
|
|
77
|
+
self, future: Generator[Any, None, _T] | Awaitable[_T]
|
|
78
|
+
) -> _T:
|
|
79
|
+
"""Run until the Future is done.
|
|
80
|
+
|
|
81
|
+
If the argument is a coroutine, it is wrapped in a Task.
|
|
82
|
+
|
|
83
|
+
WARNING: It would be disastrous to call run_until_complete()
|
|
84
|
+
with the same coroutine twice -- it would wrap it in two
|
|
85
|
+
different Tasks and that can't be good.
|
|
86
|
+
|
|
87
|
+
Return the Future's result, or raise its exception.
|
|
88
|
+
"""
|
|
89
|
+
self._check_closed()
|
|
90
|
+
self._check_running()
|
|
91
|
+
|
|
92
|
+
new_task = not futures.isfuture(future)
|
|
93
|
+
future = tasks.ensure_future(future, loop=self) # type: ignore[arg-type]
|
|
94
|
+
|
|
95
|
+
future.add_done_callback(_run_until_complete_cb)
|
|
96
|
+
try:
|
|
97
|
+
self.run_forever()
|
|
98
|
+
except:
|
|
99
|
+
if new_task and future.done() and not future.cancelled():
|
|
100
|
+
# The coroutine raised a BaseException. Consume the exception
|
|
101
|
+
# to not log a warning, the caller doesn't have access to the
|
|
102
|
+
# local task.
|
|
103
|
+
future.exception()
|
|
104
|
+
raise
|
|
105
|
+
finally:
|
|
106
|
+
future.remove_done_callback(_run_until_complete_cb)
|
|
107
|
+
if not future.done():
|
|
108
|
+
raise RuntimeError("Event loop stopped before Future completed.")
|
|
109
|
+
|
|
110
|
+
return future.result()
|
|
111
|
+
|
|
112
|
+
def create_task(
|
|
113
|
+
self, coro: Generator[Any, None, _T] | Coroutine[Any, Any, _T], **kwargs: Any
|
|
114
|
+
) -> Task[_T]:
|
|
115
|
+
"""Schedule a coroutine object.
|
|
116
|
+
|
|
117
|
+
Return a task object.
|
|
118
|
+
"""
|
|
119
|
+
self._check_closed()
|
|
120
|
+
|
|
121
|
+
# NOTE: eager_start is not supported for deterministic event loop
|
|
122
|
+
if kwargs.get("eager_start", False):
|
|
123
|
+
raise RuntimeError(
|
|
124
|
+
"eager_start in create_task is not supported for deterministic event loop"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
task = tasks.Task(coro, loop=self, **kwargs)
|
|
128
|
+
# We intentionally destroy pending tasks when shutting down the event loop.
|
|
129
|
+
# If our asyncio implementation supports it, disable the logs
|
|
130
|
+
if hasattr(task, "_log_destroy_pending"):
|
|
131
|
+
setattr(task, "_log_destroy_pending", False)
|
|
132
|
+
return task
|
|
133
|
+
|
|
134
|
+
def create_future(self) -> Future[Any]:
|
|
135
|
+
return futures.Future(loop=self)
|
|
136
|
+
|
|
137
|
+
def _run_once(self) -> None:
|
|
138
|
+
ntodo = len(self._ready)
|
|
139
|
+
for i in range(ntodo):
|
|
140
|
+
handle = self._ready.popleft()
|
|
141
|
+
if handle._cancelled:
|
|
142
|
+
continue
|
|
143
|
+
handle._run()
|
|
144
|
+
|
|
145
|
+
def _run_forever_setup(self) -> None:
|
|
146
|
+
self._check_closed()
|
|
147
|
+
self._check_running()
|
|
148
|
+
self._thread_id = threading.get_ident()
|
|
149
|
+
events._set_running_loop(self)
|
|
150
|
+
|
|
151
|
+
def _run_forever_cleanup(self) -> None:
|
|
152
|
+
self._stopping = False
|
|
153
|
+
self._thread_id = None
|
|
154
|
+
events._set_running_loop(None)
|
|
155
|
+
|
|
156
|
+
def stop(self) -> None:
|
|
157
|
+
self._stopping = True
|
|
158
|
+
|
|
159
|
+
def _check_closed(self) -> None:
|
|
160
|
+
if self._closed:
|
|
161
|
+
raise RuntimeError("Event loop is closed")
|
|
162
|
+
|
|
163
|
+
def _check_running(self) -> None:
|
|
164
|
+
if self.is_running():
|
|
165
|
+
raise RuntimeError("This event loop is already running")
|
|
166
|
+
if events._get_running_loop() is not None:
|
|
167
|
+
raise RuntimeError(
|
|
168
|
+
"Cannot run the event loop while another loop is running"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def is_running(self) -> bool:
|
|
172
|
+
return self._thread_id is not None
|
|
173
|
+
|
|
174
|
+
def close(self) -> None:
|
|
175
|
+
"""Close the event loop.
|
|
176
|
+
The event loop must not be running.
|
|
177
|
+
"""
|
|
178
|
+
if self.is_running():
|
|
179
|
+
raise RuntimeError("Cannot close a running event loop")
|
|
180
|
+
if self._closed:
|
|
181
|
+
return
|
|
182
|
+
if self._debug:
|
|
183
|
+
logger.debug("Close %r", self)
|
|
184
|
+
self._closed = True
|
|
185
|
+
self._ready.clear()
|
|
186
|
+
|
|
187
|
+
def is_closed(self) -> bool:
|
|
188
|
+
"""Returns True if the event loop was closed."""
|
|
189
|
+
return self._closed
|
|
190
|
+
|
|
191
|
+
# Timer operations - not supported for deterministic execution
|
|
192
|
+
def time(self) -> float:
|
|
193
|
+
raise NotImplementedError("Timers not supported in deterministic event loop")
|
|
194
|
+
|
|
195
|
+
def call_later( # type: ignore[override]
|
|
196
|
+
self,
|
|
197
|
+
delay: float,
|
|
198
|
+
callback: Callable[..., Any],
|
|
199
|
+
*args: Any,
|
|
200
|
+
context: Context | None = None,
|
|
201
|
+
) -> TimerHandle:
|
|
202
|
+
raise NotImplementedError("Timers not supported in deterministic event loop")
|
|
203
|
+
|
|
204
|
+
def call_at( # type: ignore[override]
|
|
205
|
+
self,
|
|
206
|
+
when: float,
|
|
207
|
+
callback: Callable[..., Any],
|
|
208
|
+
*args: Any,
|
|
209
|
+
context: Context | None = None,
|
|
210
|
+
) -> TimerHandle:
|
|
211
|
+
raise NotImplementedError("Timers not supported in deterministic event loop")
|
|
212
|
+
|
|
213
|
+
# Thread operations - not supported
|
|
214
|
+
def call_soon_threadsafe( # type: ignore[override]
|
|
215
|
+
self, callback: Callable[..., Any], *args: Any, context: Context | None = None
|
|
216
|
+
) -> Handle:
|
|
217
|
+
raise NotImplementedError(
|
|
218
|
+
"Thread operations not supported in deterministic event loop"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def run_in_executor( # type: ignore[override]
|
|
222
|
+
self, executor: Any, func: Callable[..., Any], *args: Any
|
|
223
|
+
) -> Future[Any]:
|
|
224
|
+
raise NotImplementedError(
|
|
225
|
+
"Executor operations not supported in deterministic event loop"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def set_default_executor(self, executor: Any) -> None:
|
|
229
|
+
raise NotImplementedError(
|
|
230
|
+
"Executor operations not supported in deterministic event loop"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# I/O operations - not supported for deterministic execution
|
|
234
|
+
def add_reader(self, fd: int, callback: Callable[..., Any], *args: Any) -> None: # type: ignore[override]
|
|
235
|
+
raise NotImplementedError(
|
|
236
|
+
"I/O operations not supported in deterministic event loop"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def remove_reader(self, fd: int) -> bool: # type: ignore[override]
|
|
240
|
+
raise NotImplementedError(
|
|
241
|
+
"I/O operations not supported in deterministic event loop"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def add_writer(self, fd: int, callback: Callable[..., Any], *args: Any) -> None: # type: ignore[override]
|
|
245
|
+
raise NotImplementedError(
|
|
246
|
+
"I/O operations not supported in deterministic event loop"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def remove_writer(self, fd: int) -> bool: # type: ignore[override]
|
|
250
|
+
raise NotImplementedError(
|
|
251
|
+
"I/O operations not supported in deterministic event loop"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Socket operations - not supported
|
|
255
|
+
async def sock_recv(self, sock: Any, nbytes: int) -> bytes:
|
|
256
|
+
raise NotImplementedError(
|
|
257
|
+
"Socket operations not supported in deterministic event loop"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
async def sock_recv_into(self, sock: Any, buf: Any) -> int:
|
|
261
|
+
raise NotImplementedError(
|
|
262
|
+
"Socket operations not supported in deterministic event loop"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
async def sock_sendall(self, sock: Any, data: bytes) -> None: # type: ignore[override]
|
|
266
|
+
raise NotImplementedError(
|
|
267
|
+
"Socket operations not supported in deterministic event loop"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
async def sock_connect(self, sock: Any, address: Any) -> None:
|
|
271
|
+
raise NotImplementedError(
|
|
272
|
+
"Socket operations not supported in deterministic event loop"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
async def sock_accept(self, sock: Any) -> tuple[Any, Any]:
|
|
276
|
+
raise NotImplementedError(
|
|
277
|
+
"Socket operations not supported in deterministic event loop"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
async def sock_sendfile( # type: ignore[override]
|
|
281
|
+
self,
|
|
282
|
+
sock: Any,
|
|
283
|
+
file: Any,
|
|
284
|
+
offset: int = 0,
|
|
285
|
+
count: int | None = None,
|
|
286
|
+
*,
|
|
287
|
+
fallback: bool = True,
|
|
288
|
+
) -> int:
|
|
289
|
+
raise NotImplementedError(
|
|
290
|
+
"Socket operations not supported in deterministic event loop"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
async def sock_recvfrom(self, sock: Any, bufsize: int) -> tuple[bytes, Any]:
|
|
294
|
+
raise NotImplementedError(
|
|
295
|
+
"Socket operations not supported in deterministic event loop"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
async def sock_recvfrom_into(
|
|
299
|
+
self, sock: Any, buf: Any, nbytes: int = 0
|
|
300
|
+
) -> tuple[int, Any]:
|
|
301
|
+
raise NotImplementedError(
|
|
302
|
+
"Socket operations not supported in deterministic event loop"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
async def sock_sendto(self, sock: Any, data: bytes, address: Any) -> int: # type: ignore[override]
|
|
306
|
+
raise NotImplementedError(
|
|
307
|
+
"Socket operations not supported in deterministic event loop"
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# DNS operations - not supported
|
|
311
|
+
async def getaddrinfo( # type: ignore[override]
|
|
312
|
+
self,
|
|
313
|
+
host: str | None,
|
|
314
|
+
port: int | str | None,
|
|
315
|
+
*,
|
|
316
|
+
family: int = 0,
|
|
317
|
+
type: int = 0,
|
|
318
|
+
proto: int = 0,
|
|
319
|
+
flags: int = 0,
|
|
320
|
+
) -> list[tuple[Any, ...]]:
|
|
321
|
+
raise NotImplementedError(
|
|
322
|
+
"DNS operations not supported in deterministic event loop"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
async def getnameinfo( # type: ignore[override]
|
|
326
|
+
self, sockaddr: tuple[str, int], flags: int = 0
|
|
327
|
+
) -> tuple[str, str]:
|
|
328
|
+
raise NotImplementedError(
|
|
329
|
+
"DNS operations not supported in deterministic event loop"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Network connection operations - not supported
|
|
333
|
+
async def create_connection(
|
|
334
|
+
self,
|
|
335
|
+
protocol_factory: Any,
|
|
336
|
+
host: str | None = None,
|
|
337
|
+
port: int | None = None,
|
|
338
|
+
**kwargs: Any,
|
|
339
|
+
) -> tuple[Any, Any]:
|
|
340
|
+
raise NotImplementedError(
|
|
341
|
+
"Network operations not supported in deterministic event loop"
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
async def create_server( # type: ignore[override]
|
|
345
|
+
self,
|
|
346
|
+
protocol_factory: Any,
|
|
347
|
+
host: str | None = None,
|
|
348
|
+
port: int | None = None,
|
|
349
|
+
**kwargs: Any,
|
|
350
|
+
) -> Any:
|
|
351
|
+
raise NotImplementedError(
|
|
352
|
+
"Network operations not supported in deterministic event loop"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
async def create_unix_connection(
|
|
356
|
+
self, protocol_factory: Any, path: str | None = None, **kwargs: Any
|
|
357
|
+
) -> tuple[Any, Any]:
|
|
358
|
+
raise NotImplementedError(
|
|
359
|
+
"Unix socket operations not supported in deterministic event loop"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
async def create_unix_server( # type: ignore[override]
|
|
363
|
+
self, protocol_factory: Any, path: str | None = None, **kwargs: Any
|
|
364
|
+
) -> Any:
|
|
365
|
+
raise NotImplementedError(
|
|
366
|
+
"Unix socket operations not supported in deterministic event loop"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
async def create_datagram_endpoint( # type: ignore[override]
|
|
370
|
+
self,
|
|
371
|
+
protocol_factory: Any,
|
|
372
|
+
local_addr: tuple[str, int] | None = None,
|
|
373
|
+
remote_addr: tuple[str, int] | None = None,
|
|
374
|
+
**kwargs: Any,
|
|
375
|
+
) -> tuple[Any, Any]:
|
|
376
|
+
raise NotImplementedError(
|
|
377
|
+
"Datagram operations not supported in deterministic event loop"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
async def sendfile(
|
|
381
|
+
self,
|
|
382
|
+
transport: Any,
|
|
383
|
+
file: Any,
|
|
384
|
+
offset: int = 0,
|
|
385
|
+
count: int | None = None,
|
|
386
|
+
*,
|
|
387
|
+
fallback: bool = True,
|
|
388
|
+
) -> int:
|
|
389
|
+
raise NotImplementedError(
|
|
390
|
+
"Sendfile operations not supported in deterministic event loop"
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
async def start_tls(
|
|
394
|
+
self, transport: Any, protocol: Any, sslcontext: Any, **kwargs: Any
|
|
395
|
+
) -> Any:
|
|
396
|
+
raise NotImplementedError(
|
|
397
|
+
"TLS operations not supported in deterministic event loop"
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# Pipe operations - not supported
|
|
401
|
+
async def connect_read_pipe(
|
|
402
|
+
self, protocol_factory: Any, pipe: Any
|
|
403
|
+
) -> tuple[Any, Any]:
|
|
404
|
+
raise NotImplementedError(
|
|
405
|
+
"Pipe operations not supported in deterministic event loop"
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
async def connect_write_pipe(
|
|
409
|
+
self, protocol_factory: Any, pipe: Any
|
|
410
|
+
) -> tuple[Any, Any]:
|
|
411
|
+
raise NotImplementedError(
|
|
412
|
+
"Pipe operations not supported in deterministic event loop"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Subprocess operations - not supported
|
|
416
|
+
async def subprocess_shell(self, cmd: str, **kwargs: Any) -> Any: # type: ignore[override]
|
|
417
|
+
raise NotImplementedError(
|
|
418
|
+
"Subprocess operations not supported in deterministic event loop"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
async def subprocess_exec(self, program: str, *args: Any, **kwargs: Any) -> Any: # type: ignore[override]
|
|
422
|
+
raise NotImplementedError(
|
|
423
|
+
"Subprocess operations not supported in deterministic event loop"
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# Signal handlers - not supported
|
|
427
|
+
def add_signal_handler( # type: ignore[override]
|
|
428
|
+
self, sig: int, callback: Callable[..., Any], *args: Any
|
|
429
|
+
) -> None:
|
|
430
|
+
raise NotImplementedError(
|
|
431
|
+
"Signal handlers not supported in deterministic event loop"
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def remove_signal_handler(self, sig: int) -> bool:
|
|
435
|
+
raise NotImplementedError(
|
|
436
|
+
"Signal handlers not supported in deterministic event loop"
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
# Exception handling
|
|
440
|
+
def set_exception_handler(
|
|
441
|
+
self, handler: Callable[[Any, dict[str, Any]], Any] | None
|
|
442
|
+
) -> None:
|
|
443
|
+
raise NotImplementedError(
|
|
444
|
+
"Custom exception handlers not supported in deterministic event loop"
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
def get_exception_handler(self) -> Callable[[Any, dict[str, Any]], Any] | None:
|
|
448
|
+
raise NotImplementedError(
|
|
449
|
+
"Custom exception handlers not supported in deterministic event loop"
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
def default_exception_handler(self, context: dict[str, Any]) -> None:
|
|
453
|
+
raise NotImplementedError(
|
|
454
|
+
"Custom exception handlers not supported in deterministic event loop"
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
def call_exception_handler(self, context: dict[str, Any]) -> None:
|
|
458
|
+
raise NotImplementedError(
|
|
459
|
+
"Custom exception handlers not supported in deterministic event loop"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# Task factory
|
|
463
|
+
def set_task_factory( # type: ignore[override]
|
|
464
|
+
self, factory: Callable[[Any, Coroutine[Any, Any, Any]], Task[Any]] | None
|
|
465
|
+
) -> None:
|
|
466
|
+
raise NotImplementedError(
|
|
467
|
+
"Custom task factory not supported in deterministic event loop"
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
def get_task_factory( # type: ignore[override]
|
|
471
|
+
self,
|
|
472
|
+
) -> Callable[[Any, Coroutine[Any, Any, Any]], Task[Any]] | None:
|
|
473
|
+
return None
|
|
474
|
+
|
|
475
|
+
# Shutdown operations
|
|
476
|
+
async def shutdown_asyncgens(self) -> None:
|
|
477
|
+
# This is a no-op for deterministic event loop
|
|
478
|
+
pass
|
|
479
|
+
|
|
480
|
+
async def shutdown_default_executor(self, timeout: float | None = None) -> None:
|
|
481
|
+
# This is a no-op for deterministic event loop
|
|
482
|
+
pass
|
|
483
|
+
|
|
484
|
+
# Accepted socket connection
|
|
485
|
+
async def connect_accepted_socket(
|
|
486
|
+
self, protocol_factory: Any, sock: Any, **kwargs: Any
|
|
487
|
+
) -> tuple[Any, Any]:
|
|
488
|
+
raise NotImplementedError(
|
|
489
|
+
"Socket operations not supported in deterministic event loop"
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _run_until_complete_cb(fut: Future[Any]) -> None:
|
|
494
|
+
if not fut.cancelled():
|
|
495
|
+
exc = fut.exception()
|
|
496
|
+
if isinstance(exc, (SystemExit, KeyboardInterrupt)):
|
|
497
|
+
return
|
|
498
|
+
fut.get_loop().stop()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import Iterator, List, Optional
|
|
2
|
+
from cadence.api.v1.history_pb2 import HistoryEvent
|
|
3
|
+
from cadence.api.v1.service_worker_pb2 import PollForDecisionTaskResponse
|
|
4
|
+
from cadence.api.v1.service_workflow_pb2 import (
|
|
5
|
+
GetWorkflowExecutionHistoryRequest,
|
|
6
|
+
GetWorkflowExecutionHistoryResponse,
|
|
7
|
+
)
|
|
8
|
+
from cadence.client import Client
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def iterate_history_events(
|
|
12
|
+
decision_task: PollForDecisionTaskResponse, client: Client
|
|
13
|
+
):
|
|
14
|
+
PAGE_SIZE = 1000
|
|
15
|
+
|
|
16
|
+
current_page = decision_task.history.events
|
|
17
|
+
next_page_token = decision_task.next_page_token
|
|
18
|
+
workflow_execution = decision_task.workflow_execution
|
|
19
|
+
|
|
20
|
+
while True:
|
|
21
|
+
for event in current_page:
|
|
22
|
+
yield event
|
|
23
|
+
if not next_page_token:
|
|
24
|
+
break
|
|
25
|
+
response: GetWorkflowExecutionHistoryResponse = (
|
|
26
|
+
await client.workflow_stub.GetWorkflowExecutionHistory(
|
|
27
|
+
GetWorkflowExecutionHistoryRequest(
|
|
28
|
+
domain=client.domain,
|
|
29
|
+
workflow_execution=workflow_execution,
|
|
30
|
+
next_page_token=next_page_token,
|
|
31
|
+
page_size=PAGE_SIZE,
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
current_page = response.history.events
|
|
36
|
+
next_page_token = response.next_page_token
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class HistoryEventsIterator(Iterator[HistoryEvent]):
|
|
40
|
+
def __init__(self, events: List[HistoryEvent]):
|
|
41
|
+
self._iter = iter(events)
|
|
42
|
+
self._current = next(self._iter, None)
|
|
43
|
+
|
|
44
|
+
def __iter__(self):
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
def __next__(self) -> HistoryEvent:
|
|
48
|
+
if not self._current:
|
|
49
|
+
raise StopIteration("No more events")
|
|
50
|
+
event = self._current
|
|
51
|
+
self._current = next(self._iter, None)
|
|
52
|
+
return event
|
|
53
|
+
|
|
54
|
+
def has_next(self) -> bool:
|
|
55
|
+
return self._current is not None
|
|
56
|
+
|
|
57
|
+
def peek(self) -> Optional[HistoryEvent]:
|
|
58
|
+
return self._current
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from cadence._internal.workflow.statemachine.decision_state_machine import (
|
|
2
|
+
DecisionState,
|
|
3
|
+
DecisionType,
|
|
4
|
+
DecisionId,
|
|
5
|
+
DecisionFuture,
|
|
6
|
+
BaseDecisionStateMachine,
|
|
7
|
+
)
|
|
8
|
+
from cadence._internal.workflow.statemachine.event_dispatcher import EventDispatcher
|
|
9
|
+
from cadence.api.v1 import decision, history
|
|
10
|
+
from cadence.api.v1.common_pb2 import Payload
|
|
11
|
+
from cadence.error import ActivityFailure
|
|
12
|
+
|
|
13
|
+
activity_events = EventDispatcher("scheduled_event_id")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ActivityStateMachine(BaseDecisionStateMachine):
|
|
17
|
+
request: decision.ScheduleActivityTaskDecisionAttributes
|
|
18
|
+
completed: DecisionFuture[Payload]
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
request: decision.ScheduleActivityTaskDecisionAttributes,
|
|
23
|
+
completed: DecisionFuture[Payload],
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__()
|
|
26
|
+
self.request = request
|
|
27
|
+
self.completed = completed
|
|
28
|
+
|
|
29
|
+
def get_id(self) -> DecisionId:
|
|
30
|
+
return DecisionId(DecisionType.ACTIVITY, self.request.activity_id)
|
|
31
|
+
|
|
32
|
+
def get_decision(self) -> decision.Decision | None:
|
|
33
|
+
if self.state is DecisionState.CREATED:
|
|
34
|
+
return decision.Decision(
|
|
35
|
+
schedule_activity_task_decision_attributes=self.request
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if self.state is DecisionState.CANCELED_AFTER_INITIATED:
|
|
39
|
+
return decision.Decision(
|
|
40
|
+
request_cancel_activity_task_decision_attributes=decision.RequestCancelActivityTaskDecisionAttributes(
|
|
41
|
+
activity_id=self.request.activity_id,
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
def request_cancel(self) -> bool:
|
|
48
|
+
if self.state is DecisionState.CREATED:
|
|
49
|
+
self._transition(DecisionState.COMPLETED)
|
|
50
|
+
self.completed.force_cancel()
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
if self.state is DecisionState.INITIATED:
|
|
54
|
+
self._transition(DecisionState.CANCELED_AFTER_INITIATED)
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
@activity_events.event(id_attr="activity_id", event_id_is_alias=True)
|
|
60
|
+
def handle_scheduled(self, _: history.ActivityTaskScheduledEventAttributes) -> None:
|
|
61
|
+
self._transition(DecisionState.INITIATED)
|
|
62
|
+
|
|
63
|
+
@activity_events.event()
|
|
64
|
+
def handle_started(self, _: history.ActivityTaskStartedEventAttributes) -> None:
|
|
65
|
+
# Started doesn't actually do anything in the Go client.
|
|
66
|
+
# The workflow can't observe it, and it doesn't impact cancellation
|
|
67
|
+
# self._transition(DecisionState.STARTED)
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@activity_events.event()
|
|
71
|
+
def handle_completed(
|
|
72
|
+
self, event: history.ActivityTaskCompletedEventAttributes
|
|
73
|
+
) -> None:
|
|
74
|
+
self._transition(DecisionState.COMPLETED)
|
|
75
|
+
self.completed.set_result(event.result)
|
|
76
|
+
|
|
77
|
+
@activity_events.event()
|
|
78
|
+
def handle_failed(self, event: history.ActivityTaskFailedEventAttributes) -> None:
|
|
79
|
+
self._transition(DecisionState.COMPLETED)
|
|
80
|
+
self.completed.set_exception(ActivityFailure(event.failure.reason))
|
|
81
|
+
|
|
82
|
+
@activity_events.event()
|
|
83
|
+
def handle_timeout(
|
|
84
|
+
self, event: history.ActivityTaskTimedOutEventAttributes
|
|
85
|
+
) -> None:
|
|
86
|
+
self._transition(DecisionState.COMPLETED)
|
|
87
|
+
self.completed.set_exception(ActivityFailure(event.details.data.decode()))
|
|
88
|
+
|
|
89
|
+
@activity_events.event()
|
|
90
|
+
def handle_canceled(
|
|
91
|
+
self, event: history.ActivityTaskCanceledEventAttributes
|
|
92
|
+
) -> None:
|
|
93
|
+
self._transition(DecisionState.COMPLETED)
|
|
94
|
+
self.completed.force_cancel(event.details.data.decode())
|
|
95
|
+
|
|
96
|
+
@activity_events.event("activity_id")
|
|
97
|
+
def handle_cancel_requested(
|
|
98
|
+
self, _: history.ActivityTaskCancelRequestedEventAttributes
|
|
99
|
+
) -> None:
|
|
100
|
+
self._transition(DecisionState.CANCELLATION_DECISION_SENT)
|
|
101
|
+
|
|
102
|
+
@activity_events.event("activity_id")
|
|
103
|
+
def handle_cancel_failed(
|
|
104
|
+
self, _: history.RequestCancelActivityTaskFailedEventAttributes
|
|
105
|
+
) -> None:
|
|
106
|
+
self._transition(DecisionState.INITIATED)
|