diff --git a/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/execution_plugin.py b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/execution_plugin.py index bc829db6..0a511a04 100644 --- a/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/execution_plugin.py +++ b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/execution_plugin.py @@ -25,16 +25,14 @@ import threading from typing import Any -from aws_durable_execution_sdk_python.lambda_service import ( - InvocationStatus, - OperationType, -) from aws_durable_execution_sdk_python.plugin import ( DurableInstrumentationPlugin, InvocationEndInfo, + InvocationStatus, InvocationStartInfo, OperationEndInfo, OperationStartInfo, + OperationType, UserFunctionEndInfo, UserFunctionOutcome, UserFunctionStartInfo, @@ -521,7 +519,7 @@ def _operation_attributes(self, info: Any) -> dict[str, Any]: # STEP user-function spans represent attempts, not durable operations. if ( not ( - isinstance(info, (UserFunctionStartInfo, UserFunctionEndInfo)) + hasattr(info, "is_replay_children") and info.operation_type is OperationType.STEP ) and getattr(info, "status", None) is not None diff --git a/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/invocation_plugin.py b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/invocation_plugin.py index 4f42ca32..ec09a170 100644 --- a/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/invocation_plugin.py +++ b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/invocation_plugin.py @@ -7,16 +7,14 @@ import threading from typing import Any -from aws_durable_execution_sdk_python.lambda_service import ( - InvocationStatus, - OperationType, -) from aws_durable_execution_sdk_python.plugin import ( DurableInstrumentationPlugin, InvocationEndInfo, + InvocationStatus, InvocationStartInfo, OperationEndInfo, OperationStartInfo, + OperationType, UserFunctionEndInfo, UserFunctionOutcome, UserFunctionStartInfo, @@ -634,7 +632,7 @@ def _extract_attributes(self, info: Any) -> _SpanAttributes: "durable.execution.arn": self._execution_arn, } - if isinstance(info, InvocationStartInfo): + if hasattr(info, "is_first_invocation"): attributes["durable.invocation.first"] = info.is_first_invocation if hasattr(info, "operation_id") and info.operation_id is not None: attributes["durable.operation.id"] = info.operation_id @@ -645,7 +643,7 @@ def _extract_attributes(self, info: Any) -> _SpanAttributes: # STEP user-function spans represent attempts, not durable operations. if ( not ( - isinstance(info, (UserFunctionStartInfo, UserFunctionEndInfo)) + hasattr(info, "is_replay_children") and info.operation_type is OperationType.STEP ) and hasattr(info, "status") diff --git a/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin.py b/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin.py index 75488662..8f3247b8 100644 --- a/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin.py +++ b/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin.py @@ -3,21 +3,22 @@ from __future__ import annotations from datetime import UTC, datetime +from types import SimpleNamespace import opentelemetry.context as otel_context import pytest from aws_durable_execution_sdk_python.lambda_service import ( ErrorObject, - InvocationStatus, OperationStatus, OperationSubType, - OperationType, ) from aws_durable_execution_sdk_python.plugin import ( InvocationEndInfo, + InvocationStatus, InvocationStartInfo, OperationEndInfo, OperationStartInfo, + OperationType, UserFunctionEndInfo, UserFunctionOutcome, UserFunctionStartInfo, @@ -96,6 +97,30 @@ def _invocation_end_info( ) +def test_operation_attributes_use_structural_user_function_marker(): + plugin, _ = _create_plugin() + + operation_attributes = plugin._operation_attributes( + SimpleNamespace( + operation_type=OperationType.STEP, + status=OperationStatus.STARTED, + ) + ) + assert ( + operation_attributes["durable.operation.status"] + == OperationStatus.STARTED.value + ) + + user_function_attributes = plugin._operation_attributes( + SimpleNamespace( + operation_type=OperationType.STEP, + status=OperationStatus.STARTED, + is_replay_children=False, + ) + ) + assert "durable.operation.status" not in user_function_attributes + + # --------------------------------------------------------------------------- # derive_workflow_span_id # --------------------------------------------------------------------------- diff --git a/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin_integration.py b/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin_integration.py index 98f450c5..5db9a5c2 100644 --- a/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin_integration.py +++ b/packages/aws-durable-execution-sdk-python-otel/tests/test_execution_plugin_integration.py @@ -17,16 +17,16 @@ import opentelemetry.context as otel_context import pytest from aws_durable_execution_sdk_python.lambda_service import ( - InvocationStatus, OperationStatus, OperationSubType, - OperationType, ) from aws_durable_execution_sdk_python.plugin import ( InvocationEndInfo, + InvocationStatus, InvocationStartInfo, OperationEndInfo, OperationStartInfo, + OperationType, UserFunctionEndInfo, UserFunctionOutcome, UserFunctionStartInfo, diff --git a/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin.py b/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin.py index d6cf063a..3e409a4a 100644 --- a/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin.py +++ b/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin.py @@ -5,21 +5,22 @@ import time from concurrent.futures import ThreadPoolExecutor from datetime import UTC, datetime +from types import SimpleNamespace import opentelemetry.context as otel_context import pytest from aws_durable_execution_sdk_python.lambda_service import ( ErrorObject, - InvocationStatus, OperationStatus, OperationSubType, - OperationType, ) from aws_durable_execution_sdk_python.plugin import ( InvocationEndInfo, + InvocationStatus, InvocationStartInfo, OperationEndInfo, OperationStartInfo, + OperationType, UserFunctionEndInfo, UserFunctionOutcome, UserFunctionStartInfo, @@ -148,6 +149,35 @@ def _user_function_end_info( ) +def test_extract_attributes_uses_structural_event_attributes(): + plugin, _ = _create_plugin() + + invocation_attributes = plugin._extract_attributes( + SimpleNamespace(is_first_invocation=False) + ) + assert invocation_attributes["durable.invocation.first"] is False + + operation_attributes = plugin._extract_attributes( + SimpleNamespace( + operation_type=OperationType.STEP, + status=OperationStatus.STARTED, + ) + ) + assert ( + operation_attributes["durable.operation.status"] + == OperationStatus.STARTED.value + ) + + user_function_attributes = plugin._extract_attributes( + SimpleNamespace( + operation_type=OperationType.STEP, + status=OperationStatus.STARTED, + is_replay_children=False, + ) + ) + assert "durable.operation.status" not in user_function_attributes + + def test_invocation_start_and_end_emit_invocation_span(): """Verify invocation lifecycle callbacks create and finish the root span.""" plugin, exporter = _create_plugin() diff --git a/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin_integration.py b/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin_integration.py index c1c6d4b1..0662926c 100644 --- a/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin_integration.py +++ b/packages/aws-durable-execution-sdk-python-otel/tests/test_invocation_plugin_integration.py @@ -20,16 +20,16 @@ import opentelemetry.context as otel_context import pytest from aws_durable_execution_sdk_python.lambda_service import ( - InvocationStatus, OperationStatus, OperationSubType, - OperationType, ) from aws_durable_execution_sdk_python.plugin import ( InvocationEndInfo, + InvocationStatus, InvocationStartInfo, OperationEndInfo, OperationStartInfo, + OperationType, UserFunctionEndInfo, UserFunctionOutcome, UserFunctionStartInfo, diff --git a/packages/aws-durable-execution-sdk-python-otel/tests/test_log_filter.py b/packages/aws-durable-execution-sdk-python-otel/tests/test_log_filter.py index f419f9ad..36236c68 100644 --- a/packages/aws-durable-execution-sdk-python-otel/tests/test_log_filter.py +++ b/packages/aws-durable-execution-sdk-python-otel/tests/test_log_filter.py @@ -7,10 +7,10 @@ from aws_durable_execution_sdk_python.lambda_service import ( OperationStatus, - OperationType, ) from aws_durable_execution_sdk_python.plugin import ( InvocationStartInfo, + OperationType, UserFunctionStartInfo, ) from opentelemetry.context import Context diff --git a/packages/aws-durable-execution-sdk-python-otel/tests/test_package_metadata.py b/packages/aws-durable-execution-sdk-python-otel/tests/test_package_metadata.py index 678d6274..b3daf746 100644 --- a/packages/aws-durable-execution-sdk-python-otel/tests/test_package_metadata.py +++ b/packages/aws-durable-execution-sdk-python-otel/tests/test_package_metadata.py @@ -3,11 +3,35 @@ PACKAGE_ROOT = Path(__file__).resolve().parents[1] +REPOSITORY_ROOT = PACKAGE_ROOT.parents[1] +CORE_DEPENDENCY = "aws-durable-execution-sdk-python>=1.8.0" + + +def _load_pyproject(path: Path) -> dict: + with path.open("rb") as pyproject: + return tomllib.load(pyproject) def test_package_is_marked_production_stable() -> None: - with (PACKAGE_ROOT / "pyproject.toml").open("rb") as pyproject: - classifiers = tomllib.load(pyproject)["project"]["classifiers"] + classifiers = _load_pyproject(PACKAGE_ROOT / "pyproject.toml")["project"][ + "classifiers" + ] assert "Development Status :: 5 - Production/Stable" in classifiers assert "Development Status :: 4 - Beta" not in classifiers + + +def test_package_requires_compatible_core_sdk() -> None: + dependencies = _load_pyproject(PACKAGE_ROOT / "pyproject.toml")["project"][ + "dependencies" + ] + + assert CORE_DEPENDENCY in dependencies + + +def test_pypi_compatibility_environment_uses_compatible_core_sdk() -> None: + dependencies = _load_pyproject(REPOSITORY_ROOT / "pyproject.toml")["tool"]["hatch"][ + "envs" + ]["test-pypi-otel"]["dependencies"] + + assert CORE_DEPENDENCY in dependencies diff --git a/packages/aws-durable-execution-sdk-python-testing/tests/e2e/wait_suspend_replay_test.py b/packages/aws-durable-execution-sdk-python-testing/tests/e2e/wait_suspend_replay_test.py index 755e4e16..41789171 100644 --- a/packages/aws-durable-execution-sdk-python-testing/tests/e2e/wait_suspend_replay_test.py +++ b/packages/aws-durable-execution-sdk-python-testing/tests/e2e/wait_suspend_replay_test.py @@ -20,12 +20,12 @@ ) from aws_durable_execution_sdk_python.lambda_service import ( OperationStatus, - OperationType, ) from aws_durable_execution_sdk_python.plugin import ( DurableInstrumentationPlugin, InvocationStartInfo, OperationEndInfo, + OperationType, ) from aws_durable_execution_sdk_python_testing.runner import ( diff --git a/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/plugin.py b/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/plugin.py index ffc5a65f..daab80cc 100644 --- a/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/plugin.py +++ b/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/plugin.py @@ -15,12 +15,12 @@ from aws_durable_execution_sdk_python.lambda_service import ( DurableExecutionInvocationOutput, ErrorObject, - InvocationStatus, + InvocationStatus as ServiceInvocationStatus, Operation, OperationAction, OperationStatus, OperationSubType, - OperationType, + OperationType as ServiceOperationType, OperationUpdate, ) from aws_durable_execution_sdk_python.types import LambdaContext @@ -31,6 +31,34 @@ DURABLE_INSTRUMENTATION_PLUGIN_API_VERSION = 1 +class InvocationStatus(Enum): + """Invocation outcomes exposed to instrumentation plugins.""" + + SUCCEEDED = "SUCCEEDED" + FAILED = "FAILED" + PENDING = "PENDING" + RETRY = "RETRY" + + +class OperationType(Enum): + """Durable operation categories exposed to instrumentation plugins.""" + + EXECUTION = "EXECUTION" + CONTEXT = "CONTEXT" + STEP = "STEP" + WAIT = "WAIT" + CALLBACK = "CALLBACK" + CHAINED_INVOKE = "CHAINED_INVOKE" + + +def _to_invocation_status(status: ServiceInvocationStatus) -> InvocationStatus: + return InvocationStatus(status.value) + + +def _to_operation_type(operation_type: ServiceOperationType) -> OperationType: + return OperationType(operation_type.value) + + def _extract_result(operation: Operation) -> str | None: if operation.step_details and operation.step_details.result is not None: return operation.step_details.result @@ -91,7 +119,7 @@ def from_operation( ) -> OperationInfo: return OperationInfo( operation_id=operation.operation_id, - operation_type=operation.operation_type, + operation_type=_to_operation_type(operation.operation_type), sub_type=operation.sub_type, name=operation.name, parent_id=operation.parent_id, @@ -338,7 +366,7 @@ def from_durable_execution_invocation_output( if operations is not None else invocation_start_info.operations ), - status=output.status, + status=_to_invocation_status(output.status), error=output.error, execution_result=output.result, ) @@ -609,7 +637,7 @@ def on_user_function_start( """Execute any registered plugins for the operation when its user function starts to execute.""" start_info = UserFunctionStartInfo( operation_id=operation_identifier.operation_id, - operation_type=operation_identifier.type, + operation_type=_to_operation_type(operation_identifier.type), sub_type=operation_identifier.sub_type, name=operation_identifier.name, parent_id=operation_identifier.parent_id, @@ -647,7 +675,7 @@ def on_operation_action( self.execute_plugins( OperationStartInfo( operation_id=update.operation_id, - operation_type=update.operation_type, + operation_type=_to_operation_type(update.operation_type), sub_type=update.sub_type, name=update.name, parent_id=update.parent_id, @@ -665,7 +693,7 @@ def on_operation_replay(self, operation: Operation) -> None: start_info = OperationStartInfo( operation_id=operation.operation_id, - operation_type=operation.operation_type, + operation_type=_to_operation_type(operation.operation_type), sub_type=operation.sub_type, name=operation.name, parent_id=operation.parent_id, @@ -688,7 +716,7 @@ def on_child_context_end( self.execute_plugins( OperationEndInfo( operation_id=operation_identifier.operation_id, - operation_type=operation_identifier.type, + operation_type=_to_operation_type(operation_identifier.type), sub_type=operation_identifier.sub_type, name=operation_identifier.name, parent_id=operation_identifier.parent_id, @@ -732,7 +760,7 @@ def on_operation_update( self.execute_plugins( OperationEndInfo( operation_id=operation.operation_id, - operation_type=operation.operation_type, + operation_type=_to_operation_type(operation.operation_type), sub_type=operation.sub_type, name=operation.name, parent_id=operation.parent_id, diff --git a/packages/aws-durable-execution-sdk-python/tests/plugin_test.py b/packages/aws-durable-execution-sdk-python/tests/plugin_test.py index c33c370a..b693ab10 100644 --- a/packages/aws-durable-execution-sdk-python/tests/plugin_test.py +++ b/packages/aws-durable-execution-sdk-python/tests/plugin_test.py @@ -10,23 +10,25 @@ from aws_durable_execution_sdk_python.lambda_service import ( DurableExecutionInvocationOutput, ErrorObject, - InvocationStatus, + InvocationStatus as ServiceInvocationStatus, Operation, OperationAction, OperationStatus, OperationSubType, - OperationType, + OperationType as ServiceOperationType, StepDetails, ) from aws_durable_execution_sdk_python.plugin import ( DurableInstrumentationPlugin, InvocationEndInfo, InvocationInfo, + InvocationStatus, InvocationStartInfo, OperationChangeInfo, OperationEndInfo, OperationInfo, OperationStartInfo, + OperationType, PluginExecutor, UserFunctionEndInfo, UserFunctionOutcome, @@ -280,6 +282,41 @@ def test_payload_fields_are_declared_non_compare(self): self.assertFalse(holder[name].compare, name) self.assertIs(holder[name].hash, False, name) + def test_plugin_enums_are_independent_from_service_enums(self): + self.assertIsNot(InvocationStatus, ServiceInvocationStatus) + self.assertIsNot(OperationType, ServiceOperationType) + self.assertEqual( + {status.value for status in InvocationStatus}, + {status.value for status in ServiceInvocationStatus}, + ) + self.assertEqual( + {operation_type.value for operation_type in OperationType}, + {operation_type.value for operation_type in ServiceOperationType}, + ) + + def test_operation_info_converts_service_operation_type(self): + operation = Operation( + operation_id="wait-1", + operation_type=ServiceOperationType.WAIT, + status=OperationStatus.STARTED, + ) + + info = OperationInfo.from_operation(operation) + + self.assertIs(info.operation_type, OperationType.WAIT) + + def test_invocation_end_info_converts_service_invocation_status(self): + output = DurableExecutionInvocationOutput( + status=ServiceInvocationStatus.PENDING, + ) + + info = InvocationEndInfo.from_durable_execution_invocation_output( + INVOCATION_START_INFO, + output, + ) + + self.assertIs(info.status, InvocationStatus.PENDING) + def test_payload_fields_are_marked_experimental(self): plugin_info_types = ( OperationInfo, @@ -544,7 +581,7 @@ def test_on_invocation_end_is_safe_when_empty(self): is_first_invocation=False, ) output = DurableExecutionInvocationOutput( - status=InvocationStatus.SUCCEEDED, result=None, error=None + status=ServiceInvocationStatus.SUCCEEDED, result=None, error=None ) # Should not raise @@ -557,7 +594,7 @@ def test_on_operation_action_is_safe_when_empty(self): update = MagicMock() update.action = OperationAction.START update.operation_id = "op-1" - update.operation_type = OperationType.STEP + update.operation_type = ServiceOperationType.STEP update.sub_type = OperationSubType.STEP update.name = "my-step" update.parent_id = None @@ -569,7 +606,7 @@ def test_on_operation_update_is_safe_when_empty(self): executor = PluginExecutor(plugins=[]) op = MagicMock() op.operation_id = "op-1" - op.operation_type = OperationType.STEP + op.operation_type = ServiceOperationType.STEP op.sub_type = OperationSubType.STEP op.name = "my-step" op.parent_id = None @@ -754,7 +791,7 @@ def _make_operation(self, start_ts=None, end_ts=None): def test_succeeded_fires_invocation_end(self): output = DurableExecutionInvocationOutput( - status=InvocationStatus.SUCCEEDED, result=None, error=None + status=ServiceInvocationStatus.SUCCEEDED, result=None, error=None ) with self.executor.run(): @@ -772,7 +809,7 @@ def test_succeeded_fires_invocation_end(self): def test_failed_fires_invocation_end(self): output = DurableExecutionInvocationOutput( - status=InvocationStatus.FAILED, result=None, error=ERROR + status=ServiceInvocationStatus.FAILED, result=None, error=ERROR ) with self.executor.run(): @@ -790,7 +827,7 @@ def test_failed_fires_invocation_end(self): def test_pending_fires_invocation_end(self): output = DurableExecutionInvocationOutput( - status=InvocationStatus.PENDING, result=None, error=None + status=ServiceInvocationStatus.PENDING, result=None, error=None ) with self.executor.run(): @@ -1259,7 +1296,7 @@ def on_operation_start(self, info: OperationStartInfo) -> None: update = MagicMock() update.action = OperationAction.START update.operation_id = "op-1" - update.operation_type = OperationType.STEP + update.operation_type = ServiceOperationType.STEP update.sub_type = OperationSubType.STEP update.name = "my-step" update.parent_id = "parent-1" @@ -1268,6 +1305,7 @@ def on_operation_start(self, info: OperationStartInfo) -> None: self.executor.on_operation_action(update) self.assertIn("operation_start:op-1", self.plugin.calls) + self.assertIs(captured[0].operation_type, OperationType.STEP) self.assertEqual(captured[0].status, OperationStatus.STARTED) self.assertFalse(captured[0].is_replayed) @@ -1284,14 +1322,14 @@ def on_operation_start(self, info: OperationStartInfo) -> None: update = MagicMock() update.action = OperationAction.START update.operation_id = "op-1" - update.operation_type = OperationType.STEP + update.operation_type = ServiceOperationType.STEP update.sub_type = OperationSubType.STEP update.name = "my-step" update.parent_id = "parent-1" operation = Operation( operation_id="op-1", - operation_type=OperationType.STEP, + operation_type=ServiceOperationType.STEP, status=OperationStatus.STARTED, start_timestamp=START_TS, ) @@ -1314,19 +1352,19 @@ def on_operation_start(self, info: OperationStartInfo) -> None: update = MagicMock() update.action = OperationAction.START update.operation_id = "op-1" - update.operation_type = OperationType.STEP + update.operation_type = ServiceOperationType.STEP update.sub_type = OperationSubType.STEP update.name = "my-step" update.parent_id = "parent-1" current_operation = Operation( operation_id="op-1", - operation_type=OperationType.STEP, + operation_type=ServiceOperationType.STEP, status=OperationStatus.STARTED, ) previous_operation = Operation( operation_id="op-1", - operation_type=OperationType.STEP, + operation_type=ServiceOperationType.STEP, status=OperationStatus.READY, ) @@ -1376,7 +1414,7 @@ def test_terminal_operation_does_not_fire_callbacks(self): executor = PluginExecutor(plugins=[plugin]) operation = Operation( operation_id="op-1", - operation_type=OperationType.STEP, + operation_type=ServiceOperationType.STEP, status=status, ) @@ -1390,7 +1428,7 @@ def test_non_terminal_operation_fires_operation_start(self): executor = PluginExecutor(plugins=[plugin]) operation = Operation( operation_id="op-1", - operation_type=OperationType.WAIT, + operation_type=ServiceOperationType.WAIT, status=OperationStatus.STARTED, ) @@ -1447,6 +1485,20 @@ def on_operation_end(self, info: OperationEndInfo) -> None: self.assertLessEqual(info.end_time, after) +class TestPluginExecutorOnUserFunction(unittest.TestCase): + def test_user_function_info_uses_plugin_operation_type(self): + executor = PluginExecutor(plugins=[_TrackingPlugin()]) + identifier = OperationIdentifier( + operation_id="step-1", + sub_type=OperationSubType.STEP, + ) + + with executor.run(): + info = executor.on_user_function_start(identifier) + + self.assertIs(info.operation_type, OperationType.STEP) + + class TestPluginExecutorOnOperationUpdate(unittest.TestCase): """Tests for PluginExecutor.on_operation_update.""" @@ -1464,7 +1516,7 @@ def _make_operation( ): op = MagicMock() op.operation_id = "op-1" - op.operation_type = OperationType.STEP + op.operation_type = ServiceOperationType.STEP op.sub_type = OperationSubType.STEP op.name = "my-step" op.parent_id = "parent-1" @@ -1536,7 +1588,7 @@ def setUp(self): def test_operation_change_uses_invocation_and_operation_maps(self): updated_operation = Operation( operation_id="op-1", - operation_type=OperationType.STEP, + operation_type=ServiceOperationType.STEP, status=OperationStatus.SUCCEEDED, name="my-step", parent_id="parent-1", @@ -1547,7 +1599,7 @@ def test_operation_change_uses_invocation_and_operation_maps(self): ) other_operation = Operation( operation_id="op-2", - operation_type=OperationType.WAIT, + operation_type=ServiceOperationType.WAIT, status=OperationStatus.STARTED, name="my-wait", sub_type=OperationSubType.WAIT, @@ -1585,6 +1637,7 @@ def on_operation_change(self, info: OperationChangeInfo) -> None: updated_info = captured[0].updated_operations["op-1"] self.assertIsInstance(updated_info, OperationInfo) + self.assertIs(updated_info.operation_type, OperationType.STEP) self.assertEqual(updated_info.status, OperationStatus.SUCCEEDED) self.assertEqual(updated_info.result, '"done"') self.assertEqual(updated_info.attempt, 2) @@ -1594,7 +1647,7 @@ def on_operation_change(self, info: OperationChangeInfo) -> None: def test_operation_change_without_invocation_start_is_noop(self): operation = Operation( operation_id="op-1", - operation_type=OperationType.STEP, + operation_type=ServiceOperationType.STEP, status=OperationStatus.STARTED, )