aws-durable-execution-sdk-python 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 (36) hide show
  1. aws_durable_execution_sdk_python/.gitignore +0 -0
  2. aws_durable_execution_sdk_python/__about__.py +4 -0
  3. aws_durable_execution_sdk_python/__init__.py +34 -0
  4. aws_durable_execution_sdk_python/botocore/data/lambdainternal/2015-03-31/service-2.json +7864 -0
  5. aws_durable_execution_sdk_python/concurrency/__init__.py +0 -0
  6. aws_durable_execution_sdk_python/concurrency/executor.py +436 -0
  7. aws_durable_execution_sdk_python/concurrency/models.py +469 -0
  8. aws_durable_execution_sdk_python/config.py +499 -0
  9. aws_durable_execution_sdk_python/context.py +551 -0
  10. aws_durable_execution_sdk_python/exceptions.py +374 -0
  11. aws_durable_execution_sdk_python/execution.py +428 -0
  12. aws_durable_execution_sdk_python/identifier.py +14 -0
  13. aws_durable_execution_sdk_python/lambda_service.py +1034 -0
  14. aws_durable_execution_sdk_python/logger.py +131 -0
  15. aws_durable_execution_sdk_python/operation/__init__.py +1 -0
  16. aws_durable_execution_sdk_python/operation/callback.py +123 -0
  17. aws_durable_execution_sdk_python/operation/child.py +162 -0
  18. aws_durable_execution_sdk_python/operation/invoke.py +119 -0
  19. aws_durable_execution_sdk_python/operation/map.py +137 -0
  20. aws_durable_execution_sdk_python/operation/parallel.py +122 -0
  21. aws_durable_execution_sdk_python/operation/step.py +269 -0
  22. aws_durable_execution_sdk_python/operation/wait.py +53 -0
  23. aws_durable_execution_sdk_python/operation/wait_for_condition.py +235 -0
  24. aws_durable_execution_sdk_python/py.typed +1 -0
  25. aws_durable_execution_sdk_python/retries.py +174 -0
  26. aws_durable_execution_sdk_python/serdes.py +502 -0
  27. aws_durable_execution_sdk_python/state.py +790 -0
  28. aws_durable_execution_sdk_python/suspend.py +84 -0
  29. aws_durable_execution_sdk_python/threading.py +222 -0
  30. aws_durable_execution_sdk_python/types.py +180 -0
  31. aws_durable_execution_sdk_python/waits.py +130 -0
  32. aws_durable_execution_sdk_python-1.0.0.dist-info/METADATA +679 -0
  33. aws_durable_execution_sdk_python-1.0.0.dist-info/RECORD +36 -0
  34. aws_durable_execution_sdk_python-1.0.0.dist-info/WHEEL +4 -0
  35. aws_durable_execution_sdk_python-1.0.0.dist-info/licenses/LICENSE +175 -0
  36. aws_durable_execution_sdk_python-1.0.0.dist-info/licenses/NOTICE +1 -0
