Skip to content

Commit 9086c84

Browse files
author
Rares Polenciuc
committed
feat: implement Lambda invocation with error handling
- Add parameter validation and comprehensive error handling for all exception types - Implement status code validation and function error detection - Add ResourceNotFoundException and InvalidParameterValueException handling - Include test coverage for all error paths and edge cases - Support synchronous Lambda invocation with proper payload serialization and response parsing
1 parent a6bdb18 commit 9086c84

File tree

2 files changed

+411
-30
lines changed

2 files changed

+411
-30
lines changed

src/aws_durable_execution_sdk_python_testing/invoker.py

Lines changed: 115 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@
88
DurableExecutionInvocationInput,
99
DurableExecutionInvocationInputWithClient,
1010
DurableExecutionInvocationOutput,
11-
InitialExecutionState,
11+
InitialExecutionState, InvocationStatus,
1212
)
1313

1414
from aws_durable_execution_sdk_python_testing.exceptions import (
1515
DurableFunctionsTestError,
1616
)
1717
from aws_durable_execution_sdk_python_testing.model import LambdaContext
1818

19-
2019
if TYPE_CHECKING:
2120
from collections.abc import Callable
2221

@@ -55,17 +54,17 @@ def create_test_lambda_context() -> LambdaContext:
5554

5655
class Invoker(Protocol):
5756
def create_invocation_input(
58-
self, execution: Execution
57+
self, execution: Execution
5958
) -> DurableExecutionInvocationInput: ... # pragma: no cover
6059

6160
def invoke(
62-
self,
63-
function_name: str,
64-
input: DurableExecutionInvocationInput,
61+
self,
62+
function_name: str,
63+
input: DurableExecutionInvocationInput,
6564
) -> DurableExecutionInvocationOutput: ... # pragma: no cover
6665

6766
def update_endpoint(
68-
self, endpoint_url: str, region_name: str
67+
self, endpoint_url: str, region_name: str
6968
) -> None: ... # pragma: no cover
7069

7170

@@ -75,7 +74,7 @@ def __init__(self, handler: Callable, service_client: InMemoryServiceClient):
7574
self.service_client = service_client
7675

