From fe50333fe6b503c351d985b786abd114b943d5b8 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Thu, 13 Aug 2026 03:37:26 +0000 Subject: [PATCH 1/4] feat(plugin): decouple instrumentation enums from lambda service --- .../execution_plugin.py | 8 +- .../invocation_plugin.py | 10 +- .../plugin_provider.py | 4 +- .../tests/test_execution_plugin.py | 29 +++++- .../test_execution_plugin_integration.py | 4 +- .../tests/test_invocation_plugin.py | 34 ++++++- .../test_invocation_plugin_integration.py | 4 +- .../tests/test_log_filter.py | 2 +- .../tests/e2e/wait_suspend_replay_test.py | 2 +- .../README.md | 2 +- .../plugin.py | 48 ++++++++-- .../tests/plugin_test.py | 93 +++++++++++++++---- 12 files changed, 186 insertions(+), 54 deletions(-) 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/src/aws_durable_execution_sdk_python_otel/plugin_provider.py b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/plugin_provider.py index c4be734c..e26b8a14 100644 --- a/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/plugin_provider.py +++ b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/plugin_provider.py @@ -13,11 +13,11 @@ INVOCATION_OTEL_PLUGIN_PROVIDER = DurableInstrumentationPluginProvider( plugin_type=InvocationOtelPlugin, factory=InvocationOtelPlugin, - plugin_api_version=1, + plugin_api_version=2, ) EXECUTION_OTEL_PLUGIN_PROVIDER = DurableInstrumentationPluginProvider( plugin_type=ExecutionOtelPlugin, factory=ExecutionOtelPlugin, - plugin_api_version=1, + plugin_api_version=2, ) 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-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/README.md b/packages/aws-durable-execution-sdk-python/README.md index bf7776f4..13377dfc 100644 --- a/packages/aws-durable-execution-sdk-python/README.md +++ b/packages/aws-durable-execution-sdk-python/README.md @@ -59,7 +59,7 @@ class AuditPlugin(DurableInstrumentationPlugin): AUDIT_PLUGIN_PROVIDER = DurableInstrumentationPluginProvider( plugin_type=AuditPlugin, factory=AuditPlugin, - plugin_api_version=1, + plugin_api_version=2, ) ``` 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 7ccb8d50..abde00c9 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 @@ -28,7 +28,35 @@ logger = logging.getLogger(__name__) -DURABLE_INSTRUMENTATION_PLUGIN_API_VERSION = 1 +DURABLE_INSTRUMENTATION_PLUGIN_API_VERSION = 2 + + +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: @@ -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, @@ -251,7 +279,7 @@ def from_durable_execution_invocation_output( is_first_invocation=invocation_start_info.is_first_invocation, execution_start_time=invocation_start_info.execution_start_time, execution_input=invocation_start_info.execution_input, - status=output.status, + status=_to_invocation_status(output.status), error=output.error, execution_result=output.result, ) @@ -464,7 +492,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, @@ -502,7 +530,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, @@ -520,7 +548,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, @@ -543,7 +571,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, @@ -587,7 +615,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 89de9c68..df625c31 100644 --- a/packages/aws-durable-execution-sdk-python/tests/plugin_test.py +++ b/packages/aws-durable-execution-sdk-python/tests/plugin_test.py @@ -8,23 +8,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, @@ -242,6 +244,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, @@ -443,7 +480,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 @@ -456,7 +493,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 @@ -468,7 +505,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 @@ -653,7 +690,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(): @@ -671,7 +708,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(): @@ -689,7 +726,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(): @@ -726,7 +763,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" @@ -735,6 +772,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) @@ -751,14 +789,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, ) @@ -781,19 +819,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, ) @@ -843,7 +881,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, ) @@ -857,7 +895,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, ) @@ -914,6 +952,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.""" @@ -931,7 +983,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" @@ -1003,7 +1055,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", @@ -1014,7 +1066,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, @@ -1052,6 +1104,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) @@ -1061,7 +1114,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, ) From 0a0f9a6e0016e44c2226e517d3851ce791dba4fb Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Thu, 13 Aug 2026 03:43:45 +0000 Subject: [PATCH 2/4] fix(plugin): keep unpublished API version unchanged --- .../aws_durable_execution_sdk_python_otel/plugin_provider.py | 4 ++-- packages/aws-durable-execution-sdk-python/README.md | 2 +- .../src/aws_durable_execution_sdk_python/plugin.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/plugin_provider.py b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/plugin_provider.py index e26b8a14..c4be734c 100644 --- a/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/plugin_provider.py +++ b/packages/aws-durable-execution-sdk-python-otel/src/aws_durable_execution_sdk_python_otel/plugin_provider.py @@ -13,11 +13,11 @@ INVOCATION_OTEL_PLUGIN_PROVIDER = DurableInstrumentationPluginProvider( plugin_type=InvocationOtelPlugin, factory=InvocationOtelPlugin, - plugin_api_version=2, + plugin_api_version=1, ) EXECUTION_OTEL_PLUGIN_PROVIDER = DurableInstrumentationPluginProvider( plugin_type=ExecutionOtelPlugin, factory=ExecutionOtelPlugin, - plugin_api_version=2, + plugin_api_version=1, ) diff --git a/packages/aws-durable-execution-sdk-python/README.md b/packages/aws-durable-execution-sdk-python/README.md index 13377dfc..bf7776f4 100644 --- a/packages/aws-durable-execution-sdk-python/README.md +++ b/packages/aws-durable-execution-sdk-python/README.md @@ -59,7 +59,7 @@ class AuditPlugin(DurableInstrumentationPlugin): AUDIT_PLUGIN_PROVIDER = DurableInstrumentationPluginProvider( plugin_type=AuditPlugin, factory=AuditPlugin, - plugin_api_version=2, + plugin_api_version=1, ) ``` 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 abde00c9..b81d61bd 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 @@ -28,7 +28,7 @@ logger = logging.getLogger(__name__) -DURABLE_INSTRUMENTATION_PLUGIN_API_VERSION = 2 +DURABLE_INSTRUMENTATION_PLUGIN_API_VERSION = 1 class InvocationStatus(Enum): From c7c4c096d0e4b6cd5f52db0df9e18d51f5ea3520 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Fri, 14 Aug 2026 00:05:11 +0000 Subject: [PATCH 3/4] fix(otel): require core SDK 1.9.0 --- .../README.md | 2 +- .../pyproject.toml | 2 +- .../tests/test_package_metadata.py | 28 +++++++++++++++++-- pyproject.toml | 2 +- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/aws-durable-execution-sdk-python-otel/README.md b/packages/aws-durable-execution-sdk-python-otel/README.md index 4eb563aa..867a57e3 100644 --- a/packages/aws-durable-execution-sdk-python-otel/README.md +++ b/packages/aws-durable-execution-sdk-python-otel/README.md @@ -306,7 +306,7 @@ setups. ## Requirements - Python >= 3.11 -- `aws-durable-execution-sdk-python` >= 1.8.0 +- `aws-durable-execution-sdk-python` >= 1.9.0 - `opentelemetry-api` >= 1.20.0 - `opentelemetry-sdk` >= 1.20.0 - `opentelemetry-exporter-otlp` diff --git a/packages/aws-durable-execution-sdk-python-otel/pyproject.toml b/packages/aws-durable-execution-sdk-python-otel/pyproject.toml index e5700d8b..d6904165 100644 --- a/packages/aws-durable-execution-sdk-python-otel/pyproject.toml +++ b/packages/aws-durable-execution-sdk-python-otel/pyproject.toml @@ -22,7 +22,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "aws-durable-execution-sdk-python>=1.8.0", + "aws-durable-execution-sdk-python>=1.9.0", "opentelemetry-api>=1.20.0", "opentelemetry-sdk>=1.20.0", "opentelemetry-exporter-otlp", 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..8aa8608e 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.9.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/pyproject.toml b/pyproject.toml index 6ea8e7c1..36c454b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,7 +124,7 @@ test = "pytest packages/aws-durable-execution-sdk-python-examples/test {args}" [tool.hatch.envs.test-pypi-otel] dependencies = [ - "aws-durable-execution-sdk-python>=1.8.0", + "aws-durable-execution-sdk-python>=1.9.0", "opentelemetry-sdk>=1.20.0", "pytest", "pytest-cov", From 007eb1c2bf6491660291e93b59abb9971f932ec3 Mon Sep 17 00:00:00 2001 From: Frank Chen Date: Fri, 14 Aug 2026 19:01:24 +0000 Subject: [PATCH 4/4] fix(otel): support core SDK 1.8.0 --- packages/aws-durable-execution-sdk-python-otel/README.md | 2 +- packages/aws-durable-execution-sdk-python-otel/pyproject.toml | 2 +- .../tests/test_package_metadata.py | 2 +- pyproject.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/aws-durable-execution-sdk-python-otel/README.md b/packages/aws-durable-execution-sdk-python-otel/README.md index 867a57e3..4eb563aa 100644 --- a/packages/aws-durable-execution-sdk-python-otel/README.md +++ b/packages/aws-durable-execution-sdk-python-otel/README.md @@ -306,7 +306,7 @@ setups. ## Requirements - Python >= 3.11 -- `aws-durable-execution-sdk-python` >= 1.9.0 +- `aws-durable-execution-sdk-python` >= 1.8.0 - `opentelemetry-api` >= 1.20.0 - `opentelemetry-sdk` >= 1.20.0 - `opentelemetry-exporter-otlp` diff --git a/packages/aws-durable-execution-sdk-python-otel/pyproject.toml b/packages/aws-durable-execution-sdk-python-otel/pyproject.toml index d6904165..e5700d8b 100644 --- a/packages/aws-durable-execution-sdk-python-otel/pyproject.toml +++ b/packages/aws-durable-execution-sdk-python-otel/pyproject.toml @@ -22,7 +22,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "aws-durable-execution-sdk-python>=1.9.0", + "aws-durable-execution-sdk-python>=1.8.0", "opentelemetry-api>=1.20.0", "opentelemetry-sdk>=1.20.0", "opentelemetry-exporter-otlp", 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 8aa8608e..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 @@ -4,7 +4,7 @@ PACKAGE_ROOT = Path(__file__).resolve().parents[1] REPOSITORY_ROOT = PACKAGE_ROOT.parents[1] -CORE_DEPENDENCY = "aws-durable-execution-sdk-python>=1.9.0" +CORE_DEPENDENCY = "aws-durable-execution-sdk-python>=1.8.0" def _load_pyproject(path: Path) -> dict: diff --git a/pyproject.toml b/pyproject.toml index 36c454b2..6ea8e7c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,7 +124,7 @@ test = "pytest packages/aws-durable-execution-sdk-python-examples/test {args}" [tool.hatch.envs.test-pypi-otel] dependencies = [ - "aws-durable-execution-sdk-python>=1.9.0", + "aws-durable-execution-sdk-python>=1.8.0", "opentelemetry-sdk>=1.20.0", "pytest", "pytest-cov",