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.
Files changed (95) hide show
  1. cadence/__init__.py +18 -0
  2. cadence/_internal/__init__.py +8 -0
  3. cadence/_internal/activity/__init__.py +5 -0
  4. cadence/_internal/activity/_activity_executor.py +113 -0
  5. cadence/_internal/activity/_context.py +58 -0
  6. cadence/_internal/rpc/__init__.py +0 -0
  7. cadence/_internal/rpc/error.py +148 -0
  8. cadence/_internal/rpc/retry.py +104 -0
  9. cadence/_internal/rpc/yarpc.py +42 -0
  10. cadence/_internal/workflow/__init__.py +0 -0
  11. cadence/_internal/workflow/context.py +121 -0
  12. cadence/_internal/workflow/decision_events_iterator.py +161 -0
  13. cadence/_internal/workflow/decisions_helper.py +312 -0
  14. cadence/_internal/workflow/deterministic_event_loop.py +498 -0
  15. cadence/_internal/workflow/history_event_iterator.py +58 -0
  16. cadence/_internal/workflow/statemachine/__init__.py +0 -0
  17. cadence/_internal/workflow/statemachine/activity_state_machine.py +106 -0
  18. cadence/_internal/workflow/statemachine/decision_manager.py +157 -0
  19. cadence/_internal/workflow/statemachine/decision_state_machine.py +87 -0
  20. cadence/_internal/workflow/statemachine/event_dispatcher.py +76 -0
  21. cadence/_internal/workflow/statemachine/timer_state_machine.py +73 -0
  22. cadence/_internal/workflow/workflow_engine.py +245 -0
  23. cadence/_internal/workflow/workflow_intance.py +44 -0
  24. cadence/activity.py +255 -0
  25. cadence/api/v1/__init__.py +92 -0
  26. cadence/api/v1/common_pb2.py +90 -0
  27. cadence/api/v1/common_pb2.pyi +200 -0
  28. cadence/api/v1/common_pb2_grpc.py +24 -0
  29. cadence/api/v1/decision_pb2.py +67 -0
  30. cadence/api/v1/decision_pb2.pyi +225 -0
  31. cadence/api/v1/decision_pb2_grpc.py +24 -0
  32. cadence/api/v1/domain_pb2.py +68 -0
  33. cadence/api/v1/domain_pb2.pyi +145 -0
  34. cadence/api/v1/domain_pb2_grpc.py +24 -0
  35. cadence/api/v1/error_pb2.py +59 -0
  36. cadence/api/v1/error_pb2.pyi +82 -0
  37. cadence/api/v1/error_pb2_grpc.py +24 -0
  38. cadence/api/v1/history_pb2.py +134 -0
  39. cadence/api/v1/history_pb2.pyi +780 -0
  40. cadence/api/v1/history_pb2_grpc.py +24 -0
  41. cadence/api/v1/query_pb2.py +49 -0
  42. cadence/api/v1/query_pb2.pyi +59 -0
  43. cadence/api/v1/query_pb2_grpc.py +24 -0
  44. cadence/api/v1/service_domain_pb2.py +76 -0
  45. cadence/api/v1/service_domain_pb2.pyi +164 -0
  46. cadence/api/v1/service_domain_pb2_grpc.py +327 -0
  47. cadence/api/v1/service_meta_pb2.py +41 -0
  48. cadence/api/v1/service_meta_pb2.pyi +17 -0
  49. cadence/api/v1/service_meta_pb2_grpc.py +97 -0
  50. cadence/api/v1/service_visibility_pb2.py +71 -0
  51. cadence/api/v1/service_visibility_pb2.pyi +149 -0
  52. cadence/api/v1/service_visibility_pb2_grpc.py +362 -0
  53. cadence/api/v1/service_worker_pb2.py +116 -0
  54. cadence/api/v1/service_worker_pb2.pyi +350 -0
  55. cadence/api/v1/service_worker_pb2_grpc.py +743 -0
  56. cadence/api/v1/service_workflow_pb2.py +126 -0
  57. cadence/api/v1/service_workflow_pb2.pyi +395 -0
  58. cadence/api/v1/service_workflow_pb2_grpc.py +861 -0
  59. cadence/api/v1/tasklist_pb2.py +78 -0
  60. cadence/api/v1/tasklist_pb2.pyi +147 -0
  61. cadence/api/v1/tasklist_pb2_grpc.py +24 -0
  62. cadence/api/v1/visibility_pb2.py +47 -0
  63. cadence/api/v1/visibility_pb2.pyi +53 -0
  64. cadence/api/v1/visibility_pb2_grpc.py +24 -0
  65. cadence/api/v1/workflow_pb2.py +89 -0
  66. cadence/api/v1/workflow_pb2.pyi +365 -0
  67. cadence/api/v1/workflow_pb2_grpc.py +24 -0
  68. cadence/client.py +382 -0
  69. cadence/data_converter.py +78 -0
  70. cadence/error.py +111 -0
  71. cadence/metrics/__init__.py +12 -0
  72. cadence/metrics/constants.py +136 -0
  73. cadence/metrics/metrics.py +56 -0
  74. cadence/metrics/prometheus.py +165 -0
  75. cadence/sample/__init__.py +1 -0
  76. cadence/sample/client_example.py +15 -0
  77. cadence/sample/grpc_usage_example.py +230 -0
  78. cadence/sample/simple_usage_example.py +155 -0
  79. cadence/signal.py +174 -0
  80. cadence/worker/__init__.py +13 -0
  81. cadence/worker/_activity.py +60 -0
  82. cadence/worker/_base_task_handler.py +71 -0
  83. cadence/worker/_decision.py +62 -0
  84. cadence/worker/_decision_task_handler.py +285 -0
  85. cadence/worker/_poller.py +64 -0
  86. cadence/worker/_registry.py +245 -0
  87. cadence/worker/_types.py +26 -0
  88. cadence/worker/_worker.py +56 -0
  89. cadence/workflow.py +271 -0
  90. cadence_python_client-0.1.0.dist-info/METADATA +180 -0
  91. cadence_python_client-0.1.0.dist-info/RECORD +95 -0
  92. cadence_python_client-0.1.0.dist-info/WHEEL +5 -0
  93. cadence_python_client-0.1.0.dist-info/licenses/LICENSE +201 -0
  94. cadence_python_client-0.1.0.dist-info/licenses/NOTICE +19 -0
  95. 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)