7776
def create_invocation_input(
78-
self, execution: Execution
77+
self, execution: Execution
7978
) -> DurableExecutionInvocationInput:
8079
return DurableExecutionInvocationInputWithClient(
8180
durable_execution_arn=execution.durable_execution_arn,
@@ -90,9 +89,9 @@ def create_invocation_input(
9089
)
9190

9291
def invoke(
93-
self,
94-
function_name: str, # noqa: ARG002
95-
input: DurableExecutionInvocationInput,
92+
self,
93+
function_name: str, # noqa: ARG002
94+
input: DurableExecutionInvocationInput,
9695
) -> DurableExecutionInvocationOutput:
9796
# TODO: reasses if function_name will be used in future
9897
input_with_client = DurableExecutionInvocationInputWithClient.from_durable_execution_invocation_input(
@@ -126,7 +125,7 @@ def update_endpoint(self, endpoint_url: str, region_name: str) -> None:
126125
)
127126

128127
def create_invocation_input(
129-
self, execution: Execution
128+
self, execution: Execution
130129
) -> DurableExecutionInvocationInput:
131130
return DurableExecutionInvocationInput(
132131
durable_execution_arn=execution.durable_execution_arn,
@@ -139,21 +138,111 @@ def create_invocation_input(
139138
)
140139

141140
def invoke(
142-
self,
143-
function_name: str,
144-
input: DurableExecutionInvocationInput,
141+
self,
142+
function_name: str,
143+
input: DurableExecutionInvocationInput,
145144
) -> DurableExecutionInvocationOutput:
146-
# TODO: wrap ResourceNotFoundException from lambda in ResourceNotFoundException from this lib
147-
response = self.lambda_client.invoke(
148-
FunctionName=function_name,
149-
InvocationType="RequestResponse", # Synchronous invocation
150-
Payload=json.dumps(input.to_dict(), default=str),
145+
"""Invoke AWS Lambda function and return durable execution result.
146+
147+
Args:
148+
function_name: Name of the Lambda function to invoke
149+
input: Durable execution invocation input
150+
151+
Returns:
152+
DurableExecutionInvocationOutput: Result of the function execution
153+
154+
Raises:
155+
ResourceNotFoundException: If function does not exist
156+
InvalidParameterValueException: If parameters are invalid
157+
DurableFunctionsTestError: For other invocation failures
158+
"""
159+
from aws_durable_execution_sdk_python_testing.exceptions import (
160+
ResourceNotFoundException,
161+
InvalidParameterValueException,
151162
)
152163

153-
# very simplified placeholder lol
154-
if response["StatusCode"] == 200: # noqa: PLR2004
155-
json_response = json.loads(response["Payload"].read().decode("utf-8"))
156-
return DurableExecutionInvocationOutput.from_dict(json_response)
164+
# Parameter validation
165+
if not function_name or not function_name.strip():
166+
msg = "Function name is required"
167+
raise InvalidParameterValueException(msg)
168+
169+
try:
170+
# Invoke AWS Lambda function using standard invoke method
171+
response = self.lambda_client.invoke(
172+
FunctionName=function_name,
173+
InvocationType="RequestResponse", # Synchronous invocation
174+
Payload=json.dumps(input.to_dict(), default=str),
175+
)
157176

158-
msg: str = f"Lambda invocation failed with status code: {response['StatusCode']}, {response['Payload']=}"
159-
raise DurableFunctionsTestError(msg)
177+
# Check HTTP status code
178+
status_code = response.get("StatusCode")
179+
if status_code not in (200, 202, 204):
180+
msg = f"Lambda invocation failed with status code: {status_code}"
181+
raise DurableFunctionsTestError(msg)
182+
183+
# Check for function errors
184+
if "FunctionError" in response:
185+
error_payload = response["Payload"].read().decode("utf-8")
186+
msg = f"Lambda invocation failed with status {status_code}: {error_payload}"
187+
raise DurableFunctionsTestError(msg)
188+
189+
# Parse response payload
190+
response_payload = response["Payload"].read().decode("utf-8")
191+
response_dict = json.loads(response_payload)
192+
193+
# Convert to DurableExecutionInvocationOutput
194+
return DurableExecutionInvocationOutput.from_dict(response_dict)
195+
196+
except self.lambda_client.exceptions.ResourceNotFoundException as e:
197+
msg = f"Function not found: {function_name}"
198+
raise ResourceNotFoundException(msg) from e
199+
except self.lambda_client.exceptions.InvalidParameterValueException as e:
200+
msg = f"Invalid parameter: {e}"
201+
raise InvalidParameterValueException(msg) from e
202+
except (
203+
self.lambda_client.exceptions.TooManyRequestsException,
204+
self.lambda_client.exceptions.ServiceException,
205+
self.lambda_client.exceptions.ResourceConflictException,
206+
self.lambda_client.exceptions.InvalidRequestContentException,
207+
self.lambda_client.exceptions.RequestTooLargeException,
208+
self.lambda_client.exceptions.UnsupportedMediaTypeException,
209+
self.lambda_client.exceptions.InvalidRuntimeException,
210+
self.lambda_client.exceptions.InvalidZipFileException,
211+
self.lambda_client.exceptions.ResourceNotReadyException,
212+
self.lambda_client.exceptions.SnapStartTimeoutException,
213+
self.lambda_client.exceptions.SnapStartNotReadyException,
214+
self.lambda_client.exceptions.SnapStartException,
215+
self.lambda_client.exceptions.RecursiveInvocationException,
216+
) as e:
217+
msg = f"Lambda invocation failed: {e}"
218+
raise DurableFunctionsTestError(msg) from e
219+
except (
220+
self.lambda_client.exceptions.InvalidSecurityGroupIDException,
221+
self.lambda_client.exceptions.EC2ThrottledException,
222+
self.lambda_client.exceptions.EFSMountConnectivityException,
223+
self.lambda_client.exceptions.SubnetIPAddressLimitReachedException,
224+
self.lambda_client.exceptions.EC2UnexpectedException,
225+
self.lambda_client.exceptions.InvalidSubnetIDException,
226+
self.lambda_client.exceptions.EC2AccessDeniedException,
227+
self.lambda_client.exceptions.EFSIOException,
228+
self.lambda_client.exceptions.ENILimitReachedException,
229+
self.lambda_client.exceptions.EFSMountTimeoutException,
230+
self.lambda_client.exceptions.EFSMountFailureException,
231+
) as e:
232+
msg = f"Lambda infrastructure error: {e}"
233+
raise DurableFunctionsTestError(msg) from e
234+
except (
235+
self.lambda_client.exceptions.KMSAccessDeniedException,
236+
self.lambda_client.exceptions.KMSDisabledException,
237+
self.lambda_client.exceptions.KMSNotFoundException,
238+
self.lambda_client.exceptions.KMSInvalidStateException,
239+
) as e:
240+
msg = f"Lambda KMS error: {e}"
241+
raise DurableFunctionsTestError(msg) from e
242+
except Exception as e:
243+
# Handle any remaining exceptions, including custom ones like DurableExecutionAlreadyStartedException
244+
if "DurableExecutionAlreadyStartedException" in str(type(e)):
245+
msg = f"Durable execution already started: {e}"
246+
raise DurableFunctionsTestError(msg) from e
247+
msg = f"Unexpected error during Lambda invocation: {e}"
248+
raise DurableFunctionsTestError(msg) from e

0 commit comments

Comments
 (0)