diff --git a/packages/aws-durable-execution-sdk-python-otel/pyproject.toml b/packages/aws-durable-execution-sdk-python-otel/pyproject.toml index 9f9d4ff4..e5700d8b 100644 --- a/packages/aws-durable-execution-sdk-python-otel/pyproject.toml +++ b/packages/aws-durable-execution-sdk-python-otel/pyproject.toml @@ -12,7 +12,7 @@ license = "Apache-2.0" keywords = ["opentelemetry", "tracing", "observability", "durable-execution"] authors = [{ name = "AWS durable-execution-dev", email = "durable-execution-dev@amazon.com" }] classifiers = [ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "Programming Language :: Python", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", 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 new file mode 100644 index 00000000..678d6274 --- /dev/null +++ b/packages/aws-durable-execution-sdk-python-otel/tests/test_package_metadata.py @@ -0,0 +1,13 @@ +import tomllib +from pathlib import Path + + +PACKAGE_ROOT = Path(__file__).resolve().parents[1] + + +def test_package_is_marked_production_stable() -> None: + with (PACKAGE_ROOT / "pyproject.toml").open("rb") as pyproject: + classifiers = tomllib.load(pyproject)["project"]["classifiers"] + + assert "Development Status :: 5 - Production/Stable" in classifiers + assert "Development Status :: 4 - Beta" not in classifiers diff --git a/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/execution.py b/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/execution.py index e5b367c4..b541a6e7 100644 --- a/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/execution.py +++ b/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/execution.py @@ -4,7 +4,6 @@ import functools import json import logging -import warnings from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass, field from typing import TYPE_CHECKING, Any @@ -178,8 +177,7 @@ def durable_execution( Args: func: The user function to decorate boto3_client: Optional boto3 Lambda client to use - plugins: Optional list of plugins to use (EXPERIMENTAL: This - feature has known issues and this parameter may change or be removed.) + plugins: Optional list of instrumentation plugins to use """ # Decorator called with parameters if func is None: @@ -190,13 +188,6 @@ def durable_execution( logger.debug("Starting durable execution handler...") - if plugins: - warnings.warn( - "The 'plugins' parameter is provisional and may be altered or removed.", - category=FutureWarning, - stacklevel=2, # point the warning to the caller of durable_execution - ) - plugin_executor = PluginExecutor(load_configured_plugins(plugins)) @plugin_executor.handle_durable_output 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 72cd6820..a8076a58 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 @@ -68,8 +68,18 @@ class OperationInfo: is_replayed: bool status: OperationStatus end_time: datetime.datetime | None = field(default=None, kw_only=True) - result: str | None = field(default=None, kw_only=True) - error: ErrorObject | None = field(default=None, kw_only=True) + result: str | None = field( + default=None, + kw_only=True, + metadata={"experimental": True}, + ) + """EXPERIMENTAL: The serialized operation result, when available.""" + error: ErrorObject | None = field( + default=None, + kw_only=True, + metadata={"experimental": True}, + ) + """EXPERIMENTAL: The operation error, when available.""" attempt: int | None = field(default=None, kw_only=True) @staticmethod @@ -175,7 +185,11 @@ class InvocationStartInfo(InvocationInfo): @dataclass(frozen=True) class InvocationEndInfo(InvocationInfo): status: InvocationStatus = field(kw_only=True) - error: ErrorObject | None = None + error: ErrorObject | None = field( + default=None, + metadata={"experimental": True}, + ) + """EXPERIMENTAL: The invocation error, when available.""" @classmethod def from_durable_execution_invocation_output( diff --git a/packages/aws-durable-execution-sdk-python/tests/execution_test.py b/packages/aws-durable-execution-sdk-python/tests/execution_test.py index 3334815f..9d4c1c32 100644 --- a/packages/aws-durable-execution-sdk-python/tests/execution_test.py +++ b/packages/aws-durable-execution-sdk-python/tests/execution_test.py @@ -3,6 +3,7 @@ import datetime import json import time +import warnings from typing import Any from unittest.mock import Mock, patch @@ -2914,12 +2915,13 @@ def test_durable_execution_loads_plugins_when_handler_is_initialized(): resolved_plugin = _RecordingPlugin() with ( + warnings.catch_warnings(), patch( "aws_durable_execution_sdk_python.execution.load_configured_plugins", return_value=[explicit_plugin, resolved_plugin], ) as load_plugins, - pytest.warns(FutureWarning), ): + warnings.simplefilter("error", FutureWarning) @durable_execution(plugins=[explicit_plugin]) def test_handler(event: Any, context: DurableContext) -> dict: 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 97e3d9f5..322e1e3f 100644 --- a/packages/aws-durable-execution-sdk-python/tests/plugin_test.py +++ b/packages/aws-durable-execution-sdk-python/tests/plugin_test.py @@ -1,6 +1,7 @@ import datetime import logging import unittest +from dataclasses import fields from unittest.mock import MagicMock from aws_durable_execution_sdk_python.identifier import OperationIdentifier @@ -18,6 +19,7 @@ from aws_durable_execution_sdk_python.plugin import ( DurableInstrumentationPlugin, InvocationEndInfo, + InvocationInfo, InvocationStartInfo, OperationChangeInfo, OperationEndInfo, @@ -110,6 +112,29 @@ class TestDataClasses(unittest.TestCase): + def test_payload_fields_are_marked_experimental(self): + plugin_info_types = ( + OperationInfo, + OperationStartInfo, + OperationEndInfo, + OperationChangeInfo, + UserFunctionStartInfo, + UserFunctionEndInfo, + InvocationInfo, + InvocationStartInfo, + InvocationEndInfo, + ) + payload_field_terms = ("input", "output", "result", "error") + + for info_type in plugin_info_types: + for info_field in fields(info_type): + if any(term in info_field.name for term in payload_field_terms): + self.assertIs( + info_field.metadata.get("experimental"), + True, + f"{info_type.__name__}.{info_field.name}", + ) + def test_operation_start_info(self): self.assertEqual(OPERATION_START_INFO.sub_type, OperationSubType.CALLBACK) self.assertEqual(OPERATION_START_INFO.name, "my-op")