async-durable-execution-runner 2.0.0a1__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 (55) hide show
  1. LICENSE +175 -0
  2. NOTICE +8 -0
  3. VERSION.py +5 -0
  4. async_durable_execution_runner/__about__.py +33 -0
  5. async_durable_execution_runner/__init__.py +23 -0
  6. async_durable_execution_runner/checkpoint/__init__.py +1 -0
  7. async_durable_execution_runner/checkpoint/processor.py +101 -0
  8. async_durable_execution_runner/checkpoint/processors/__init__.py +1 -0
  9. async_durable_execution_runner/checkpoint/processors/base.py +199 -0
  10. async_durable_execution_runner/checkpoint/processors/callback.py +89 -0
  11. async_durable_execution_runner/checkpoint/processors/context.py +59 -0
  12. async_durable_execution_runner/checkpoint/processors/execution.py +52 -0
  13. async_durable_execution_runner/checkpoint/processors/step.py +124 -0
  14. async_durable_execution_runner/checkpoint/processors/wait.py +95 -0
  15. async_durable_execution_runner/checkpoint/transformer.py +104 -0
  16. async_durable_execution_runner/checkpoint/validators/__init__.py +1 -0
  17. async_durable_execution_runner/checkpoint/validators/checkpoint.py +242 -0
  18. async_durable_execution_runner/checkpoint/validators/operations/__init__.py +1 -0
  19. async_durable_execution_runner/checkpoint/validators/operations/callback.py +45 -0
  20. async_durable_execution_runner/checkpoint/validators/operations/context.py +73 -0
  21. async_durable_execution_runner/checkpoint/validators/operations/execution.py +47 -0
  22. async_durable_execution_runner/checkpoint/validators/operations/invoke.py +56 -0
  23. async_durable_execution_runner/checkpoint/validators/operations/step.py +106 -0
  24. async_durable_execution_runner/checkpoint/validators/operations/wait.py +54 -0
  25. async_durable_execution_runner/checkpoint/validators/transitions.py +66 -0
  26. async_durable_execution_runner/cli.py +498 -0
  27. async_durable_execution_runner/client.py +50 -0
  28. async_durable_execution_runner/exceptions.py +288 -0
  29. async_durable_execution_runner/execution.py +444 -0
  30. async_durable_execution_runner/executor.py +1234 -0
  31. async_durable_execution_runner/invoker.py +340 -0
  32. async_durable_execution_runner/model.py +3296 -0
  33. async_durable_execution_runner/observer.py +144 -0
  34. async_durable_execution_runner/py.typed +1 -0
  35. async_durable_execution_runner/runner.py +1167 -0
  36. async_durable_execution_runner/scheduler.py +246 -0
  37. async_durable_execution_runner/stores/__init__.py +1 -0
  38. async_durable_execution_runner/stores/base.py +147 -0
  39. async_durable_execution_runner/stores/filesystem.py +79 -0
  40. async_durable_execution_runner/stores/memory.py +38 -0
  41. async_durable_execution_runner/stores/sqlite.py +273 -0
  42. async_durable_execution_runner/token.py +49 -0
  43. async_durable_execution_runner/web/__init__.py +1 -0
  44. async_durable_execution_runner/web/errors.py +8 -0
  45. async_durable_execution_runner/web/handlers.py +813 -0
  46. async_durable_execution_runner/web/models.py +266 -0
  47. async_durable_execution_runner/web/routes.py +692 -0
  48. async_durable_execution_runner/web/serialization.py +235 -0
  49. async_durable_execution_runner/web/server.py +243 -0
  50. async_durable_execution_runner-2.0.0a1.dist-info/METADATA +238 -0
  51. async_durable_execution_runner-2.0.0a1.dist-info/RECORD +55 -0
  52. async_durable_execution_runner-2.0.0a1.dist-info/WHEEL +4 -0
  53. async_durable_execution_runner-2.0.0a1.dist-info/entry_points.txt +2 -0
  54. async_durable_execution_runner-2.0.0a1.dist-info/licenses/LICENSE +175 -0
  55. async_durable_execution_runner-2.0.0a1.dist-info/licenses/NOTICE +1 -0
