Skip to content

Commit b719326

Browse files
author
Alex Wang
committed
feat: add three more conformance suites
Adds callback (4-1..4-19), invoke (5-1..5-16), and wait_for_callback (7-1..7-15) suites; template-driven discovery picks them up automatically. Inject script now preserves CloudFormation short-form intrinsic tags, and the workflow uses a stack-safe suite slug plus runner env passthrough.
1 parent 136976d commit b719326

64 files changed

Lines changed: 2131 additions & 3 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/conformance-tests.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,26 @@ jobs:
104104
--template template_${{ matrix.suite }}.yaml \
105105
--role-arn "$ROLE_ARN"
106106
107+
- name: Compute stack-safe suite slug
108+
run: |
109+
# CloudFormation stack names allow only [a-zA-Z][-a-zA-Z0-9]*;
110+
# suite names like wait_for_callback contain underscores.
111+
echo "SUITE_SLUG=$(echo '${{ matrix.suite }}' | tr '_' '-')" >> "$GITHUB_ENV"
112+
107113
- name: Run conformance suite
114+
env:
115+
# Same env the SAM-based integration deploy passes. LAMBDA_ENDPOINT is
116+
# the durable-execution endpoint override; the role/account are used by
117+
# the SDK/tooling when present.
118+
LAMBDA_ENDPOINT: ${{ secrets.LAMBDA_ENDPOINT }}
119+
TEST_ACCOUNT_ID: ${{ secrets.TEST_ACCOUNT_ID }}
120+
TEST_LAMBDA_EXECUTION_ROLE_ARN: ${{ secrets.TEST_LAMBDA_EXECUTION_ROLE_ARN }}
108121
run: |
109122
python -m aws_durable_execution_conformance_tests.app \
110123
--template template_${{ matrix.suite }}.yaml \
111124
--language python \
112125
--suite ${{ matrix.suite }} \
113-
--name conf-py-${{ matrix.suite }}-${{ github.run_id }} \
126+
--name conf-py-${SUITE_SLUG}-${{ github.run_id }} \
114127
--region ${{ env.AWS_REGION }} \
115128
--history-dir history-${{ matrix.suite }} \
116129
--report junit \

packages/aws-durable-execution-sdk-python-conformance-tests/handlers/callback/__init__.py

