|
26 | 26 | Operation, |
27 | 27 | OperationType, |
28 | 28 | OperationUpdate, |
| 29 | + InvocationStatus, |
| 30 | + DurableExecutionInvocationOutput, |
| 31 | +) |
| 32 | +from aws_durable_execution_sdk_python.plugin import ( |
| 33 | + DurableExecutionPlugin, |
| 34 | + PluginExecutor, |
| 35 | + handle_plugins, |
29 | 36 | ) |
30 | 37 | from aws_durable_execution_sdk_python.state import ExecutionState, ReplayStatus |
31 | 38 |
|
@@ -149,77 +156,36 @@ def from_durable_execution_invocation_input( |
149 | 156 | ) |
150 | 157 |
|
151 | 158 |
|
152 | | -class InvocationStatus(Enum): |
153 | | - SUCCEEDED = "SUCCEEDED" |
154 | | - FAILED = "FAILED" |
155 | | - PENDING = "PENDING" |
156 | | - |
157 | | - |
158 | | -@dataclass(frozen=True) |
159 | | -class DurableExecutionInvocationOutput: |
160 | | - """Representation the DurableExecutionInvocationOutput. This is what the Durable lambda handler returns. |
161 | | -
|
162 | | - If the execution has been already completed via an update to the EXECUTION operation via CheckpointDurableExecution, |
163 | | - payload must be empty for SUCCEEDED/FAILED status. |
164 | | - """ |
165 | | - |
166 | | - status: InvocationStatus |
167 | | - result: str | None = None |
168 | | - error: ErrorObject | None = None |
169 | | - |
170 | | - @classmethod |
171 | | - def from_dict( |
172 | | - cls, data: MutableMapping[str, Any] |
173 | | - ) -> DurableExecutionInvocationOutput: |
174 | | - """Create an instance from a dictionary. |
175 | | -
|
176 | | - Args: |
177 | | - data: Dictionary with camelCase keys matching the original structure |
178 | | -
|
179 | | - Returns: |
180 | | - A DurableExecutionInvocationOutput instance |
181 | | - """ |
182 | | - status = InvocationStatus(data.get("Status")) |
183 | | - error = ErrorObject.from_dict(data["Error"]) if data.get("Error") else None |
184 | | - return cls(status=status, result=data.get("Result"), error=error) |
185 | | - |
186 | | - def to_dict(self) -> MutableMapping[str, Any]: |
187 | | - """Convert to a dictionary with the original field names. |
188 | | -
|
189 | | - Returns: |
190 | | - Dictionary with the original camelCase keys |
191 | | - """ |
192 | | - result: MutableMapping[str, Any] = {"Status": self.status.value} |
193 | | - |
194 | | - if self.result is not None: |
195 | | - # large payloads return "", because checkpointed already |
196 | | - result["Result"] = self.result |
197 | | - if self.error: |
198 | | - result["Error"] = self.error.to_dict() |
199 | | - |
200 | | - return result |
201 | | - |
202 | | - @classmethod |
203 | | - def create_succeeded(cls, result: str) -> DurableExecutionInvocationOutput: |
204 | | - """Create a succeeded invocation output.""" |
205 | | - return cls(status=InvocationStatus.SUCCEEDED, result=result) |
206 | | - |
207 | | - |
208 | 159 | # endregion Invocation models |
209 | 160 |
|
210 | 161 |
|
211 | 162 | def durable_execution( |
212 | 163 | func: Callable[[Any, DurableContext], Any] | None = None, |
213 | 164 | *, |
214 | 165 | boto3_client: Boto3LambdaClient | None = None, |
| 166 | + plugins: list[DurableExecutionPlugin] | None = None, |
215 | 167 | ) -> Callable[[Any, LambdaContext], Any]: |
| 168 | + """ |
| 169 | + Decorator to create a durable execution handler. |
| 170 | +
|
| 171 | + Args: |
| 172 | + func: The user function to decorate |
| 173 | + boto3_client: Optional boto3 Lambda client to use |
| 174 | + plugins: Optional list of plugins to use (EXPERIMENTAL: This |
| 175 | + parameter may change or be removed.) |
| 176 | + """ |
216 | 177 | # Decorator called with parameters |
217 | 178 | if func is None: |
218 | 179 | logger.debug("Decorator called with parameters") |
219 | | - return functools.partial(durable_execution, boto3_client=boto3_client) |
| 180 | + return functools.partial( |
| 181 | + durable_execution, boto3_client=boto3_client, plugins=plugins |
| 182 | + ) |
220 | 183 |
|
221 | 184 | logger.debug("Starting durable execution handler...") |
222 | 185 |
|
| 186 | + plugin_executor = PluginExecutor(plugins) |
| 187 | + |
| 188 | + @handle_plugins(plugin_executor) |
223 | 189 | def wrapper(event: Any, context: LambdaContext) -> MutableMapping[str, Any]: |
224 | 190 | invocation_input: DurableExecutionInvocationInput |
225 | 191 | service_client: DurableServiceClient |
@@ -255,6 +221,7 @@ def wrapper(event: Any, context: LambdaContext) -> MutableMapping[str, Any]: |
255 | 221 | operations={}, |
256 | 222 | service_client=service_client, |
257 | 223 | replay_status=ReplayStatus.NEW, |
| 224 | + plugin_executor=plugin_executor, |
258 | 225 | ) |
259 | 226 |
|
260 | 227 | try: |
@@ -306,6 +273,13 @@ def wrapper(event: Any, context: LambdaContext) -> MutableMapping[str, Any]: |
306 | 273 | ) as executor, |
307 | 274 | contextlib.closing(execution_state) as execution_state, |
308 | 275 | ): |
| 276 | + # execute the plugins |
| 277 | + plugin_executor.on_invocation_start( |
| 278 | + durable_execution_arn=invocation_input.durable_execution_arn, |
| 279 | + context=context, |
| 280 | + execution_operation=execution_state.get_execution_operation(), |
| 281 | + is_replaying=execution_state.is_replaying(), |
| 282 | + ) |
309 | 283 | # Thread 1: Run background checkpoint processing |
310 | 284 | executor.submit(execution_state.checkpoint_batches_forever) |
311 | 285 |
|
|
0 commit comments