Coverage for async-durable-execution/src/async_durable_execution/primitive/base.py: 100%
59 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 16:54 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 16:54 +0000
1"""Base classes and shared helpers for operation executors."""
3from __future__ import annotations
5from abc import ABC, abstractmethod
6from dataclasses import dataclass
7from typing import TYPE_CHECKING, Generic, TypeVar
9from ..models import (
10 Operation,
11 OperationIdentifier,
12)
13from ..serdes import SerDes, deserialize, serialize
15if TYPE_CHECKING:
16 from ..models import LambdaContext
17 from ..models import OperationUpdate
18 from ..state import ExecutionState
20T = TypeVar("T")
21S = TypeVar("S")
24@dataclass(frozen=True)
25class OperationContext:
26 """Protocol defining the interface for durable execution contexts."""
28 execution_state: ExecutionState
29 operation_identifier: OperationIdentifier
31 @property
32 def lambda_context(self) -> LambdaContext | None:
33 """Get the Lambda context for the active invocation."""
34 return self.execution_state.lambda_context
36 @property
37 def durable_execution_arn(self) -> str:
38 """Get the ARN of the Durable Execution."""
39 return self.execution_state.durable_execution_arn
41 @property
42 def parent_id(self) -> str | None:
43 return self.operation_identifier.parent_id
45 @property
46 def operation_id(self) -> str | None:
47 return self.operation_identifier.operation_id
49 @property
50 def operation_name(self) -> str | None:
51 return self.operation_identifier.name
53 def is_replaying(self) -> bool:
54 """Return whether the active context is replaying prior user code."""
55 return False
58class OperationExecutor(ABC, Generic[T]):
59 """Base class for durable operations with shared state and serdes helpers."""
61 def __init__(
62 self,
63 state: ExecutionState,
64 operation_identifier: OperationIdentifier,
65 ) -> None:
66 self.state = state
67 self.operation_identifier = operation_identifier
69 @property
70 def operation_id(self) -> str:
71 """Return the required operation id for this executor."""
72 return self.operation_identifier.require_operation_id()
74 @property
75 def operation_name(self) -> str | None:
76 """Return the human-readable operation name, if provided."""
77 return self.operation_identifier.name
79 @property
80 def durable_execution_arn(self) -> str:
81 """Return the durable execution ARN for serialization helpers."""
82 return self.state.durable_execution_arn
84 async def create_checkpoint(
85 self,
86 operation_update: OperationUpdate,
87 *,
88 is_sync: bool | None = None,
89 ) -> Operation | None:
90 """Persist a checkpoint update for this operation."""
91 if is_sync is None:
92 return await self.state.create_checkpoint(
93 operation_update=operation_update,
94 )
96 return await self.state.create_checkpoint(
97 operation_update=operation_update,
98 is_sync=is_sync,
99 )
101 async def serialize_value(self, value: S, serdes: SerDes[S] | None) -> str:
102 """Serialize a value using operation-scoped metadata."""
103 return await serialize(
104 serdes=serdes,
105 value=value,
106 operation_id=self.operation_id,
107 durable_execution_arn=self.durable_execution_arn,
108 )
110 async def deserialize_value(self, data: str, serdes: SerDes[S] | None) -> S:
111 """Deserialize a value using operation-scoped metadata."""
112 return await deserialize(
113 serdes=serdes,
114 data=data,
115 operation_id=self.operation_id,
116 durable_execution_arn=self.durable_execution_arn,
117 )
119 @abstractmethod
120 async def start(self) -> T:
121 """Start a new operation with no existing checkpoint."""
122 ... # pragma: no cover
124 @abstractmethod
125 async def replay(self, operation: Operation) -> T:
126 """Replay an operation from an existing checkpoint."""
127 ... # pragma: no cover
129 async def process(self) -> T:
130 """Process the operation, including replay and checkpoint handling."""
131 operation = self.state.operations.get(self.operation_id)
132 if operation is None:
133 return await self.start()
134 return await self.replay(operation)