@@ -0,0 +1,498 @@
1
+ """Command-line interface for the AWS Durable Functions Local Runner.
2
+
3
+ This module provides the dex-local-runner CLI with commands for:
4
+ - start-server: Start the local web server
5
+ - invoke: Invoke a durable execution
6
+ - get-durable-execution: Get execution details
7
+ - get-durable-execution-history: Get execution history
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import logging
15
+ import os
16
+ import sys
17
+ import uuid
18
+ from dataclasses import dataclass
19
+ from typing import Any
20
+ from urllib.parse import urljoin
21
+
22
+ import async_durable_execution
23
+ import boto3 # type: ignore
24
+ from urllib.error import HTTPError, URLError
25
+ from urllib.request import Request, urlopen
26
+
27
+ from botocore.exceptions import ConnectionError # type: ignore
28
+
29
+ from async_durable_execution_runner.exceptions import (
30
+ DurableFunctionsLocalRunnerError,
31
+ DurableFunctionsTestError,
32
+ )
33
+ from async_durable_execution_runner.model import (
34
+ StartDurableExecutionInput,
35
+ )
36
+ from async_durable_execution_runner.runner import WebRunner, WebRunnerConfig
37
+ from async_durable_execution_runner.stores.base import StoreType
38
+ from async_durable_execution_runner.web.server import WebServiceConfig
39
+
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class CliConfig:
46
+ """Configuration for the CLI application with environment variable support."""
47
+
48
+ # Server configuration
49
+ host: str = "0.0.0.0" # noqa:S104
50
+ port: int = 5000
51
+ log_level: int = logging.INFO
52
+ lambda_endpoint: str = "http://127.0.0.1:3001"
53
+ local_runner_endpoint: str = "http://0.0.0.0:5000"
54
+ local_runner_region: str = "us-west-2"
55
+ local_runner_mode: str = "local"
56
+
57
+ # Store configuration
58
+ store_type: StoreType = StoreType.MEMORY
59
+ store_path: str | None = None
60
+
61
+ @classmethod
62
+ def from_environment(cls) -> CliConfig:
63
+ """Create configuration from environment variables with defaults."""
64
+ # Convert log level string to integer if provided
65
+ log_level_str = os.getenv("AWS_DEX_LOG_LEVEL", "INFO")
66
+ log_level = logging.getLevelNamesMapping().get(log_level_str, logging.INFO)
67
+
68
+ return cls(
69
+ host=os.getenv("AWS_DEX_HOST", "0.0.0.0"), # noqa:S104
70
+ port=int(os.getenv("AWS_DEX_PORT", "5000")),
71
+ log_level=log_level,
72
+ lambda_endpoint=os.getenv(
73
+ "AWS_DEX_LAMBDA_ENDPOINT", "http://127.0.0.1:3001"
74
+ ),
75
+ local_runner_endpoint=os.getenv(
76
+ "AWS_DEX_LOCAL_RUNNER_ENDPOINT", "http://0.0.0.0:5000"
77
+ ),
78
+ local_runner_region=os.getenv("AWS_DEX_LOCAL_RUNNER_REGION", "us-west-2"),
79
+ local_runner_mode=os.getenv("AWS_DEX_LOCAL_RUNNER_MODE", "local"),
80
+ store_type=StoreType(os.getenv("AWS_DEX_STORE_TYPE", "memory")),
81
+ store_path=os.getenv("AWS_DEX_STORE_PATH"),
82
+ )
83
+
84
+
85
+ class CliApp:
86
+ """Main CLI application for dex-local-runner."""
87
+
88
+ def __init__(self) -> None:
89
+ """Initialize the CLI application."""
90
+ self.config = CliConfig.from_environment()
91
+
92
+ def run(self, args: list[str] | None = None) -> int:
93
+ """Run the CLI application with the given arguments.
94
+
95
+ Args:
96
+ args: Command line arguments. If None, uses sys.argv[1:]
97
+
98
+ Returns:
99
+ Exit code (0 for success, non-zero for error)
100
+ """
101
+ try:
102
+ parser = self._create_parsers()
103
+ parsed_args = parser.parse_args(args)
104
+
105
+ # Configure logging based on log level
106
+ if hasattr(parsed_args, "log_level") and isinstance(
107
+ parsed_args.log_level, str
108
+ ):
109
+ level = logging.getLevelNamesMapping().get(
110
+ parsed_args.log_level, logging.INFO
111
+ )
112
+ else:
113
+ # config.log_level is always an integer
114
+ level = self.config.log_level
115
+
116
+ logging.basicConfig(
117
+ level=level,
118
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
119
+ )
120
+ logging.getLogger("botocore").setLevel(logging.WARNING)
121
+
122
+ # Execute the appropriate command
123
+ return parsed_args.func(parsed_args)
124
+
125
+ except SystemExit as e:
126
+ # argparse calls sys.exit() for help, errors, etc.
127
+ return int(e.code) if e.code is not None else 1
128
+ except KeyboardInterrupt:
129
+ print("\nOperation cancelled by user", file=sys.stderr) # noqa: T201
130
+ return 130 # Standard exit code for SIGINT
131
+ except DurableFunctionsTestError:
132
+ logger.exception("Error")
133
+ return 1
134
+ except Exception:
135
+ logger.exception("Unexpected error.")
136
+ return 1
137
+
138
+ def _create_parsers(self) -> argparse.ArgumentParser:
139
+ """Create the argument parsers for all commands."""
140
+ parser = argparse.ArgumentParser(
141
+ prog="dex-local-runner",
142
+ description="AWS Durable Functions Local Runner CLI",
143
+ )
144
+
145
+ subparsers = parser.add_subparsers(
146
+ dest="command", help="Available commands", required=True
147
+ )
148
+
149
+ # Create individual parsers
150
+ self._create_start_server_parser(subparsers)
151
+ self._create_invoke_parser(subparsers)
152
+ self._create_get_durable_execution_parser(subparsers)
153
+ self._create_get_durable_execution_history_parser(subparsers)
154
+
155
+ return parser
156
+
157
+ # region parsers
158
+
159
+ def _create_start_server_parser(self, subparsers) -> None:
160
+ """Create the start-server command parser."""
161
+ start_server_parser = subparsers.add_parser(
162
+ "start-server", help="Start the local Durable Functions Server"
163
+ )
164
+ start_server_parser.add_argument(
165
+ "--host",
166
+ default=self.config.host,
167
+ help=f"Server bind address (default: {self.config.host}, env: AWS_DEX_HOST)",
168
+ )
169
+ start_server_parser.add_argument(
170
+ "--port",
171
+ type=int,
172
+ default=self.config.port,
173
+ help=f"Server port (default: {self.config.port}, env: AWS_DEX_PORT)",
174
+ )
175
+ start_server_parser.add_argument(
176
+ "--log-level",
177
+ type=str,
178
+ choices=list(logging.getLevelNamesMapping().keys()),
179
+ default=logging.getLevelName(self.config.log_level),
180
+ help=f"Logging level (default: {logging.getLevelName(self.config.log_level)}, env: AWS_DEX_LOG_LEVEL)",
181
+ )
182
+ start_server_parser.add_argument(
183
+ "--lambda-endpoint",
184
+ default=self.config.lambda_endpoint,
185
+ help=f"Lambda Service endpoint (default: {self.config.lambda_endpoint}, env: AWS_DEX_LAMBDA_ENDPOINT)",
186
+ )
187
+ start_server_parser.add_argument(
188
+ "--local-runner-endpoint",
189
+ default=self.config.local_runner_endpoint,
190
+ help=f"Local Runner endpoint (default: {self.config.local_runner_endpoint}, env: AWS_DEX_LOCAL_RUNNER_ENDPOINT)",
191
+ )
192
+ start_server_parser.add_argument(
193
+ "--local-runner-region",
194
+ default=self.config.local_runner_region,
195
+ help=f"Local Runner region (default: {self.config.local_runner_region}, env: AWS_DEX_LOCAL_RUNNER_REGION)",
196
+ )
197
+ start_server_parser.add_argument(
198
+ "--local-runner-mode",
199
+ default=self.config.local_runner_mode,
200
+ help=f"Local Runner mode (default: {self.config.local_runner_mode}, env: AWS_DEX_LOCAL_RUNNER_MODE)",
201
+ )
202
+ start_server_parser.add_argument(
203
+ "--store-type",
204
+ choices=[store_type.value for store_type in StoreType],
205
+ default=self.config.store_type.value,
206
+ help=f"Store type for execution persistence (default: {self.config.store_type.value}, env: AWS_DEX_STORE_TYPE)",
207
+ )
208
+ start_server_parser.add_argument(
209
+ "--store-path",
210
+ default=self.config.store_path,
211
+ help=f"Path for filesystem store (default: {self.config.store_path or '.durable_executions'}, env: AWS_DEX_STORE_PATH)",
212
+ )
213
+ start_server_parser.set_defaults(func=self.start_server_command)
214
+
215
+ def _create_invoke_parser(self, subparsers) -> None:
216
+ """Create the invoke command parser."""
217
+ invoke_parser = subparsers.add_parser(
218
+ "invoke", help="Invoke a Durable Execution"
219
+ )
220
+ invoke_parser.add_argument(
221
+ "--function-name", required=True, help="Function name (required)"
222
+ )
223
+ invoke_parser.add_argument(
224
+ "--input", default="{}", help="Input data (default: {})"
225
+ )
226
+ invoke_parser.add_argument(
227
+ "--durable-execution-name", help="Durable execution name (optional)"
228
+ )
229
+ invoke_parser.set_defaults(func=self.invoke_command)
230
+
231
+ def _create_get_durable_execution_parser(self, subparsers) -> None:
232
+ """Create the get-durable-execution command parser."""
233
+ get_execution_parser = subparsers.add_parser(
234
+ "get-durable-execution", help="Get execution details"
235
+ )
236
+ get_execution_parser.add_argument(
237
+ "--durable-execution-arn",
238
+ required=True,
239
+ help="Durable execution ARN (required)",
240
+ )
241
+ get_execution_parser.set_defaults(func=self.get_durable_execution_command)
242
+
243
+ def _create_get_durable_execution_history_parser(self, subparsers) -> None:
244
+ """Create the get-durable-execution-history command parser."""
245
+ get_history_parser = subparsers.add_parser(
246
+ "get-durable-execution-history", help="Get execution history"
247
+ )
248
+ get_history_parser.add_argument(
249
+ "--durable-execution-arn",
250
+ required=True,
251
+ help="Durable execution ARN (required)",
252
+ )
253
+ get_history_parser.set_defaults(func=self.get_durable_execution_history_command)
254
+
255
+ # endregion parsers
256
+
257
+ # region commands
258
+
259
+ def start_server_command(self, args: argparse.Namespace) -> int:
260
+ """Execute the start-server command.
261
+
262
+ Args:
263
+ args: Parsed command line arguments
264
+
265
+ Returns:
266
+ Exit code (0 for success, non-zero for error)
267
+ """
268
+ try:
269
+ # Create web service configuration from CLI arguments
270
+ web_config = WebServiceConfig(
271
+ host=args.host,
272
+ port=args.port,
273
+ log_level=args.log_level,
274
+ )
275
+
276
+ # Create web runner configuration with composition
277
+ runner_config = WebRunnerConfig(
278
+ web_service=web_config,
279
+ lambda_endpoint=args.lambda_endpoint,
280
+ local_runner_endpoint=args.local_runner_endpoint,
281
+ local_runner_region=args.local_runner_region,
282
+ local_runner_mode=args.local_runner_mode,
283
+ store_type=StoreType(args.store_type),
284
+ store_path=args.store_path,
285
+ )
286
+
287
+ logger.info(
288
+ "Starting Durable Functions Local Runner on %s:%s",
289
+ args.host,
290
+ args.port,
291
+ )
292
+ logger.info("Configuration:")
293
+ logger.info(" Host: %s", args.host)
294
+ logger.info(" Port: %s", args.port)
295
+ logger.info(" Log Level: %s", args.log_level)
296
+ logger.info(" Lambda Endpoint: %s", args.lambda_endpoint)
297
+ logger.info(" Local Runner Endpoint: %s", args.local_runner_endpoint)
298
+ logger.info(" Local Runner Region: %s", args.local_runner_region)
299
+ logger.info(" Local Runner Mode: %s", args.local_runner_mode)
300
+ logger.info(" Store Type: %s", args.store_type)
301
+ if StoreType(args.store_type) == StoreType.FILESYSTEM:
302
+ store_path = args.store_path or ".durable_executions"
303
+ logger.info(" Store Path: %s", store_path)
304
+
305
+ # Use runner as context manager for proper lifecycle
306
+ with WebRunner(runner_config) as runner:
307
+ logger.info("Server started successfully. Press Ctrl+C to stop.")
308
+ runner.serve_forever()
309
+
310
+ return 0 # noqa: TRY300
311
+
312
+ except KeyboardInterrupt:
313
+ logger.info("Received shutdown signal, stopping server...")
314
+ return 130 # Standard exit code for SIGINT
315
+ except Exception:
316
+ logger.exception("Failed to start server")
317
+ return 1
318
+
319
+ def invoke_command(self, args: argparse.Namespace) -> int:
320
+ """Execute the invoke command.
321
+
322
+ Args:
323
+ args: Parsed command line arguments
324
+
325
+ Returns:
326
+ Exit code (0 for success, non-zero for error)
327
+ """
328
+ # Validate input JSON
329
+ try:
330
+ json.loads(args.input) # Just validate, don't store
331
+ except json.JSONDecodeError:
332
+ logger.exception("JSON decode error")
333
+ return 1
334
+
335
+ try:
336
+ # Create StartDurableExecutionInput
337
+ start_input = StartDurableExecutionInput(
338
+ account_id="123456789012", # Default account ID for local testing
339
+ function_name=args.function_name,
340
+ function_qualifier="$LATEST", # Default qualifier
341
+ execution_name=args.durable_execution_name
342
+ or f"{args.function_name}-execution",
343
+ execution_timeout_seconds=300, # 5 minutes default
344
+ execution_retention_period_days=7, # 1 week default
345
+ invocation_id=str(uuid.uuid4()), # Generate unique invocation ID
346
+ input=args.input,
347
+ )
348
+
349
+ # Make HTTP request to start-durable-execution endpoint
350
+ endpoint_url = self.config.local_runner_endpoint
351
+ url = urljoin(endpoint_url, "/start-durable-execution")
352
+
353
+ payload = start_input.to_dict()
354
+ data = json.dumps(payload).encode("utf-8")
355
+ req = Request(
356
+ url,
357
+ data=data,
358
+ headers={"Content-Type": "application/json"},
359
+ method="POST",
360
+ )
361
+
362
+ try:
363
+ with urlopen(req, timeout=10) as response: # noqa: S310
364
+ result = json.loads(response.read().decode("utf-8"))
365
+ print(json.dumps(result, indent=2)) # noqa: T201
366
+ return 0
367
+ except HTTPError as e:
368
+ try:
369
+ error_data = json.loads(e.read().decode("utf-8"))
370
+ logger.exception("HTTP error response")
371
+ print( # noqa: T201
372
+ f"Error: {error_data.get('ErrorMessage', 'Unknown error')}",
373
+ file=sys.stderr,
374
+ )
375
+ except json.JSONDecodeError:
376
+ logger.exception("Non-JSON error response")
377
+ return 1
378
+
379
+ except URLError:
380
+ logger.exception(
381
+ "Error: Could not connect to the local runner server. Is it running?"
382
+ )
383
+ return 1
384
+ except Exception:
385
+ logger.exception("Unexpected error in invoke command")
386
+ return 1
387
+
388
+ def get_durable_execution_command(self, args: argparse.Namespace) -> int:
389
+ """Execute the get-durable-execution command.
390
+
391
+ Args:
392
+ args: Parsed command line arguments
393
+
394
+ Returns:
395
+ Exit code (0 for success, non-zero for error)
396
+ """
397
+ try:
398
+ # Set up boto3 client with local endpoint
399
+ client = self._create_boto3_client()
400
+
401
+ # Call get_durable_execution
402
+ response = client.get_durable_execution(
403
+ DurableExecutionArn=args.durable_execution_arn
404
+ )
405
+
406
+ # Print formatted response
407
+ print(json.dumps(response, indent=2, default=str)) # noqa: T201
408
+ return 0 # noqa: TRY300
409
+
410
+ except client.exceptions.InvalidParameterValueException as e:
411
+ print(f"Error: Invalid parameter - {e}", file=sys.stderr) # noqa: T201
412
+ return 1
413
+ except client.exceptions.ResourceNotFoundException as e:
414
+ print(f"Error: Execution not found - {e}", file=sys.stderr) # noqa: T201
415
+ return 1
416
+ except client.exceptions.TooManyRequestsException as e:
417
+ print(f"Error: Too many requests - {e}", file=sys.stderr) # noqa: T201
418
+ return 1
419
+ except client.exceptions.ServiceException as e:
420
+ print(f"Error: Service error - {e}", file=sys.stderr) # noqa: T201
421
+ return 1
422
+ except ConnectionError:
423
+ logger.exception(
424
+ "Error: Could not connect to the local runner server. Is it running?"
425
+ )
426
+ return 1
427
+ except Exception:
428
+ logger.exception("Unexpected error in get-durable-execution command")
429
+ return 1
430
+
431
+ def get_durable_execution_history_command(self, args: argparse.Namespace) -> int:
432
+ """Execute the get-durable-execution-history command.
433
+
434
+ TODO: implement - this is incomplete
435
+
436
+ Args:
437
+ args: Parsed command line arguments
438
+
439
+ Returns:
440
+ Exit code (0 for success, non-zero for error)
441
+ """
442
+ try:
443
+ # Set up boto3 client with local endpoint
444
+ client = self._create_boto3_client()
445
+
446
+ # Call get_durable_execution_history
447
+ response = client.get_durable_execution_history(
448
+ DurableExecutionArn=args.durable_execution_arn
449
+ )
450
+
451
+ print(json.dumps(response, indent=2, default=str)) # noqa: T201
452
+ return 0 # noqa: TRY300
453
+
454
+ except Exception:
455
+ logger.exception("General error")
456
+ return 1
457
+
458
+ # endregion commands
459
+
460
+ def _create_boto3_client(
461
+ self, endpoint_url: str | None = None, region_name: str | None = None
462
+ ) -> Any:
463
+ """Create boto3 client for Lambda service.
464
+
465
+ Args:
466
+ endpoint_url: Optional endpoint URL override
467
+ region_name: Optional region name override
468
+
469
+ Returns:
470
+ Configured boto3 client for local runner
471
+
472
+ Raises:
473
+ Exception: If client creation fails
474
+ """
475
+ try:
476
+ # Use provided values or fall back to config
477
+ final_endpoint = endpoint_url or self.config.local_runner_endpoint
478
+ final_region = region_name or self.config.local_runner_region
479
+
480
+ # Create client with local endpoint - no AWS access keys required
481
+ return boto3.client(
482
+ "lambda",
483
+ endpoint_url=final_endpoint,
484
+ region_name=final_region,
485
+ )
486
+ except Exception as e:
487
+ msg = f"Failed to create boto3 client: {e}"
488
+ raise DurableFunctionsLocalRunnerError(msg) from e
489
+
490
+
491
+ def main() -> int:
492
+ """Main entry point for the dex-local-runner CLI."""
493
+ app = CliApp()
494
+ return app.run()
495
+
496
+
497
+ if __name__ == "__main__":
498
+ sys.exit(main())
@@ -0,0 +1,50 @@
1
+ """An in-memory service client, that can replace the boto lambda service client."""
2
+
3
+ import datetime
4
+
5
+ from async_durable_execution.lambda_service import (
6
+ CheckpointOutput,
7
+ DurableServiceClient,
8
+ OperationUpdate,
9
+ StateOutput,
10
+ )
11
+
12
+ from async_durable_execution_runner.checkpoint.processor import (
13
+ CheckpointProcessor,
14
+ )
15
+
16
+
17
+ class InMemoryServiceClient(DurableServiceClient):
18
+ """An in-memory service client, that can replace the boto lambda service client."""
19
+
20
+ def __init__(self, checkpoint_processor: CheckpointProcessor):
21
+ self._checkpoint_processor: CheckpointProcessor = checkpoint_processor
22
+
23
+ def checkpoint(
24
+ self,
25
+ durable_execution_arn: str, # noqa: ARG002
26
+ checkpoint_token: str,
27
+ updates: list[OperationUpdate],
28
+ client_token: str | None,
29
+ ) -> CheckpointOutput:
30
+ # durable_execution_arn is not used in in-memory testing
31
+ return self._checkpoint_processor.process_checkpoint(
32
+ checkpoint_token, updates, client_token
33
+ )
34
+
35
+ def get_execution_state(
36
+ self,
37
+ durable_execution_arn: str, # noqa: ARG002
38
+ checkpoint_token: str,
39
+ next_marker: str,
40
+ max_items: int = 1000,
41
+ ) -> StateOutput:
42
+ # durable_execution_arn is not used in in-memory testing
43
+ return self._checkpoint_processor.get_execution_state(
44
+ checkpoint_token, next_marker, max_items
45
+ )
46
+
47
+ def stop(self, execution_arn: str, payload: bytes | None) -> datetime.datetime: # noqa: ARG002
48
+ # TODO: implement
49
+ # Return current time for in-memory testing
50
+ return datetime.datetime.now(tz=datetime.UTC)