Skip to content

Commit 2239c48

Browse files
committed
Update example and docs for type-directed deserialization
1 parent 39440cd commit 2239c48

2 files changed

Lines changed: 29 additions & 25 deletions

File tree

docs/supported-patterns.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ def purchase_order_workflow(ctx: task.OrchestrationContext, order: Order):
6868
yield ctx.call_activity(send_approval_request, input=order)
6969

7070
# Approvals must be received within 24 hours or they will be cancelled.
71-
approval_event = ctx.wait_for_external_event("approval_received")
71+
# Passing ``data_type`` reconstructs the event payload as an ``Approval``.
72+
approval_event = ctx.wait_for_external_event("approval_received", data_type=Approval)
7273
timeout_event = ctx.create_timer(timedelta(hours=24))
7374
winner = yield task.when_any([approval_event, timeout_event])
7475
if winner == timeout_event:
@@ -81,9 +82,11 @@ def purchase_order_workflow(ctx: task.OrchestrationContext, order: Order):
8182
```
8283

8384
As an aside, you'll also notice that the example orchestration above works with custom business
84-
objects. Support for custom business objects includes support for custom classes, custom data
85-
classes, and named tuples. Serialization and deserialization of these objects is handled
86-
automatically by the SDK.
85+
objects. Custom classes, data classes, and named tuples are serialized to plain JSON automatically.
86+
To reconstruct the original type on the receiving side, supply the type — for example via the
87+
`data_type` argument to `wait_for_external_event` (shown above), the `return_type` argument to
88+
`call_activity` / `call_sub_orchestrator` / `call_entity`, or by annotating the consuming function's
89+
input parameter. Without a type, the payload is returned as plain JSON (a `dict` or `list`).
8790

8891
See the full [human interaction sample](../examples/human_interaction.py).
8992

examples/in_memory_backend_example/src/workflows.py

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
1010
Note on serialization
1111
---------------------
12-
The Durable Task SDK serializes dataclass and namedtuple inputs to JSON.
13-
When deserialized on the receiving side, top-level objects become
14-
``SimpleNamespace`` instances while nested objects become plain ``dict``s.
15-
Activities that receive complex inputs should therefore use dict-style
16-
access (``item["quantity"]``) for nested data.
12+
The Durable Task SDK serializes dataclass inputs to JSON. Annotating an
13+
orchestrator's or activity's input parameter with its dataclass type lets the
14+
SDK reconstruct that type on the receiving side (including nested dataclass
15+
fields), so the functions below use attribute access (``order.items``,
16+
``item.quantity``). Without a type annotation, payloads arrive as plain
17+
``dict`` / ``list`` values and would need dict-style access instead.
1718
"""
1819

1920
from dataclasses import dataclass
@@ -25,22 +26,22 @@
2526
# ---------------------------------------------------------------------------
2627
# Data models
2728
# ---------------------------------------------------------------------------
28-
# These dataclasses document the expected shape of the data. At runtime,
29-
# they are serialized to JSON and arrive in activities as SimpleNamespace
30-
# (top-level) or dict (nested) objects.
29+
# These dataclasses describe the shape of the data. Because the orchestrators
30+
# and activities annotate their inputs with these types, the SDK reconstructs
31+
# them (including the nested ``OrderItem`` list) on the receiving side.
3132

3233

3334
@dataclass
3435
class OrderItem:
35-
"""A single item in an order (arrives as a ``dict`` inside activities)."""
36+
"""A single item in an order."""
3637
name: str
3738
quantity: int
3839
unit_price: float
3940

4041

4142
@dataclass
4243
class Order:
43-
"""An order containing one or more items (arrives as ``SimpleNamespace``)."""
44+
"""An order containing one or more items."""
4445
customer: str
4546
items: list[OrderItem]
4647

@@ -50,25 +51,25 @@ class Order:
5051
# ---------------------------------------------------------------------------
5152

5253

53-
def validate_order(ctx: task.ActivityContext, order) -> None:
54+
def validate_order(ctx: task.ActivityContext, order: Order) -> None:
5455
"""Validate that the order has items and all quantities/prices are valid.
5556
5657
Raises ``ValueError`` on invalid input.
5758
"""
5859
if not order.items:
5960
raise ValueError("Order must contain at least one item")
6061
for item in order.items:
61-
if item["quantity"] <= 0:
62+
if item.quantity <= 0:
6263
raise ValueError(
63-
f"Invalid quantity for '{item['name']}': {item['quantity']}")
64-
if item["unit_price"] < 0:
64+
f"Invalid quantity for '{item.name}': {item.quantity}")
65+
if item.unit_price < 0:
6566
raise ValueError(
66-
f"Invalid price for '{item['name']}': {item['unit_price']}")
67+
f"Invalid price for '{item.name}': {item.unit_price}")
6768

6869

69-
def calculate_total(ctx: task.ActivityContext, items: list) -> float:
70-
"""Return the total cost for a list of item dicts."""
71-
return sum(item["quantity"] * item["unit_price"] for item in items)
70+
def calculate_total(ctx: task.ActivityContext, items: list[OrderItem]) -> float:
71+
"""Return the total cost for a list of order items."""
72+
return sum(item.quantity * item.unit_price for item in items)
7273

7374

7475
def process_payment(ctx: task.ActivityContext, amount: float) -> str:
@@ -95,7 +96,7 @@ def ship_item(ctx: task.ActivityContext, item_name: str) -> str:
9596
# ---------------------------------------------------------------------------
9697

9798

98-
def process_order(ctx: task.OrchestrationContext, order):
99+
def process_order(ctx: task.OrchestrationContext, order: Order):
99100
"""Process a complete order: validate, pay, ship items in parallel, confirm.
100101
101102
Demonstrates:
@@ -115,7 +116,7 @@ def process_order(ctx: task.OrchestrationContext, order):
115116

116117
# 4. Ship all items in parallel (fan-out / fan-in)
117118
ship_tasks: list[task.Task[str]] = [
118-
ctx.call_activity(ship_item, input=item["name"])
119+
ctx.call_activity(ship_item, input=item.name)
119120
for item in order.items
120121
]
121122
tracking_ids: list[str] = yield task.when_all(ship_tasks)
@@ -135,7 +136,7 @@ def process_order(ctx: task.OrchestrationContext, order):
135136
}
136137

137138

138-
def order_with_approval(ctx: task.OrchestrationContext, order):
139+
def order_with_approval(ctx: task.OrchestrationContext, order: Order):
139140
"""Order workflow that requires manager approval for high-value orders.
140141
141142
Demonstrates:

0 commit comments

Comments
 (0)