Whitespace-only changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# 4-1: Create callback basic (success via external callback)
2+
from typing import Any
3+
4+
from aws_durable_execution_sdk_python.context import DurableContext
5+
from aws_durable_execution_sdk_python.execution import durable_execution
6+
7+
8+
@durable_execution
9+
def handler(event: Any, context: DurableContext) -> str:
10+
callback = context.create_callback(name=event)
11+
return callback.result()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# 4-6: Callback failure (external system reports failure)
2+
from typing import Any
3+
4+
from aws_durable_execution_sdk_python.context import DurableContext
5+
from aws_durable_execution_sdk_python.execution import durable_execution
6+
7+
8+
@durable_execution
9+
def handler(event: Any, context: DurableContext) -> str:
10+
callback = context.create_callback(name=event)
11+
# Do not catch — let the exception propagate so the execution fails.
12+
return callback.result()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# 4-5: Create callback with heartbeat then success
2+
from typing import Any
3+
4+
from aws_durable_execution_sdk_python.config import CallbackConfig, Duration
5+
from aws_durable_execution_sdk_python.context import DurableContext
6+
from aws_durable_execution_sdk_python.execution import durable_execution
7+
8+
9+
@durable_execution
10+
def handler(event: Any, context: DurableContext) -> str:
11+
callback = context.create_callback(
12+
name=event,
13+
config=CallbackConfig(heartbeat_timeout=Duration.from_seconds(10)),
14+
)
15+
return callback.result()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# 4-4: Create callback heartbeat timeout (no heartbeat sent)
2+
from typing import Any
3+
4+
from aws_durable_execution_sdk_python.config import CallbackConfig, Duration
5+
from aws_durable_execution_sdk_python.context import DurableContext
6+
from aws_durable_execution_sdk_python.execution import durable_execution
7+
8+
9+
@durable_execution
10+
def handler(event: Any, context: DurableContext) -> str:
11+
callback = context.create_callback(
12+
name=event,
13+
config=CallbackConfig(heartbeat_timeout=Duration.from_seconds(5)),
14+
)
15+
return callback.result()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# 4-13: Callback failure caught → Wait → return
2+
from typing import Any
3+
4+
from aws_durable_execution_sdk_python.config import Duration
5+
from aws_durable_execution_sdk_python.context import DurableContext
6+
from aws_durable_execution_sdk_python.exceptions import CallbackError
7+
from aws_durable_execution_sdk_python.execution import durable_execution
8+
9+
10+
@durable_execution
11+
def handler(event: Any, context: DurableContext) -> str:
12+
callback = context.create_callback(name=event)
13+
14+
try:
15+
outcome = callback.result()
16+
except CallbackError as e:
17+
outcome = f"caught_failure:{e}"
18+
except Exception as e: # pragma: no cover - safety net
19+
outcome = f"caught_other:{type(e).__name__}:{e}"
20+
21+
context.wait(Duration.from_seconds(2), name="after-cb")
22+
return outcome
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# 4-14: Callback timeout caught → Wait → return
2+
from typing import Any
3+
4+
from aws_durable_execution_sdk_python.config import CallbackConfig, Duration
5+
from aws_durable_execution_sdk_python.context import DurableContext
6+
from aws_durable_execution_sdk_python.exceptions import CallbackError
7+
from aws_durable_execution_sdk_python.execution import durable_execution
8+
9+
10+
@durable_execution
11+
def handler(event: Any, context: DurableContext) -> str:
12+
callback = context.create_callback(
13+
name=event,
14+
config=CallbackConfig(timeout=Duration.from_seconds(3)),
15+
)
16+
17+
try:
18+
outcome = callback.result()
19+
except CallbackError as e:
20+
outcome = f"caught_timeout:{e}"
21+
except Exception as e: # pragma: no cover - safety net
22+
outcome = f"caught_other:{type(e).__name__}:{e}"
23+
24+
context.wait(Duration.from_seconds(2), name="after-cb")
25+
return outcome
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# 4-12: Callback success → Wait → verify replay
2+
from typing import Any
3+
4+
from aws_durable_execution_sdk_python.config import Duration
5+
from aws_durable_execution_sdk_python.context import DurableContext
6+
from aws_durable_execution_sdk_python.execution import durable_execution
7+
8+
9+
@durable_execution
10+
def handler(event: Any, context: DurableContext) -> str:
11+
callback = context.create_callback(name=event)
12+
cb_result = callback.result()
13+
context.wait(Duration.from_seconds(2), name="after-cb")
14+
return cb_result
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# 4-15: Callback with custom serdes (happy path - Date roundtrip)
2+
import json
3+
from dataclasses import dataclass
4+
from datetime import datetime
5+
from typing import Any
6+
7+
from aws_durable_execution_sdk_python.config import CallbackConfig
8+
from aws_durable_execution_sdk_python.context import DurableContext
9+
from aws_durable_execution_sdk_python.execution import durable_execution
10+
from aws_durable_execution_sdk_python.serdes import SerDes, SerDesContext
11+
12+
13+
@dataclass
14+
class CustomData:
15+
id: int
16+
message: str
17+
timestamp: datetime
18+
19+
20+
class CustomDataSerDes(SerDes[CustomData]):
21+
def serialize(self, value: CustomData | None, _: SerDesContext) -> str | None:
22+
if value is None:
23+
return None
24+
return json.dumps(
25+
{
26+
"id": value.id,
27+
"message": value.message,
28+
"timestamp": value.timestamp.isoformat(),
29+
}
30+
)
31+
32+
def deserialize(self, payload: str | None, _: SerDesContext) -> CustomData | None:
33+
if payload is None:
34+
return None
35+
data = json.loads(payload)
36+
ts_str = data["timestamp"]
37+
if ts_str.endswith("Z"):
38+
ts_str = ts_str[:-1] + "+00:00"
39+
return CustomData(
40+
id=data["id"],
41+
message=data["message"],
42+
timestamp=datetime.fromisoformat(ts_str),
43+
)
44+
45+
46+
@durable_execution
47+
def handler(event: Any, context: DurableContext) -> dict[str, Any]:
48+
callback = context.create_callback(
49+
name=event,
50+
config=CallbackConfig(serdes=CustomDataSerDes()),
51+
)
52+
result: CustomData = callback.result()
53+
return {
54+
"received": {
55+
"id": result.id,
56+
"message": result.message,
57+
"timestamp": int(result.timestamp.timestamp()),
58+
},
59+
}

0 commit comments

Comments
 (0)