@@ -0,0 +1,499 @@
1
+ """Configuration types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import random
6
+ from dataclasses import dataclass, field
7
+ from enum import Enum, StrEnum
8
+ from typing import TYPE_CHECKING, Generic, TypeVar
9
+
10
+ from aws_durable_execution_sdk_python.exceptions import ValidationError
11
+
12
+ P = TypeVar("P") # Payload type
13
+ R = TypeVar("R") # Result type
14
+ T = TypeVar("T")
15
+ U = TypeVar("U")
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Callable
19
+ from concurrent.futures import Future
20
+
21
+ from aws_durable_execution_sdk_python.lambda_service import OperationSubType
22
+ from aws_durable_execution_sdk_python.retries import RetryDecision
23
+ from aws_durable_execution_sdk_python.serdes import SerDes
24
+ from aws_durable_execution_sdk_python.types import SummaryGenerator
25
+
26
+
27
+ Numeric = int | float # deliberately leaving off complex
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class Duration:
32
+ """Represents a duration stored as total seconds."""
33
+
34
+ seconds: int = 0
35
+
36
+ def __post_init__(self):
37
+ if self.seconds < 0:
38
+ msg = "Duration seconds must be positive"
39
+ raise ValidationError(msg)
40
+
41
+ def to_seconds(self) -> int:
42
+ """Convert the duration to total seconds."""
43
+ return self.seconds
44
+
45
+ @classmethod
46
+ def from_seconds(cls, value: float) -> Duration:
47
+ """Create a Duration from total seconds."""
48
+ return cls(seconds=int(value))
49
+
50
+ @classmethod
51
+ def from_minutes(cls, value: float) -> Duration:
52
+ """Create a Duration from minutes."""
53
+ return cls(seconds=int(value * 60))
54
+
55
+ @classmethod
56
+ def from_hours(cls, value: float) -> Duration:
57
+ """Create a Duration from hours."""
58
+ return cls(seconds=int(value * 3600))
59
+
60
+ @classmethod
61
+ def from_days(cls, value: float) -> Duration:
62
+ """Create a Duration from days."""
63
+ return cls(seconds=int(value * 86400))
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class BatchedInput(Generic[T, U]):
68
+ batch_input: T
69
+ items: list[U]
70
+
71
+
72
+ class TerminationMode(Enum):
73
+ TERMINATE = "TERMINATE"
74
+ CANCEL = "CANCEL"
75
+ WAIT = "WAIT"
76
+ ABANDON = "ABANDON"
77
+
78
+
79
+ @dataclass(frozen=True)
80
+ class CompletionConfig:
81
+ """Configuration for determining when parallel/map operations complete.
82
+
83
+ This class defines the success/failure criteria for operations that process
84
+ multiple items or branches concurrently.
85
+
86
+ Args:
87
+ min_successful: Minimum number of successful completions required.
88
+ If None, no minimum is enforced. Use this to implement "at least N
89
+ must succeed" semantics.
90
+
91
+ tolerated_failure_count: Maximum number of failures allowed before
92
+ the operation is considered failed. If None, no limit on failure count.
93
+ Use this to implement "fail fast after N failures" semantics.
94
+
95
+ tolerated_failure_percentage: Maximum percentage of failures allowed
96
+ (0.0 to 100.0). If None, no percentage limit is enforced.
97
+ Use this to implement "fail if more than X% fail" semantics.
98
+
99
+ Note:
100
+ The operation completes when any of the completion criteria are met:
101
+ - Enough successes (min_successful reached)
102
+ - Too many failures (tolerated limits exceeded)
103
+ - All items/branches completed
104
+
105
+ Example:
106
+ # Succeed if at least 3 succeed, fail if more than 2 fail
107
+ config = CompletionConfig(
108
+ min_successful=3,
109
+ tolerated_failure_count=2
110
+ )
111
+ """
112
+
113
+ min_successful: int | None = None
114
+ tolerated_failure_count: int | None = None
115
+ tolerated_failure_percentage: int | float | None = None
116
+
117
+ # TODO: reevaluate this
118
+ # @staticmethod
119
+ # def first_completed():
120
+ # return CompletionConfig(
121
+ # min_successful=None, tolerated_failure_count=None, tolerated_failure_percentage=None
122
+ # )
123
+
124
+ @staticmethod
125
+ def first_successful():
126
+ return CompletionConfig(
127
+ min_successful=1,
128
+ tolerated_failure_count=None,
129
+ tolerated_failure_percentage=None,
130
+ )
131
+
132
+ @staticmethod
133
+ def all_completed():
134
+ return CompletionConfig(
135
+ min_successful=None,
136
+ tolerated_failure_count=None,
137
+ tolerated_failure_percentage=None,
138
+ )
139
+
140
+ @staticmethod
141
+ def all_successful():
142
+ return CompletionConfig(
143
+ min_successful=None,
144
+ tolerated_failure_count=0,
145
+ tolerated_failure_percentage=0,
146
+ )
147
+
148
+
149
+ @dataclass(frozen=True)
150
+ class ParallelConfig:
151
+ """Configuration options for parallel execution operations.
152
+
153
+ This class configures how parallel operations are executed, including
154
+ concurrency limits, completion criteria, and serialization behavior.
155
+
156
+ Args:
157
+ max_concurrency: Maximum number of parallel branches to execute concurrently.
158
+ If None, no limit is imposed and all branches run concurrently.
159
+ Use this to control resource usage and prevent overwhelming the system.
160
+
161
+ completion_config: Defines when the parallel operation should complete.
162
+ Controls success/failure criteria for the overall parallel operation.
163
+ Default is CompletionConfig.all_successful() which requires all branches
164
+ to succeed. Other options include first_successful() and all_completed().
165
+
166
+ serdes: Custom serialization/deserialization configuration for BatchResult.
167
+ Applied at the handler level to serialize the entire BatchResult object.
168
+ If None, uses the default JSON serializer for BatchResult.
169
+
170
+ Backward Compatibility: If only 'serdes' is provided (no item_serdes),
171
+ it will be used for both individual functions AND BatchResult serialization
172
+ to maintain existing behavior.
173
+
174
+ item_serdes: Custom serialization/deserialization configuration for individual functions.
175
+ Applied to each function's result as tasks complete in child contexts.
176
+ If None, uses the default JSON serializer for individual function results.
177
+
178
+ When both 'serdes' and 'item_serdes' are provided:
179
+ - item_serdes: Used for individual function results in child contexts
180
+ - serdes: Used for the entire BatchResult at handler level
181
+
182
+ summary_generator: Function to generate compact summaries for large results (>256KB).
183
+ When the serialized result exceeds CHECKPOINT_SIZE_LIMIT, this generator
184
+ creates a JSON summary instead of checkpointing the full result. The operation
185
+ is marked with ReplayChildren=true to reconstruct the full result during replay.
186
+
187
+ Used internally by map/parallel operations to handle large BatchResult payloads.
188
+ Signature: (result: T) -> str
189
+
190
+ Example:
191
+ # Run at most 3 branches concurrently, succeed if any one succeeds
192
+ config = ParallelConfig(
193
+ max_concurrency=3,
194
+ completion_config=CompletionConfig.first_successful()
195
+ )
196
+ """
197
+
198
+ max_concurrency: int | None = None
199
+ completion_config: CompletionConfig = field(
200
+ default_factory=CompletionConfig.all_successful
201
+ )
202
+ serdes: SerDes | None = None
203
+ item_serdes: SerDes | None = None
204
+ summary_generator: SummaryGenerator | None = None
205
+
206
+
207
+ class StepSemantics(Enum):
208
+ AT_MOST_ONCE_PER_RETRY = "AT_MOST_ONCE_PER_RETRY"
209
+ AT_LEAST_ONCE_PER_RETRY = "AT_LEAST_ONCE_PER_RETRY"
210
+
211
+
212
+ @dataclass(frozen=True)
213
+ class StepConfig:
214
+ """Configuration for a step."""
215
+
216
+ retry_strategy: Callable[[Exception, int], RetryDecision] | None = None
217
+ step_semantics: StepSemantics = StepSemantics.AT_LEAST_ONCE_PER_RETRY
218
+ serdes: SerDes | None = None
219
+
220
+
221
+ class CheckpointMode(Enum):
222
+ NO_CHECKPOINT = ("NO_CHECKPOINT",)
223
+ CHECKPOINT_AT_FINISH = ("CHECKPOINT_AT_FINISH",)
224
+ CHECKPOINT_AT_START_AND_FINISH = "CHECKPOINT_AT_START_AND_FINISH"
225
+
226
+
227
+ @dataclass(frozen=True)
228
+ class ChildConfig(Generic[T]):
229
+ """Configuration options for child context operations.
230
+
231
+ This class configures how child contexts are executed and checkpointed,
232
+ matching the TypeScript ChildConfig interface behavior.
233
+
234
+ Args:
235
+ serdes: Custom serialization/deserialization configuration for BatchResult.
236
+ Applied at the handler level to serialize the entire BatchResult object.
237
+ If None, uses the default JSON serializer for BatchResult.
238
+
239
+ Backward Compatibility: If only 'serdes' is provided (no item_serdes),
240
+ it will be used for both individual items AND BatchResult serialization
241
+ to maintain existing behavior.
242
+
243
+ item_serdes: Custom serialization/deserialization configuration for individual items.
244
+ Applied to each item's result as tasks complete in child contexts.
245
+ If None, uses the default JSON serializer for individual items.
246
+
247
+ When both 'serdes' and 'item_serdes' are provided:
248
+ - item_serdes: Used for individual item results in child contexts
249
+ - serdes: Used for the entire BatchResult at handler level
250
+
251
+ sub_type: Operation subtype identifier used for tracking and debugging.
252
+ Examples: OperationSubType.MAP_ITERATION, OperationSubType.PARALLEL_BRANCH.
253
+ Used internally by the execution engine for operation classification.
254
+
255
+ summary_generator: Function to generate compact summaries for large results (>256KB).
256
+ When the serialized result exceeds CHECKPOINT_SIZE_LIMIT, this generator
257
+ creates a JSON summary instead of checkpointing the full result. The operation
258
+ is marked with ReplayChildren=true to reconstruct the full result during replay.
259
+
260
+ Used internally by map/parallel operations to handle large BatchResult payloads.
261
+ Signature: (result: T) -> str
262
+ Note:
263
+ checkpoint_mode field is commented out as it's not currently implemented.
264
+ When implemented, it will control when checkpoints are created:
265
+ - CHECKPOINT_AT_START_AND_FINISH: Checkpoint at both start and completion (default)
266
+ - CHECKPOINT_AT_FINISH: Only checkpoint when operation completes
267
+ - NO_CHECKPOINT: No automatic checkpointing
268
+
269
+ See TypeScript reference: aws-durable-execution-sdk-js/src/types/index.ts
270
+ """
271
+
272
+ # checkpoint_mode: CheckpointMode = CheckpointMode.CHECKPOINT_AT_START_AND_FINISH
273
+ serdes: SerDes | None = None
274
+ item_serdes: SerDes | None = None
275
+ sub_type: OperationSubType | None = None
276
+ summary_generator: SummaryGenerator | None = None
277
+
278
+
279
+ class ItemsPerBatchUnit(Enum):
280
+ COUNT = ("COUNT",)
281
+ BYTES = "BYTES"
282
+
283
+
284
+ @dataclass(frozen=True)
285
+ class ItemBatcher(Generic[T]):
286
+ """Configuration for batching items in map operations.
287
+
288
+ This class defines how individual items should be grouped together into batches
289
+ for more efficient processing in map operations.
290
+
291
+ Args:
292
+ max_items_per_batch: Maximum number of items to include in a single batch.
293
+ If 0 (default), no item count limit is applied. Use this to control
294
+ batch size when processing many small items.
295
+
296
+ max_item_bytes_per_batch: Maximum total size in bytes for items in a batch.
297
+ If 0 (default), no size limit is applied. Use this to control memory
298
+ usage when processing large items or when items vary significantly in size.
299
+
300
+ batch_input: Additional data to include with each batch.
301
+ This data is passed to the processing function along with the batched items.
302
+ Useful for providing context or configuration that applies to all items
303
+ in the batch.
304
+
305
+ Example:
306
+ # Batch up to 100 items or 1MB, whichever comes first
307
+ batcher = ItemBatcher(
308
+ max_items_per_batch=100,
309
+ max_item_bytes_per_batch=1024*1024,
310
+ batch_input={"processing_mode": "fast"}
311
+ )
312
+ """
313
+
314
+ max_items_per_batch: int = 0
315
+ max_item_bytes_per_batch: int | float = 0
316
+ batch_input: T | None = None
317
+
318
+
319
+ @dataclass(frozen=True)
320
+ class MapConfig:
321
+ """Configuration options for map operations over collections.
322
+
323
+ This class configures how map operations process collections of items,
324
+ including concurrency, batching, completion criteria, and serialization.
325
+
326
+ Args:
327
+ max_concurrency: Maximum number of items to process concurrently.
328
+ If None, no limit is imposed and all items are processed concurrently.
329
+ Use this to control resource usage when processing large collections.
330
+
331
+ item_batcher: Configuration for batching multiple items together for processing.
332
+ Allows grouping items by count or size to optimize processing efficiency.
333
+ Default is no batching (each item processed individually).
334
+
335
+ completion_config: Defines when the map operation should complete.
336
+ Controls success/failure criteria for the overall map operation.
337
+ Default allows any number of failures. Use CompletionConfig.all_successful()
338
+ to require all items to succeed.
339
+
340
+ serdes: Custom serialization/deserialization configuration for BatchResult.
341
+ Applied at the handler level to serialize the entire BatchResult object.
342
+ If None, uses the default JSON serializer for BatchResult.
343
+
344
+ Backward Compatibility: If only 'serdes' is provided (no item_serdes),
345
+ it will be used for both individual items AND BatchResult serialization
346
+ to maintain existing behavior.
347
+
348
+ item_serdes: Custom serialization/deserialization configuration for individual items.
349
+ Applied to each item's result as tasks complete in child contexts.
350
+ If None, uses the default JSON serializer for individual items.
351
+
352
+ When both 'serdes' and 'item_serdes' are provided:
353
+ - item_serdes: Used for individual item results in child contexts
354
+ - serdes: Used for the entire BatchResult at handler level
355
+
356
+ summary_generator: Function to generate compact summaries for large results (>256KB).
357
+ When the serialized result exceeds CHECKPOINT_SIZE_LIMIT, this generator
358
+ creates a JSON summary instead of checkpointing the full result. The operation
359
+ is marked with ReplayChildren=true to reconstruct the full result during replay.
360
+
361
+ Used internally by map/parallel operations to handle large BatchResult payloads.
362
+ Signature: (result: T) -> str
363
+
364
+ Example:
365
+ # Process 5 items at a time, batch by count, require all to succeed
366
+ config = MapConfig(
367
+ max_concurrency=5,
368
+ item_batcher=ItemBatcher(max_items_per_batch=10),
369
+ completion_config=CompletionConfig.all_successful()
370
+ )
371
+ """
372
+
373
+ max_concurrency: int | None = None
374
+ item_batcher: ItemBatcher = field(default_factory=ItemBatcher)
375
+ completion_config: CompletionConfig = field(default_factory=CompletionConfig)
376
+ serdes: SerDes | None = None
377
+ item_serdes: SerDes | None = None
378
+ summary_generator: SummaryGenerator | None = None
379
+
380
+
381
+ @dataclass(frozen=True)
382
+ class InvokeConfig(Generic[P, R]):
383
+ """
384
+ Configuration for invoke operations.
385
+
386
+ This class configures how function invocations are executed, including
387
+ timeout behavior, serialization, and tenant isolation.
388
+
389
+ Args:
390
+ timeout: Maximum duration to wait for the invoked function to complete.
391
+ Default is no timeout. Use this to prevent long-running invocations
392
+ from blocking execution indefinitely.
393
+
394
+ serdes_payload: Custom serialization/deserialization for the payload
395
+ sent to the invoked function. Defaults to DEFAULT_JSON_SERDES when
396
+ not set.
397
+
398
+ serdes_result: Custom serialization/deserialization for the result
399
+ returned from the invoked function. Defaults to DEFAULT_JSON_SERDES when
400
+ not set.
401
+
402
+ tenant_id: Optional tenant identifier for multi-tenant isolation.
403
+ If provided, the invocation will be scoped to this tenant.
404
+ """
405
+
406
+ # retry_strategy: Callable[[Exception, int], RetryDecision] | None = None
407
+ timeout: Duration = field(default_factory=Duration)
408
+ serdes_payload: SerDes[P] | None = None
409
+ serdes_result: SerDes[R] | None = None
410
+ tenant_id: str | None = None
411
+
412
+ @property
413
+ def timeout_seconds(self) -> int:
414
+ """Get timeout in seconds."""
415
+ return self.timeout.to_seconds()
416
+
417
+
418
+ @dataclass(frozen=True)
419
+ class CallbackConfig:
420
+ """Configuration for callbacks."""
421
+
422
+ timeout: Duration = field(default_factory=Duration)
423
+ heartbeat_timeout: Duration = field(default_factory=Duration)
424
+ serdes: SerDes | None = None
425
+
426
+ @property
427
+ def timeout_seconds(self) -> int:
428
+ """Get timeout in seconds."""
429
+ return self.timeout.to_seconds()
430
+
431
+ @property
432
+ def heartbeat_timeout_seconds(self) -> int:
433
+ """Get heartbeat timeout in seconds."""
434
+ return self.heartbeat_timeout.to_seconds()
435
+
436
+
437
+ @dataclass(frozen=True)
438
+ class WaitForCallbackConfig(CallbackConfig):
439
+ """Configuration for wait for callback."""
440
+
441
+ retry_strategy: Callable[[Exception, int], RetryDecision] | None = None
442
+
443
+
444
+ class StepFuture(Generic[T]):
445
+ """A future that will block on result() until the step returns."""
446
+
447
+ def __init__(self, future: Future[T], name: str | None = None):
448
+ self.name = name
449
+ self.future = future
450
+
451
+ def result(self, timeout_seconds: int | None = None) -> T:
452
+ """Return the result of the Future."""
453
+ return self.future.result(timeout=timeout_seconds)
454
+
455
+
456
+ # region Jitter
457
+
458
+
459
+ class JitterStrategy(StrEnum):
460
+ """
461
+ Jitter strategies are used to introduce noise when attempting to retry
462
+ an invoke. We introduce noise to prevent a thundering-herd effect where
463
+ a group of accesses (e.g. invokes) happen at once.
464
+
465
+ Jitter is meant to be used to spread operations across time.
466
+
467
+ Based on AWS Architecture Blog: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
468
+
469
+ members:
470
+ :NONE: No jitter; use the exact calculated delay
471
+ :FULL: Full jitter; random delay between 0 and calculated delay
472
+ :HALF: Equal jitter; random delay between 0.5x and 1.0x of the calculated delay
473
+ """
474
+
475
+ NONE = "NONE"
476
+ FULL = "FULL"
477
+ HALF = "HALF"
478
+
479
+ def apply_jitter(self, delay: float) -> float:
480
+ """Apply jitter to a delay value and return the final delay.
481
+
482
+ Args:
483
+ delay: The base delay value to apply jitter to
484
+
485
+ Returns:
486
+ The final delay after applying jitter strategy
487
+ """
488
+ match self:
489
+ case JitterStrategy.NONE:
490
+ return delay
491
+ case JitterStrategy.HALF:
492
+ # Equal jitter: delay/2 + random(0, delay/2)
493
+ return delay / 2 + random.random() * (delay / 2) # noqa: S311
494
+ case _: # default is FULL
495
+ # Full jitter: random(0, delay)
496
+ return random.random() * delay # noqa: S311
497
+
498
+
499
+ # endregion Jitter