Skip to content

Commit 8298f71

Browse files
committed
Merge remote-tracking branch 'origin/main' into andystaples/add-scheduled-tasks
# Conflicts: # CHANGELOG.md # durabletask/client.py # durabletask/entities/entity_context.py # durabletask/internal/client_helpers.py
2 parents f8487e0 + 532479d commit 8298f71

25 files changed

Lines changed: 2118 additions & 240 deletions

CHANGELOG.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,78 @@ ADDED
2121
- Added an optional `signal_time` parameter to `OrchestrationContext.signal_entity`
2222
and to the client `signal_entity` methods (sync and async), allowing entity
2323
signals to be scheduled for future delivery from orchestrations and clients.
24+
- Added a pluggable `DataConverter` (`durabletask.serialization`) accepted by
25+
`TaskHubGrpcWorker`, `TaskHubGrpcClient`, and `AsyncTaskHubGrpcClient` via a
26+
`data_converter` argument. Every payload boundary (inputs, outputs, events,
27+
custom status, entity state) routes through it. The default
28+
`JsonDataConverter` preserves existing behavior, so a custom converter (for
29+
example one backed by pydantic) is opt-in. Custom objects can opt in via a
30+
`to_json()` hook and a `from_json(value)` classmethod.
31+
- `OrchestrationContext.call_activity`, `call_sub_orchestrator`, and
32+
`call_entity` accept an optional `return_type`, and `wait_for_external_event`
33+
accepts an optional `data_type`. When provided, the result/event payload is
34+
reconstructed as that type (dataclasses — including nested dataclass,
35+
`Optional`, and `list` fields — and `from_json()`-capable types) and the
36+
returned task is typed accordingly (e.g. `call_activity(..., return_type=Foo)`
37+
yields `CompletableTask[Foo]`). When omitted, the raw deserialized JSON is
38+
returned as before.
39+
- Inbound payloads are reconstructed from function type annotations. When an
40+
orchestrator, activity, or entity operation annotates its input parameter (or
41+
an activity its return value) with a dataclass or `from_json()`-capable type,
42+
the payload is reconstructed as that type. Builtins and unannotated/unknown
43+
types are passed through unchanged. An explicit `return_type` takes precedence
44+
over a discovered annotation.
45+
- Added typed accessors to `client.OrchestrationState`: `get_input()`,
46+
`get_output()`, and `get_custom_status()` each accept an optional
47+
`expected_type` and deserialize the corresponding payload, reconstructing
48+
dataclasses and `from_json()`-capable types. The raw `serialized_*` fields are
49+
retained.
50+
- Objects exposing a `to_json()` method are now JSON-serializable when passed as
51+
activity/orchestrator inputs or outputs.
52+
- Added `EntityMetadata.get_typed_state(intended_type=...)`, which deserializes
53+
the entity's persisted state and reconstructs dataclasses and
54+
`from_json()`-capable types. The existing `get_state()` is unchanged: with no
55+
argument it returns the raw serialized JSON payload, and `get_state(some_type)`
56+
applies constructor-based coercion (`some_type(raw)`).
57+
- Entity runtime state retrieval (`EntityContext.get_state(intended_type=...)` /
58+
`DurableEntity.get_state(...)`) now also reconstructs dataclasses and
59+
`from_json()`-capable types, in addition to the existing constructor-based
60+
coercion.
61+
62+
CHANGED
63+
64+
- Custom objects (dataclasses, `SimpleNamespace`, namedtuples) are now
65+
serialized as plain JSON. Decoding such a payload *without* a type hint now
66+
yields a plain `dict` (previously a `SimpleNamespace`; a namedtuple now
67+
round-trips as a JSON array). To get the original type back, pass the new
68+
`return_type` / `data_type` arguments, annotate the consuming function's
69+
parameter or return type, or use the typed client accessors. Payloads produced
70+
by older SDK versions still deserialize — including into a `SimpleNamespace`
71+
when no type is supplied — so in-flight orchestrations continue to replay
72+
across an upgrade.
73+
- JSON serialization failures now raise a `TypeError` that chains the original
74+
error (`__cause__`) and names the offending type.
75+
76+
FIXED
77+
78+
- Falsy entity states (`0`, `""`, `[]`, `{}`) are no longer dropped when an
79+
entity batch is persisted. Previously a falsy current state was treated as
80+
"no state" and written as `None`, effectively deleting it; only an actual
81+
`None` state now clears the persisted entity state.
82+
83+
BREAKING CHANGES (type-level only — no runtime impact for typical users)
84+
85+
These changes do not alter runtime behavior, but because the package ships
86+
`py.typed`, consumers running strict type checkers (pyright/mypy) — or
87+
subclassing the public abstract types — may need to update their code:
88+
89+
- `OrchestrationContext.call_activity`, `call_sub_orchestrator`, `call_entity`,
90+
and `wait_for_external_event` gained new keyword-only parameters
91+
(`return_type` / `data_type`). Subclasses overriding these methods should add
92+
the parameter to match the base signature.
93+
- `client.OrchestrationState` gained a non-public `_data_converter` field
94+
(excluded from equality and `repr`). Code constructing `OrchestrationState`
95+
positionally should pass it via the new field or rely on its default.
2496

2597
## v1.6.0
2698

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

0 commit comments

Comments
 (0)