|
26 | 26 | Operation, |
27 | 27 | OperationType, |
28 | 28 | OperationUpdate, |
| 29 | + InvocationStatus, |
29 | 30 | ) |
| 31 | +from aws_durable_execution_sdk_python.plugin import DurableExecutionPlugin, PluginExecutor |
30 | 32 | from aws_durable_execution_sdk_python.state import ExecutionState, ReplayStatus |
31 | 33 |
|
32 | 34 | if TYPE_CHECKING: |
@@ -149,12 +151,6 @@ def from_durable_execution_invocation_input( |
149 | 151 | ) |
150 | 152 |
|
151 | 153 |
|
152 | | -class InvocationStatus(Enum): |
153 | | - SUCCEEDED = "SUCCEEDED" |
154 | | - FAILED = "FAILED" |
155 | | - PENDING = "PENDING" |
156 | | - |
157 | | - |
158 | 154 | @dataclass(frozen=True) |
159 | 155 | class DurableExecutionInvocationOutput: |
160 | 156 | """Representation the DurableExecutionInvocationOutput. This is what the Durable lambda handler returns. |
@@ -204,22 +200,63 @@ def create_succeeded(cls, result: str) -> DurableExecutionInvocationOutput: |
204 | 200 | """Create a succeeded invocation output.""" |
205 | 201 | return cls(status=InvocationStatus.SUCCEEDED, result=result) |
206 | 202 |
|
| 203 | + @classmethod |
| 204 | + def create_retry(cls, error: ErrorObject) -> DurableExecutionInvocationOutput: |
| 205 | + """Create a failed invocation output.""" |
| 206 | + return cls(status=InvocationStatus.RETRY, error=error) |
207 | 207 |
|
208 | 208 | # endregion Invocation models |
209 | 209 |
|
210 | 210 |
|
| 211 | +def handle_plugins(plugin_executor: PluginExecutor): |
| 212 | + def decorator(func): |
| 213 | + @functools.wraps(func) |
| 214 | + def wrapper(event: Any, context: LambdaContext): |
| 215 | + with plugin_executor.start(): |
| 216 | + durable_execution_arn = event.get("DurableExecutionArn") |
| 217 | + try: |
| 218 | + output = func(event, context) |
| 219 | + plugin_executor.on_invocation_end( |
| 220 | + durable_execution_arn=durable_execution_arn, |
| 221 | + context=context, |
| 222 | + output=output, |
| 223 | + ) |
| 224 | + except Exception as e: |
| 225 | + plugin_executor.on_invocation_end( |
| 226 | + durable_execution_arn=durable_execution_arn, |
| 227 | + context=context, |
| 228 | + output=DurableExecutionInvocationOutput.create_retry(ErrorObject.from_exception(e)), |
| 229 | + ) |
| 230 | + |
| 231 | + return wrapper |
| 232 | + return decorator |
| 233 | + |
| 234 | + |
211 | 235 | def durable_execution( |
212 | 236 | func: Callable[[Any, DurableContext], Any] | None = None, |
213 | 237 | *, |
214 | 238 | boto3_client: Boto3LambdaClient | None = None, |
| 239 | + plugins: list[DurableExecutionPlugin] | None = None, |
215 | 240 | ) -> Callable[[Any, LambdaContext], Any]: |
| 241 | + """ |
| 242 | + Decorator to create a durable execution handler. |
| 243 | +
|
| 244 | + Args: |
| 245 | + func: The user function to decorate |
| 246 | + boto3_client: Optional boto3 Lambda client to use |
| 247 | + plugins: Optional list of plugins to use (EXPERIMENTAL: This |
| 248 | + parameter may change or be removed.) |
| 249 | + """ |
216 | 250 | # Decorator called with parameters |
217 | 251 | if func is None: |
218 | 252 | logger.debug("Decorator called with parameters") |
219 | | - return functools.partial(durable_execution, boto3_client=boto3_client) |
| 253 | + return functools.partial(durable_execution, boto3_client=boto3_client, plugins=plugins) |
220 | 254 |
|
221 | 255 | logger.debug("Starting durable execution handler...") |
222 | 256 |
|
| 257 | + plugin_executor = PluginExecutor(plugins) |
| 258 | + |
| 259 | + @handle_plugins(plugin_executor) |
223 | 260 | def wrapper(event: Any, context: LambdaContext) -> MutableMapping[str, Any]: |
224 | 261 | invocation_input: DurableExecutionInvocationInput |
225 | 262 | service_client: DurableServiceClient |
@@ -255,6 +292,7 @@ def wrapper(event: Any, context: LambdaContext) -> MutableMapping[str, Any]: |
255 | 292 | operations={}, |
256 | 293 | service_client=service_client, |
257 | 294 | replay_status=ReplayStatus.NEW, |
| 295 | + plugin_executor=plugin_executor, |
258 | 296 | ) |
259 | 297 |
|
260 | 298 | try: |
@@ -306,6 +344,13 @@ def wrapper(event: Any, context: LambdaContext) -> MutableMapping[str, Any]: |
306 | 344 | ) as executor, |
307 | 345 | contextlib.closing(execution_state) as execution_state, |
308 | 346 | ): |
| 347 | + # execute the plugins |
| 348 | + plugin_executor.on_invocation_start( |
| 349 | + durable_execution_arn=invocation_input.durable_execution_arn, |
| 350 | + context=context, |
| 351 | + execution_operation=execution_state.get_execution_operation(), |
| 352 | + is_replaying=execution_state.is_replaying(), |
| 353 | + ) |
309 | 354 | # Thread 1: Run background checkpoint processing |
310 | 355 | executor.submit(execution_state.checkpoint_batches_forever) |
311 | 356 |
|
|
0 commit comments