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