diff --git a/src/aws_durable_execution_sdk_python/botocore/data/lambdainternal-local/2015-03-31/service-2.json b/src/aws_durable_execution_sdk_python/botocore/data/lambdainternal-local/2015-03-31/service-2.json index 11438cb..bd0dd05 100644 --- a/src/aws_durable_execution_sdk_python/botocore/data/lambdainternal-local/2015-03-31/service-2.json +++ b/src/aws_durable_execution_sdk_python/botocore/data/lambdainternal-local/2015-03-31/service-2.json @@ -7321,10 +7321,9 @@ "Error":{"shape":"EventError"} } }, - "InvokeDetails":{ + "ChainedInvokeDetails":{ "type":"structure", "members":{ - "DurableExecutionArn":{"shape":"DurableExecutionArn"}, "Result":{"shape":"OperationPayload"}, "Error":{"shape":"ErrorObject"} } @@ -7343,12 +7342,11 @@ "RESPONSE_STREAM" ] }, - "InvokeOptions":{ + "ChainedInvokeOptions":{ "type":"structure", "members":{ "FunctionName":{"shape":"FunctionName"}, - "FunctionQualifier":{"shape":"Version"}, - "DurableExecutionName":{"shape":"DurableExecutionName"} + "TimeoutSeconds":{"shape":"DurationSeconds"} } }, "InvokeResponseStreamUpdate":{ @@ -9332,7 +9330,7 @@ "StepDetails":{"shape":"StepDetails"}, "WaitDetails":{"shape":"WaitDetails"}, "CallbackDetails":{"shape":"CallbackDetails"}, - "InvokeDetails":{"shape":"InvokeDetails"} + "ChainedInvokeDetails":{"shape":"ChainedInvokeDetails"} } }, "OperationAction":{ @@ -9387,7 +9385,7 @@ "STEP", "WAIT", "CALLBACK", - "INVOKE" + "CHAINED_INVOKE" ] }, "OperationUpdate":{ @@ -9405,7 +9403,7 @@ "StepOptions":{"shape":"StepOptions"}, "WaitOptions":{"shape":"WaitOptions"}, "CallbackOptions":{"shape":"CallbackOptions"}, - "InvokeOptions":{"shape":"InvokeOptions"} + "ChainedInvokeOptions":{"shape":"ChainedInvokeOptions"} } }, "OperationUpdates":{ diff --git a/src/aws_durable_execution_sdk_python/botocore/data/lambdainternal/2015-03-31/service-2.json b/src/aws_durable_execution_sdk_python/botocore/data/lambdainternal/2015-03-31/service-2.json index 10a4cbb..264d2c6 100644 --- a/src/aws_durable_execution_sdk_python/botocore/data/lambdainternal/2015-03-31/service-2.json +++ b/src/aws_durable_execution_sdk_python/botocore/data/lambdainternal/2015-03-31/service-2.json @@ -7320,10 +7320,9 @@ "Error":{"shape":"EventError"} } }, - "InvokeDetails":{ + "ChainedInvokeDetails":{ "type":"structure", "members":{ - "DurableExecutionArn":{"shape":"DurableExecutionArn"}, "Result":{"shape":"OperationPayload"}, "Error":{"shape":"ErrorObject"} } @@ -7342,12 +7341,11 @@ "RESPONSE_STREAM" ] }, - "InvokeOptions":{ + "ChainedInvokeOptions":{ "type":"structure", "members":{ "FunctionName":{"shape":"FunctionName"}, - "FunctionQualifier":{"shape":"Version"}, - "DurableExecutionName":{"shape":"DurableExecutionName"} + "TimeoutSeconds":{"shape":"DurationSeconds"} } }, "InvokeResponseStreamUpdate":{ @@ -9331,7 +9329,7 @@ "StepDetails":{"shape":"StepDetails"}, "WaitDetails":{"shape":"WaitDetails"}, "CallbackDetails":{"shape":"CallbackDetails"}, - "InvokeDetails":{"shape":"InvokeDetails"} + "ChainedInvokeDetails":{"shape":"ChainedInvokeDetails"} } }, "OperationAction":{ @@ -9386,7 +9384,7 @@ "STEP", "WAIT", "CALLBACK", - "INVOKE" + "CHAINED_INVOKE" ] }, "OperationUpdate":{ @@ -9404,7 +9402,7 @@ "StepOptions":{"shape":"StepOptions"}, "WaitOptions":{"shape":"WaitOptions"}, "CallbackOptions":{"shape":"CallbackOptions"}, - "InvokeOptions":{"shape":"InvokeOptions"} + "ChainedInvokeOptions":{"shape":"ChainedInvokeOptions"} } }, "OperationUpdates":{ diff --git a/src/aws_durable_execution_sdk_python/lambda_service.py b/src/aws_durable_execution_sdk_python/lambda_service.py index db80854..fb8d622 100644 --- a/src/aws_durable_execution_sdk_python/lambda_service.py +++ b/src/aws_durable_execution_sdk_python/lambda_service.py @@ -22,6 +22,10 @@ logger = logging.getLogger(__name__) +type ReplayChildren = bool +type OperationPayload = str +type TimeoutSeconds = int + # region model class OperationAction(Enum): @@ -49,7 +53,24 @@ class OperationType(Enum): STEP = "STEP" WAIT = "WAIT" CALLBACK = "CALLBACK" - INVOKE = "INVOKE" + CHAINED_INVOKE = "CHAINED_INVOKE" + + +class CallbackTimeoutType(Enum): + TIMEOUT = "Callback.Timeout" + HEARTBEAT = "Callback.Heartbeat" + + +class ChainedInvokeFailedToStartType(Enum): + FAILED_TO_START = "ChainedInvoke.FailedToStart" + + +class ChainedInvokeTimeoutType(Enum): + TIMEOUT = "ChainedInvoke.Timeout" + + +class ChainedInvokeStopType(Enum): + STOPPED = "ChainedInvoke.Stopped" class OperationSubType(Enum): @@ -77,8 +98,8 @@ def from_dict(cls, data: MutableMapping[str, Any]) -> ExecutionDetails: @dataclass(frozen=True) class ContextDetails: - replay_children: bool = False - result: str | None = None + replay_children: ReplayChildren = False + result: OperationPayload | None = None error: ErrorObject | None = None @classmethod @@ -150,7 +171,7 @@ def to_callable_runtime_error(self) -> CallableRuntimeError: class StepDetails: attempt: int = 0 next_attempt_timestamp: datetime.datetime | None = None - result: str | None = None + result: OperationPayload | None = None error: ErrorObject | None = None @classmethod @@ -190,16 +211,14 @@ def from_dict(cls, data: MutableMapping[str, Any]) -> CallbackDetails: @dataclass(frozen=True) -class InvokeDetails: - durable_execution_arn: str +class ChainedInvokeDetails: result: str | None = None error: ErrorObject | None = None @classmethod - def from_dict(cls, data: MutableMapping[str, Any]) -> InvokeDetails: + def from_dict(cls, data: MutableMapping[str, Any]) -> ChainedInvokeDetails: error_raw = data.get("Error") return cls( - durable_execution_arn=data["DurableExecutionArn"], result=data.get("Result"), error=ErrorObject.from_dict(error_raw) if error_raw else None, ) @@ -233,7 +252,7 @@ def to_dict(self) -> MutableMapping[str, Any]: @dataclass(frozen=True) class CallbackOptions: - timeout_seconds: int = 0 + timeout_seconds: TimeoutSeconds = 0 heartbeat_timeout_seconds: int = 0 @classmethod @@ -251,26 +270,28 @@ def to_dict(self) -> MutableMapping[str, Any]: @dataclass(frozen=True) -class InvokeOptions: +class ChainedInvokeOptions: function_name: str - timeout_seconds: int = 0 + timeout_seconds: TimeoutSeconds = 0 @classmethod - def from_dict(cls, data: MutableMapping[str, Any]) -> InvokeOptions: + def from_dict(cls, data: MutableMapping[str, Any]) -> ChainedInvokeOptions: return cls( function_name=data["FunctionName"], timeout_seconds=data.get("TimeoutSeconds", 0), ) def to_dict(self) -> MutableMapping[str, Any]: - result: MutableMapping[str, Any] = {"FunctionName": self.function_name} - result["TimeoutSeconds"] = self.timeout_seconds + result: MutableMapping[str, Any] = { + "FunctionName": self.function_name, + "TimeoutSeconds": self.timeout_seconds, + } return result @dataclass(frozen=True) class ContextOptions: - replay_children: bool = False + replay_children: ReplayChildren = False @classmethod def from_dict(cls, data: MutableMapping[str, Any]) -> ContextOptions: @@ -299,7 +320,7 @@ class OperationUpdate: step_options: StepOptions | None = None wait_options: WaitOptions | None = None callback_options: CallbackOptions | None = None - invoke_options: InvokeOptions | None = None + chained_invoke_options: ChainedInvokeOptions | None = None def to_dict(self) -> MutableMapping[str, Any]: result: MutableMapping[str, Any] = { @@ -326,8 +347,8 @@ def to_dict(self) -> MutableMapping[str, Any]: result["WaitOptions"] = self.wait_options.to_dict() if self.callback_options: result["CallbackOptions"] = self.callback_options.to_dict() - if self.invoke_options: - result["InvokeOptions"] = self.invoke_options.to_dict() + if self.chained_invoke_options: + result["ChainedInvokeOptions"] = self.chained_invoke_options.to_dict() return result @@ -352,9 +373,9 @@ def from_dict(cls, data: MutableMapping[str, Any]) -> OperationUpdate: if callback_data := data.get("CallbackOptions"): callback_options = CallbackOptions.from_dict(callback_data) - invoke_options = None - if invoke_data := data.get("InvokeOptions"): - invoke_options = InvokeOptions.from_dict(invoke_data) + chained_invoke_options = None + if invoke_data := data.get("ChainedInvokeOptions"): + chained_invoke_options = ChainedInvokeOptions.from_dict(invoke_data) return cls( operation_id=data["Id"], @@ -369,7 +390,7 @@ def from_dict(cls, data: MutableMapping[str, Any]) -> OperationUpdate: step_options=step_options, wait_options=wait_options, callback_options=callback_options, - invoke_options=invoke_options, + chained_invoke_options=chained_invoke_options, ) @classmethod @@ -537,18 +558,18 @@ def create_invoke_start( cls, identifier: OperationIdentifier, payload: str, - invoke_options: InvokeOptions, + chained_invoke_options: ChainedInvokeOptions, ) -> OperationUpdate: """Create an instance of OperationUpdate for type: INVOKE, action: START.""" return cls( operation_id=identifier.operation_id, parent_id=identifier.parent_id, - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, sub_type=OperationSubType.INVOKE, action=OperationAction.START, name=identifier.name, payload=payload, - invoke_options=invoke_options, + chained_invoke_options=chained_invoke_options, ) # endregion invoke @@ -657,7 +678,7 @@ class Operation: step_details: StepDetails | None = None wait_details: WaitDetails | None = None callback_details: CallbackDetails | None = None - invoke_details: InvokeDetails | None = None + chained_invoke_details: ChainedInvokeDetails | None = None @classmethod def from_dict(cls, data: MutableMapping[str, Any]) -> Operation: @@ -696,9 +717,11 @@ def from_dict(cls, data: MutableMapping[str, Any]) -> Operation: if callback_details_input := data.get("CallbackDetails"): callback_details = CallbackDetails.from_dict(callback_details_input) - invoke_details = None - if invoke_details_input := data.get("InvokeDetails"): - invoke_details = InvokeDetails.from_dict(invoke_details_input) + chained_invoke_details = None + if chained_invoke_details := data.get("chained_invoke_details"): + chained_invoke_details = ChainedInvokeDetails.from_dict( + chained_invoke_details + ) return cls( operation_id=data["Id"], @@ -714,7 +737,7 @@ def from_dict(cls, data: MutableMapping[str, Any]) -> Operation: step_details=step_details, wait_details=wait_details, callback_details=callback_details, - invoke_details=invoke_details, + chained_invoke_details=chained_invoke_details, ) def to_dict(self) -> MutableMapping[str, Any]: @@ -763,15 +786,13 @@ def to_dict(self) -> MutableMapping[str, Any]: if self.callback_details.error: callback_dict["Error"] = self.callback_details.error.to_dict() result["CallbackDetails"] = callback_dict - if self.invoke_details: - invoke_dict: MutableMapping[str, Any] = { - "DurableExecutionArn": self.invoke_details.durable_execution_arn - } - if self.invoke_details.result: - invoke_dict["Result"] = self.invoke_details.result - if self.invoke_details.error: - invoke_dict["Error"] = self.invoke_details.error.to_dict() - result["InvokeDetails"] = invoke_dict + if self.chained_invoke_details: + invoke_dict: MutableMapping[str, Any] = {} + if self.chained_invoke_details.result: + invoke_dict["Result"] = self.chained_invoke_details.result + if self.chained_invoke_details.error: + invoke_dict["Error"] = self.chained_invoke_details.error.to_dict() + result["ChainedInvokeDetails"] = invoke_dict return result diff --git a/src/aws_durable_execution_sdk_python/operation/invoke.py b/src/aws_durable_execution_sdk_python/operation/invoke.py index 3002d07..fa1ff78 100644 --- a/src/aws_durable_execution_sdk_python/operation/invoke.py +++ b/src/aws_durable_execution_sdk_python/operation/invoke.py @@ -10,7 +10,7 @@ FatalError, ) from aws_durable_execution_sdk_python.lambda_service import ( - InvokeOptions, + ChainedInvokeOptions, OperationUpdate, ) from aws_durable_execution_sdk_python.serdes import deserialize, serialize @@ -50,12 +50,12 @@ def invoke_handler( # Return persisted result - no need to check for errors in successful operations if ( checkpointed_result.operation - and checkpointed_result.operation.invoke_details - and checkpointed_result.operation.invoke_details.result + and checkpointed_result.operation.chained_invoke_details + and checkpointed_result.operation.chained_invoke_details.result ): return deserialize( serdes=config.serdes_result, - data=checkpointed_result.operation.invoke_details.result, + data=checkpointed_result.operation.chained_invoke_details.result, operation_id=operation_identifier.operation_id, durable_execution_arn=state.durable_execution_arn, ) @@ -85,7 +85,7 @@ def invoke_handler( start_operation: OperationUpdate = OperationUpdate.create_invoke_start( identifier=operation_identifier, payload=serialized_payload, - invoke_options=InvokeOptions( + chained_invoke_options=ChainedInvokeOptions( function_name=function_name, timeout_seconds=config.timeout_seconds ), ) diff --git a/src/aws_durable_execution_sdk_python/state.py b/src/aws_durable_execution_sdk_python/state.py index eb704b9..d529b1d 100644 --- a/src/aws_durable_execution_sdk_python/state.py +++ b/src/aws_durable_execution_sdk_python/state.py @@ -59,8 +59,8 @@ def create_from_operation(cls, operation: Operation) -> CheckpointedResult: result = callback_details.result if callback_details else None error = callback_details.error if callback_details else None - case OperationType.INVOKE: - invoke_details = operation.invoke_details + case OperationType.CHAINED_INVOKE: + invoke_details = operation.chained_invoke_details result = invoke_details.result if invoke_details else None error = invoke_details.error if invoke_details else None diff --git a/tests/lambda_service_test.py b/tests/lambda_service_test.py index 63c67bf..ae70530 100644 --- a/tests/lambda_service_test.py +++ b/tests/lambda_service_test.py @@ -13,6 +13,8 @@ from aws_durable_execution_sdk_python.lambda_service import ( CallbackDetails, CallbackOptions, + ChainedInvokeDetails, + ChainedInvokeOptions, CheckpointOutput, CheckpointUpdatedExecutionState, ContextDetails, @@ -20,8 +22,6 @@ DurableServiceClient, ErrorObject, ExecutionDetails, - InvokeDetails, - InvokeOptions, LambdaClient, Operation, OperationAction, @@ -330,42 +330,35 @@ def test_callback_details_minimal(): def test_invoke_details_from_dict(): - """Test InvokeDetails.from_dict method.""" + """Test ChainedInvokeDetails.from_dict method.""" error_data = {"ErrorMessage": "Invoke error"} data = { - "DurableExecutionArn": "arn:test", "Result": "invoke_result", "Error": error_data, } - details = InvokeDetails.from_dict(data) - assert details.durable_execution_arn == "arn:test" + details = ChainedInvokeDetails.from_dict(data) assert details.result == "invoke_result" assert details.error.message == "Invoke error" def test_invoke_details_all_fields(): - """Test InvokeDetails.from_dict with all fields.""" + """Test ChainedInvokeDetails.from_dict with all fields.""" error_data = {"ErrorMessage": "Invoke failed", "ErrorType": "InvokeError"} data = { - "DurableExecutionArn": "arn:aws:lambda:us-west-2:123456789012:function:test", "Result": "invoke_success", "Error": error_data, } - details = InvokeDetails.from_dict(data) - assert ( - details.durable_execution_arn - == "arn:aws:lambda:us-west-2:123456789012:function:test" - ) + details = ChainedInvokeDetails.from_dict(data) assert details.result == "invoke_success" assert details.error.message == "Invoke failed" assert details.error.type == "InvokeError" def test_invoke_details_minimal(): - """Test InvokeDetails.from_dict with minimal required data.""" + """Test ChainedInvokeDetails.from_dict with minimal required data.""" data = {"DurableExecutionArn": "arn:minimal"} - details = InvokeDetails.from_dict(data) - assert details.durable_execution_arn == "arn:minimal" + details = ChainedInvokeDetails.from_dict(data) + assert hasattr(details, "durable_execution_arn") is False assert details.result is None assert details.error is None @@ -405,17 +398,17 @@ def test_callback_options_from_dict_partial(): def test_invoke_options_from_dict(): - """Test InvokeOptions.from_dict method.""" + """Test ChainedInvokeOptions.from_dict method.""" data = {"FunctionName": "test-function", "TimeoutSeconds": 120} - options = InvokeOptions.from_dict(data) + options = ChainedInvokeOptions.from_dict(data) assert options.function_name == "test-function" assert options.timeout_seconds == 120 def test_invoke_options_from_dict_required_only(): - """Test InvokeOptions.from_dict with only required field.""" + """Test ChainedInvokeOptions.from_dict with only required field.""" data = {"FunctionName": "test-function"} - options = InvokeOptions.from_dict(data) + options = ChainedInvokeOptions.from_dict(data) assert options.function_name == "test-function" assert options.timeout_seconds == 0 @@ -450,10 +443,10 @@ def test_callback_options_roundtrip(): def test_invoke_options_roundtrip(): - """Test InvokeOptions to_dict -> from_dict roundtrip.""" - original = InvokeOptions(function_name="test-func", timeout_seconds=120) + """Test ChainedInvokeOptions to_dict -> from_dict roundtrip.""" + original = ChainedInvokeOptions(function_name="test-func", timeout_seconds=120) data = original.to_dict() - restored = InvokeOptions.from_dict(data) + restored = ChainedInvokeOptions.from_dict(data) assert restored == original @@ -502,8 +495,8 @@ def test_callback_options_all_fields(): def test_invoke_options_to_dict(): - """Test InvokeOptions.to_dict method.""" - options = InvokeOptions( + """Test ChainedInvokeOptions.to_dict method.""" + options = ChainedInvokeOptions( function_name="test_function", timeout_seconds=30, ) @@ -516,8 +509,8 @@ def test_invoke_options_to_dict(): def test_invoke_options_to_dict_minimal(): - """Test InvokeOptions.to_dict with minimal fields.""" - options = InvokeOptions(function_name="test_function") + """Test ChainedInvokeOptions.to_dict with minimal fields.""" + options = ChainedInvokeOptions(function_name="test_function") result = options.to_dict() assert result == {"FunctionName": "test_function", "TimeoutSeconds": 0} @@ -544,16 +537,16 @@ def test_context_options_to_dict_false(): def test_invoke_options_from_dict_missing_function_name(): - """Test InvokeOptions.from_dict with missing required FunctionName.""" + """Test ChainedInvokeOptions.from_dict with missing required FunctionName.""" data = {"TimeoutSeconds": 60} with pytest.raises(KeyError): - InvokeOptions.from_dict(data) + ChainedInvokeOptions.from_dict(data) def test_invoke_options_to_dict_complete(): - """Test InvokeOptions.to_dict with all fields.""" - options = InvokeOptions(function_name="test_func", timeout_seconds=120) + """Test ChainedInvokeOptions.to_dict with all fields.""" + options = ChainedInvokeOptions(function_name="test_func", timeout_seconds=120) result = options.to_dict() @@ -569,7 +562,7 @@ def test_invoke_options_to_dict_complete(): def test_operation_update_create_invoke_start(): """Test OperationUpdate.create_invoke_start method to cover line 545.""" identifier = OperationIdentifier("test-id", "parent-id") - invoke_options = InvokeOptions("test-func", 120) + invoke_options = ChainedInvokeOptions("test-func", 120) update = OperationUpdate.create_invoke_start(identifier, "payload", invoke_options) assert update.operation_id == "test-id" @@ -616,7 +609,9 @@ def test_operation_update_to_dict_complete(): callback_options = CallbackOptions( timeout_seconds=300, heartbeat_timeout_seconds=60 ) - invoke_options = InvokeOptions(function_name="test_func", timeout_seconds=60) + chained_invoke_options = ChainedInvokeOptions( + function_name="test_func", timeout_seconds=60 + ) update = OperationUpdate( operation_id="op1", @@ -629,7 +624,7 @@ def test_operation_update_to_dict_complete(): step_options=step_options, wait_options=wait_options, callback_options=callback_options, - invoke_options=invoke_options, + chained_invoke_options=chained_invoke_options, ) result = update.to_dict() @@ -644,7 +639,7 @@ def test_operation_update_to_dict_complete(): "StepOptions": {"NextAttemptDelaySeconds": 30}, "WaitOptions": {"WaitSeconds": 60}, "CallbackOptions": {"TimeoutSeconds": 300, "HeartbeatTimeoutSeconds": 60}, - "InvokeOptions": {"FunctionName": "test_func", "TimeoutSeconds": 60}, + "ChainedInvokeOptions": {"FunctionName": "test_func", "TimeoutSeconds": 60}, } assert result == expected @@ -812,17 +807,17 @@ def test_operation_update_wait_and_invoke_types(): assert result["WaitOptions"]["WaitSeconds"] == 30 # Test INVOKE operation - invoke_options = InvokeOptions(function_name="test_func") + chained_invoke_options = ChainedInvokeOptions(function_name="test_func") invoke_update = OperationUpdate( operation_id="invoke_op", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, action=OperationAction.START, - invoke_options=invoke_options, + chained_invoke_options=chained_invoke_options, ) result = invoke_update.to_dict() - assert result["Type"] == "INVOKE" - assert result["InvokeOptions"]["FunctionName"] == "test_func" + assert result["Type"] == "CHAINED_INVOKE" + assert result["ChainedInvokeOptions"]["FunctionName"] == "test_func" def test_operation_update_create_wait(): @@ -841,16 +836,16 @@ def test_operation_update_create_wait(): def test_operation_update_create_invoke(): """Test OperationUpdate factory method for INVOKE operations.""" - invoke_options = InvokeOptions(function_name="test-function") + chained_invoke_options = ChainedInvokeOptions(function_name="test-function") update = OperationUpdate( operation_id="invoke1", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, action=OperationAction.START, - invoke_options=invoke_options, + chained_invoke_options=chained_invoke_options, ) - assert update.operation_type == OperationType.INVOKE - assert update.invoke_options == invoke_options + assert update.operation_type == OperationType.CHAINED_INVOKE + assert update.chained_invoke_options == chained_invoke_options def test_operation_update_with_sub_type(): @@ -889,7 +884,9 @@ def test_operation_update_complete_with_new_fields(): callback_options = CallbackOptions( timeout_seconds=300, heartbeat_timeout_seconds=60 ) - invoke_options = InvokeOptions(function_name="test_func", timeout_seconds=60) + chained_invoke_options = ChainedInvokeOptions( + function_name="test_func", timeout_seconds=60 + ) update = OperationUpdate( operation_id="op1", @@ -904,7 +901,7 @@ def test_operation_update_complete_with_new_fields(): step_options=step_options, wait_options=wait_options, callback_options=callback_options, - invoke_options=invoke_options, + chained_invoke_options=chained_invoke_options, ) result = update.to_dict() @@ -921,7 +918,7 @@ def test_operation_update_complete_with_new_fields(): "StepOptions": {"NextAttemptDelaySeconds": 30}, "WaitOptions": {"WaitSeconds": 60}, "CallbackOptions": {"TimeoutSeconds": 300, "HeartbeatTimeoutSeconds": 60}, - "InvokeOptions": {"FunctionName": "test_func", "TimeoutSeconds": 60}, + "ChainedInvokeOptions": {"FunctionName": "test_func", "TimeoutSeconds": 60}, } assert result == expected @@ -1078,7 +1075,7 @@ def test_operation_update_from_dict_with_all_options(): "StepOptions": {"NextAttemptDelaySeconds": 30}, "WaitOptions": {"WaitSeconds": 60}, "CallbackOptions": {"TimeoutSeconds": 300, "HeartbeatTimeoutSeconds": 60}, - "InvokeOptions": {"FunctionName": "test_func", "TimeoutSeconds": 120}, + "ChainedInvokeOptions": {"FunctionName": "test_func", "TimeoutSeconds": 120}, } update = OperationUpdate.from_dict(data) @@ -1089,7 +1086,7 @@ def test_operation_update_from_dict_with_all_options(): assert update.step_options is not None assert update.wait_options is not None assert update.callback_options is not None - assert update.invoke_options is not None + assert update.chained_invoke_options is not None # ============================================================================= @@ -1109,7 +1106,7 @@ def test_operation_from_dict_with_all_options(): "StepOptions": {"NextAttemptDelaySeconds": 30}, "WaitOptions": {"WaitSeconds": 60}, "CallbackOptions": {"TimeoutSeconds": 300, "HeartbeatTimeoutSeconds": 60}, - "InvokeOptions": {"FunctionName": "test-func", "TimeoutSeconds": 120}, + "ChainedInvokeOptions": {"FunctionName": "test-func", "TimeoutSeconds": 120}, } operation = Operation.from_dict(data) assert operation.operation_id == "test-id" @@ -1173,13 +1170,13 @@ def test_operation_from_dict_individual_options(): op4 = Operation.from_dict(data4) assert op4.operation_id == "test4" - # Test with just InvokeOptions + # Test with just ChainedInvokeOptions data5 = { "Id": "test5", "Type": "STEP", "Action": "START", "Status": "PENDING", - "InvokeOptions": {"FunctionName": "test-func"}, + "ChainedInvokeOptions": {"FunctionName": "test-func"}, } op5 = Operation.from_dict(data5) assert op5.operation_id == "test5" @@ -1195,7 +1192,7 @@ def test_operation_from_dict_with_all_option_types(): "StepOptions": {"NextAttemptDelaySeconds": 30}, "WaitOptions": {"WaitSeconds": 60}, "CallbackOptions": {"TimeoutSeconds": 300, "HeartbeatTimeoutSeconds": 60}, - "InvokeOptions": {"FunctionName": "test_func", "TimeoutSeconds": 120}, + "ChainedInvokeOptions": {"FunctionName": "test_func", "TimeoutSeconds": 120}, } operation = Operation.from_dict(data) @@ -1219,9 +1216,7 @@ def test_operation_to_dict_with_all_details(): callback_details = CallbackDetails( callback_id="cb123", result="callback_result", error=None ) - invoke_details = InvokeDetails( - durable_execution_arn="arn:test", result="invoke_result", error=None - ) + chained_invoke_details = ChainedInvokeDetails(result="invoke_result", error=None) operation = Operation( operation_id="test", @@ -1236,7 +1231,7 @@ def test_operation_to_dict_with_all_details(): step_details=step_details, wait_details=wait_details, callback_details=callback_details, - invoke_details=invoke_details, + chained_invoke_details=chained_invoke_details, sub_type=OperationSubType.STEP, ) @@ -1249,7 +1244,7 @@ def test_operation_to_dict_with_all_details(): 2023, 1, 1, tzinfo=datetime.UTC ) assert result["CallbackDetails"]["CallbackId"] == "cb123" - assert result["InvokeDetails"]["DurableExecutionArn"] == "arn:test" + assert result["ChainedInvokeDetails"]["Result"] == "invoke_result" def test_operation_to_dict_with_step_details_partial(): @@ -1293,20 +1288,17 @@ def test_operation_to_dict_with_callback_details_partial(): def test_operation_to_dict_with_invoke_details_partial(): """Test Operation.to_dict with invoke_details having some None fields.""" - invoke_details = InvokeDetails( - durable_execution_arn="arn:test", result=None, error=None - ) + chained_invoke_details = ChainedInvokeDetails(result=None, error=None) operation = Operation( operation_id="test", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.PENDING, - invoke_details=invoke_details, + chained_invoke_details=chained_invoke_details, ) result = operation.to_dict() - invoke_dict = result["InvokeDetails"] - assert invoke_dict["DurableExecutionArn"] == "arn:test" + invoke_dict = result["ChainedInvokeDetails"] assert "Result" not in invoke_dict assert "Error" not in invoke_dict @@ -1392,23 +1384,21 @@ def test_operation_to_dict_with_callback_details_error(): def test_operation_to_dict_with_invoke_details_error(): - """Test Operation.to_dict with invoke_details having error.""" + """Test Operation.to_dict with chained_invoke_details having error.""" error = ErrorObject( message="Invoke failed", type="InvokeError", data=None, stack_trace=None ) - invoke_details = InvokeDetails( - durable_execution_arn="arn:test", result=None, error=error - ) + chained_invoke_details = ChainedInvokeDetails(result=None, error=error) operation = Operation( operation_id="test", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.FAILED, - invoke_details=invoke_details, + chained_invoke_details=chained_invoke_details, ) result = operation.to_dict() - invoke_dict = result["InvokeDetails"] + invoke_dict = result["ChainedInvokeDetails"] assert invoke_dict["Error"]["ErrorMessage"] == "Invoke failed" assert invoke_dict["Error"]["ErrorType"] == "InvokeError" @@ -1467,7 +1457,10 @@ def test_operation_from_dict_complete(): "StepDetails": {"Result": "step_result", "Attempt": 1}, "WaitDetails": {"ScheduledTimestamp": start_time}, "CallbackDetails": {"CallbackId": "cb1", "Result": "callback_result"}, - "InvokeDetails": {"DurableExecutionArn": "arn:test", "Result": "invoke_result"}, + "ChainedInvokeDetails": { + "DurableExecutionArn": "arn:test", + "Result": "invoke_result", + }, } operation = Operation.from_dict(data) assert operation.operation_id == "op1" @@ -1483,7 +1476,6 @@ def test_operation_from_dict_complete(): assert operation.step_details.result == "step_result" assert operation.wait_details.scheduled_timestamp == start_time assert operation.callback_details.callback_id == "cb1" - assert operation.invoke_details.durable_execution_arn == "arn:test" def test_operation_to_dict_with_subtype(): diff --git a/tests/operation/invoke_test.py b/tests/operation/invoke_test.py index fe4eaa5..738b2dd 100644 --- a/tests/operation/invoke_test.py +++ b/tests/operation/invoke_test.py @@ -16,8 +16,8 @@ ) from aws_durable_execution_sdk_python.identifier import OperationIdentifier from aws_durable_execution_sdk_python.lambda_service import ( + ChainedInvokeDetails, ErrorObject, - InvokeDetails, Operation, OperationAction, OperationStatus, @@ -38,11 +38,9 @@ def test_invoke_handler_already_succeeded(): operation = Operation( operation_id="invoke1", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.SUCCEEDED, - invoke_details=InvokeDetails( - durable_execution_arn="invoked_arn", result=json.dumps("test_result") - ), + chained_invoke_details=ChainedInvokeDetails(result=json.dumps("test_result")), ) mock_result = CheckpointedResult.create_from_operation(operation) mock_state.get_checkpoint_result.return_value = mock_result @@ -66,9 +64,9 @@ def test_invoke_handler_already_succeeded_none_result(): operation = Operation( operation_id="invoke2", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.SUCCEEDED, - invoke_details=InvokeDetails(durable_execution_arn="invoked_arn", result=None), + chained_invoke_details=ChainedInvokeDetails(result=None), ) mock_result = CheckpointedResult.create_from_operation(operation) mock_state.get_checkpoint_result.return_value = mock_result @@ -84,16 +82,16 @@ def test_invoke_handler_already_succeeded_none_result(): assert result is None -def test_invoke_handler_already_succeeded_no_invoke_details(): - """Test invoke_handler when operation succeeded but has no invoke_details.""" +def test_invoke_handler_already_succeeded_no_chained_invoke_details(): + """Test invoke_handler when operation succeeded but has no chained_invoke_details.""" mock_state = Mock(spec=ExecutionState) mock_state.durable_execution_arn = "test_arn" operation = Operation( operation_id="invoke3", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.SUCCEEDED, - invoke_details=None, + chained_invoke_details=None, ) mock_result = CheckpointedResult.create_from_operation(operation) mock_state.get_checkpoint_result.return_value = mock_result @@ -119,9 +117,9 @@ def test_invoke_handler_already_failed(): ) operation = Operation( operation_id="invoke4", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.FAILED, - invoke_details=InvokeDetails(durable_execution_arn="invoked_arn", error=error), + chained_invoke_details=ChainedInvokeDetails(error=error), ) mock_result = CheckpointedResult.create_from_operation(operation) mock_state.get_checkpoint_result.return_value = mock_result @@ -146,9 +144,9 @@ def test_invoke_handler_already_timed_out(): ) operation = Operation( operation_id="invoke5", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.TIMED_OUT, - invoke_details=InvokeDetails(durable_execution_arn="invoked_arn", error=error), + chained_invoke_details=ChainedInvokeDetails(error=error), ) mock_result = CheckpointedResult.create_from_operation(operation) mock_state.get_checkpoint_result.return_value = mock_result @@ -170,9 +168,9 @@ def test_invoke_handler_already_started(): operation = Operation( operation_id="invoke6", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.STARTED, - invoke_details=InvokeDetails(durable_execution_arn="invoked_arn"), + chained_invoke_details=ChainedInvokeDetails(), ) mock_result = CheckpointedResult.create_from_operation(operation) mock_state.get_checkpoint_result.return_value = mock_result @@ -194,9 +192,9 @@ def test_invoke_handler_already_started_with_timeout(): operation = Operation( operation_id="invoke7", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.STARTED, - invoke_details=InvokeDetails(durable_execution_arn="invoked_arn"), + chained_invoke_details=ChainedInvokeDetails(), ) mock_result = CheckpointedResult.create_from_operation(operation) mock_state.get_checkpoint_result.return_value = mock_result @@ -239,12 +237,12 @@ def test_invoke_handler_new_operation(): operation_update = mock_state.create_checkpoint.call_args[1]["operation_update"] assert operation_update.operation_id == "invoke8" - assert operation_update.operation_type == OperationType.INVOKE + assert operation_update.operation_type == OperationType.CHAINED_INVOKE assert operation_update.action == OperationAction.START assert operation_update.name == "test_invoke" assert operation_update.payload == json.dumps("test_input") - assert operation_update.invoke_options.function_name == "test_function" - assert operation_update.invoke_options.timeout_seconds == 60 + assert operation_update.chained_invoke_options.function_name == "test_function" + assert operation_update.chained_invoke_options.timeout_seconds == 60 def test_invoke_handler_new_operation_with_timeout(): @@ -306,7 +304,7 @@ def test_invoke_handler_no_config(): # Verify default config was used operation_update = mock_state.create_checkpoint.call_args[1]["operation_update"] - assert operation_update.invoke_options.timeout_seconds == 0 + assert operation_update.chained_invoke_options.timeout_seconds == 0 def test_invoke_handler_custom_serdes(): @@ -316,10 +314,9 @@ def test_invoke_handler_custom_serdes(): operation = Operation( operation_id="invoke12", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.SUCCEEDED, - invoke_details=InvokeDetails( - durable_execution_arn="invoked_arn", + chained_invoke_details=ChainedInvokeDetails( result='{"key": "VALUE", "number": "84", "list": [1, 2, 3]}', ), ) @@ -409,9 +406,9 @@ def test_invoke_handler_with_operation_name(): operation = Operation( operation_id="invoke14", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.STARTED, - invoke_details=InvokeDetails(durable_execution_arn="invoked_arn"), + chained_invoke_details=ChainedInvokeDetails(), ) mock_result = CheckpointedResult.create_from_operation(operation) mock_state.get_checkpoint_result.return_value = mock_result @@ -433,9 +430,9 @@ def test_invoke_handler_without_operation_name(): operation = Operation( operation_id="invoke15", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.STARTED, - invoke_details=InvokeDetails(durable_execution_arn="invoked_arn"), + chained_invoke_details=ChainedInvokeDetails(), ) mock_result = CheckpointedResult.create_from_operation(operation) mock_state.get_checkpoint_result.return_value = mock_result @@ -480,11 +477,9 @@ def test_invoke_handler_already_succeeded_with_none_payload(): operation = Operation( operation_id="invoke17", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.SUCCEEDED, - invoke_details=InvokeDetails( - durable_execution_arn="invoked_arn", result=json.dumps("test_result") - ), + chained_invoke_details=ChainedInvokeDetails(result=json.dumps("test_result")), ) mock_result = CheckpointedResult.create_from_operation(operation) mock_state.get_checkpoint_result.return_value = mock_result diff --git a/tests/state_test.py b/tests/state_test.py index a5fdbc3..3ace94e 100644 --- a/tests/state_test.py +++ b/tests/state_test.py @@ -7,10 +7,10 @@ from aws_durable_execution_sdk_python.exceptions import DurableExecutionsError from aws_durable_execution_sdk_python.lambda_service import ( CallbackDetails, + ChainedInvokeDetails, CheckpointOutput, CheckpointUpdatedExecutionState, ErrorObject, - InvokeDetails, LambdaClient, Operation, OperationAction, @@ -57,14 +57,12 @@ def test_checkpointed_result_create_from_operation_callback(): def test_checkpointed_result_create_from_operation_invoke(): """Test CheckpointedResult.create_from_operation with INVOKE operation.""" - invoke_details = InvokeDetails( - durable_execution_arn="arn:test", result="invoke_result" - ) + chained_invoke_details = ChainedInvokeDetails(result="invoke_result") operation = Operation( operation_id="op1", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.SUCCEEDED, - invoke_details=invoke_details, + chained_invoke_details=chained_invoke_details, ) result = CheckpointedResult.create_from_operation(operation) assert result.operation == operation @@ -78,12 +76,12 @@ def test_checkpointed_result_create_from_operation_invoke_with_error(): error = ErrorObject( message="Invoke error", type="InvokeError", data=None, stack_trace=None ) - invoke_details = InvokeDetails(durable_execution_arn="arn:test", error=error) + chained_invoke_details = ChainedInvokeDetails(error=error) operation = Operation( operation_id="op1", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.FAILED, - invoke_details=invoke_details, + chained_invoke_details=chained_invoke_details, ) result = CheckpointedResult.create_from_operation(operation) assert result.operation == operation @@ -93,10 +91,10 @@ def test_checkpointed_result_create_from_operation_invoke_with_error(): def test_checkpointed_result_create_from_operation_invoke_no_details(): - """Test CheckpointedResult.create_from_operation with INVOKE operation but no invoke_details.""" + """Test CheckpointedResult.create_from_operation with INVOKE operation but no chained_invoke_details.""" operation = Operation( operation_id="op1", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.STARTED, ) result = CheckpointedResult.create_from_operation(operation) @@ -111,14 +109,12 @@ def test_checkpointed_result_create_from_operation_invoke_with_both_result_and_e error = ErrorObject( message="Invoke error", type="InvokeError", data=None, stack_trace=None ) - invoke_details = InvokeDetails( - durable_execution_arn="arn:test", result="invoke_result", error=error - ) + chained_invoke_details = ChainedInvokeDetails(result="invoke_result", error=error) operation = Operation( operation_id="op1", - operation_type=OperationType.INVOKE, + operation_type=OperationType.CHAINED_INVOKE, status=OperationStatus.FAILED, - invoke_details=invoke_details, + chained_invoke_details=chained_invoke_details, ) result = CheckpointedResult.create_from_operation(operation) assert result.operation == operation