Skip to content

Commit 162361b

Browse files
author
Ayushi Ahjolia
committed
docs: add v1 to v2 migration guide
Add docs/migration-1.x-to-2.x.md covering the 2.x breaking changes: typed per-operation error hierarchy, first-run serialize/deserialize round trip, removed config types and fields, construction-time config validation, and wait_for_condition changes; plus the optional should_complete feature. Also document the initial_state serdes requirement on WaitForConditionConfig.
1 parent 9d943a7 commit 162361b

2 files changed

Lines changed: 163 additions & 1 deletion

File tree

docs/migration-1.x-to-2.x.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Migrating from 1.x to 2.x
2+
3+
`2.x` is a breaking major release. Every change is a bug fix or brings Python to
4+
parity with the JavaScript and Java SDKs. The two changes most likely to touch
5+
your code are the typed, per-operation **error hierarchy** and the
6+
**serialize/deserialize round trip on the first run**.
7+
8+
There is no compatibility shim: removed names (for example `CallableRuntimeError`)
9+
are gone with no alias. If you are not ready to migrate, stay on `1.x`.
10+
11+
## What Changed and What to Do
12+
13+
| Change | What you must do |
14+
| --- | --- |
15+
| `CallableRuntimeError`, `UserlandError`, `CallableRuntimeErrorSerializableDetails` removed; typed per-operation errors added | Catch `StepError`, `InvokeError`, `ChildContextError`, or `WaitForConditionError` (or the base `DurableOperationError`) instead of `CallableRuntimeError`. |
16+
| `CallbackError` moved out of the termination tree; graded subtypes added | Remove any `termination_reason == TerminationReason.CALLBACK_ERROR` check (the enum member is gone). Optionally catch `CallbackTimeoutError` / `CallbackExternalError` / `CallbackSubmitterError`. |
17+
| `BatchResult.throw_if_error()` now raises a typed error | Catch `ChildContextError` instead of `CallableRuntimeError`. |
18+
| First-run serialize/deserialize round trip for `step`, child contexts, `map`/`parallel`, and `wait_for_condition` | Make custom `SerDes` round-trip safe: `deserialize(serialize(x)) == x`. Ensure `wait_for_condition` `initial_state` is serializable by the configured serdes. For a transient serdes failure, raise the new `RetryableSerDesError` (retries) instead of `SerDesError` (permanent). |
19+
| `InvokeConfig.timeout` and `InvokeConfig.timeout_seconds` removed | Remove them. Enforce any timeout inside the invoked function or as a separate timer. |
20+
| Removed `ItemBatcher`, `ItemsPerBatchUnit`, `BatchedInput`, `TerminationMode`, `StepFuture`, `MapConfig.item_batcher`, `ChildConfig.item_serdes` | Remove all uses. Replace `ChildConfig.item_serdes` with `ChildConfig.serdes`. |
21+
| `MapConfig` / `ParallelConfig` / `CompletionConfig` now validate at construction | Wrap construction in `try/except ValidationError` if you build configs from external input. |
22+
| `CompletionConfig.all_completed()` now actually tolerates all failures | If you hand-built the old all-`None` config, use the factory instead. |
23+
| `WaitDecision` removed; `WaitStrategyConfig.timeout` / `timeout_seconds` removed | Use `WaitForConditionDecision` (`stop_polling()` / `continue_waiting(delay)`). |
24+
| `wait_for_condition` raises `WaitForConditionError` when it exhausts `max_attempts` | Catch `WaitForConditionError` instead of inspecting the returned state. |
25+
26+
Find affected code before upgrading:
27+
28+
```bash
29+
rg -n "CallableRuntimeError|UserlandError|CallableRuntimeErrorSerializableDetails" .
30+
rg -n "CallbackError|CALLBACK_ERROR" .
31+
rg -n "InvokeConfig\(|\.timeout_seconds" .
32+
rg -n "WaitDecision|WaitStrategyConfig\(|item_batcher|ItemBatcher|ItemsPerBatchUnit" .
33+
rg -n "TerminationMode|BatchedInput|StepFuture|ChildConfig\(" .
34+
```
35+
36+
## Error Handling (the biggest change)
37+
38+
In `1.x` nearly every user-land failure surfaced as one `CallableRuntimeError`,
39+
so a failed step was indistinguishable from a failed invoke or child branch. `2.x`
40+
raises a specific type per operation, all under a new base `DurableOperationError`,
41+
and preserves the original error as `__cause__` (on replay, `__cause__` is
42+
reconstructed from the checkpointed wire fields `error_type`/`message`/`data`/`stack_trace`).
43+
44+
```python
45+
# 1.x
46+
from aws_durable_execution_sdk_python.exceptions import CallableRuntimeError
47+
try:
48+
result = context.step(charge_card, name="charge")
49+
except CallableRuntimeError as e:
50+
log.error("something failed: %s", e.message)
51+
52+
# 2.x
53+
from aws_durable_execution_sdk_python import StepError, DurableOperationError
54+
try:
55+
result = context.step(charge_card, name="charge")
56+
except StepError as e: # or `except DurableOperationError` to catch any operation
57+
log.error("charge step failed: %s", e.message)
58+
```
59+
60+
New types, all exported from the package root: `DurableOperationError` (base),
61+
`StepError`, `InvokeError`, `ChildContextError`, `WaitForConditionError`,
62+
`CallbackError` (+ `CallbackExternalError`, `CallbackTimeoutError`,
63+
`CallbackSubmitterError`), plus `SerDesError` (now exported) and
64+
`RetryableSerDesError`. `SerDesError` stays a direct child of
65+
`DurableExecutionsError`; `RetryableSerDesError` is a retryable `InvocationError`.
66+
67+
### Callbacks
68+
69+
`context.wait_for_callback(...)` returns the payload directly and raises the
70+
callback error from the call itself (there is no `callback.result()`):
71+
72+
```python
73+
from aws_durable_execution_sdk_python import (
74+
CallbackError, CallbackTimeoutError, CallbackSubmitterError,
75+
)
76+
try:
77+
payload = context.wait_for_callback(submit_approval, name="approval")
78+
except CallbackTimeoutError:
79+
... # timeout / heartbeat expiry
80+
except CallbackSubmitterError:
81+
... # the submitter step failed
82+
except CallbackError as e: # external + internal; `callback_id` still available
83+
log.error("callback %s failed", e.callback_id)
84+
```
85+
86+
### map / parallel
87+
88+
```python
89+
result = context.map(items, process_item)
90+
try:
91+
result.throw_if_error() # raises ChildContextError for the first failure
92+
except ChildContextError:
93+
for err in result.get_errors(): # every failed item's ErrorObject
94+
log.error("%s: %s", err.type, err.message)
95+
```
96+
97+
## Serialize/Deserialize Round Trip
98+
99+
`1.x` returned the raw in-memory result on the first run but the deserialized
100+
result on replay, so a non-identity custom `SerDes` produced different values.
101+
`2.x` round-trips (`serialize` then `deserialize`) on the first run for `step`,
102+
child contexts, `map`/`parallel`, and `wait_for_condition` (which also feeds the
103+
deserialized state to the wait strategy). No API change, but a `SerDes` that is
104+
not round-trip safe now surfaces the discrepancy (and any serialization bug) on
105+
the first run. Fix it so `deserialize(serialize(x)) == x`. Async operations
106+
(`invoke`, `wait_for_callback`, `wait`) are unaffected.
107+
108+
`wait_for_condition` also round-trips `initial_state` through the serdes before
109+
the first check, so `initial_state` must now be serializable by the configured
110+
serdes.
111+
112+
## New in 2.x: Custom Completion Predicate (Optional)
113+
114+
`2.x` adds a `should_complete` predicate to `CompletionConfig`, giving `map` and
115+
`parallel` full control over when a batch completes early. This is a new feature,
116+
not a breaking change - no action is required unless you adopt it.
117+
118+
```python
119+
from aws_durable_execution_sdk_python import complete_batch, continue_batch
120+
121+
config = CompletionConfig(
122+
should_complete=lambda status: (
123+
complete_batch() if status.success_count >= 2 else continue_batch()
124+
)
125+
)
126+
```
127+
128+
The predicate receives a `CompletionStatus` snapshot (counts plus per-item
129+
statuses) and returns a `CompletionDecision` - `continue_batch()` or
130+
`complete_batch(outcome)`. The outcome reports `CUSTOM_COMPLETION_SUCCEEDED` or
131+
`CUSTOM_COMPLETION_FAILED`; a failed custom completion surfaces through
132+
`throw_if_error()` as a `ChildContextError`, so there is still no separate
133+
batch-completion error type to catch. Notes:
134+
135+
- It cannot be combined with `min_successful` or the `tolerated_failure_*`
136+
fields; doing so raises `ValidationError` at construction.
137+
- The predicate must be deterministic and side-effect-free. Replay uses the
138+
checkpointed decision and never re-invokes it.
139+
- New exports: `complete_batch`, `continue_batch`, `CompletionStatus`,
140+
`CompletionDecision`, `CompletionOutcome`, `CompletionItemStatus`,
141+
`BatchItemStatus`.
142+
143+
## Recommended Validation After Upgrading
144+
145+
1. Build and run your test suite against `2.x`, and grep for the removed names above.
146+
2. Trigger a failure in a `step`, an `invoke`, and a `map`/`parallel` branch;
147+
confirm you catch `StepError`, `InvokeError`, and `ChildContextError`.
148+
3. Exercise a `wait_for_callback` timeout and a submitter-step failure
149+
(`CallbackTimeoutError`, `CallbackSubmitterError`).
150+
4. Exercise a `wait_for_condition` that exhausts its attempts (`WaitForConditionError`).
151+
5. If you use a custom `SerDes`, run a workflow that checkpoints both a result and
152+
an error payload and confirm first-run output equals replay output.

packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/waits.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,17 @@ def wait_strategy(result: T, attempts_made: int) -> WaitForConditionDecision:
9999

100100
@dataclass(frozen=True)
101101
class WaitForConditionConfig(Generic[T]):
102-
"""Configuration for wait_for_condition."""
102+
"""Configuration for wait_for_condition.
103+
104+
Attributes:
105+
wait_strategy: Called after each poll with (state, attempts_made) and
106+
returns a WaitForConditionDecision (continue_waiting or stop_polling).
107+
initial_state: State passed to the first poll. It is round-tripped
108+
through serdes (serialize then deserialize) before the first check,
109+
so it must be serializable by the configured serdes.
110+
serdes: SerDes used to serialize and deserialize the polled state at
111+
each checkpoint. Defaults to the SDK's default JSON serdes when None.
112+
"""
103113

104114
wait_strategy: Callable[[T, int], WaitForConditionDecision]
105115
initial_state: T

0 commit comments

Comments
 (0)