@@ -12,98 +12,46 @@ ADDED
1212- Added a pluggable ` DataConverter ` (` durabletask.serialization ` ) accepted by
1313 ` TaskHubGrpcWorker ` , ` TaskHubGrpcClient ` , and ` AsyncTaskHubGrpcClient ` via a
1414 ` data_converter ` argument. Every payload boundary (inputs, outputs, events,
15- custom status, entity state) routes through it. The default
15+ custom status, entity state) routes through it, so one converter controls how
16+ Python values become JSON and how they are reconstructed. The default
1617 ` JsonDataConverter ` preserves existing behavior, so a custom converter (for
17- example one backed by pydantic) is opt-in. Custom objects can opt in via a
18- ` to_json() ` hook and a ` from_json(value) ` classmethod.
19- - ` OrchestrationContext.call_activity ` , ` call_sub_orchestrator ` , and
20- ` call_entity ` accept an optional ` return_type ` , and ` wait_for_external_event `
21- accepts an optional ` data_type ` . When provided, the result/event payload is
22- reconstructed as that type (dataclasses — including nested dataclass,
23- ` Optional ` , ` list ` , ` dict ` /` Mapping ` , and ` tuple ` fields — and
24- ` from_json() ` -capable types) and the returned task is typed accordingly (e.g.
25- ` call_activity(..., return_type=Foo) ` yields ` CompletableTask[Foo] ` ). When
26- omitted, the raw deserialized JSON is returned as before.
27- - Inbound payloads are reconstructed from function type annotations. When an
28- orchestrator, activity, or entity operation annotates its input parameter (or
29- an activity its return value) with a dataclass or ` from_json() ` -capable type,
30- the payload is reconstructed as that type. Builtins and unannotated/unknown
31- types are passed through unchanged. An explicit ` return_type ` takes precedence
32- over a discovered annotation.
33- - Added typed accessors to ` client.OrchestrationState ` : ` get_input() ` ,
34- ` get_output() ` , and ` get_custom_status() ` each accept an optional
35- ` expected_type ` and deserialize the corresponding payload, reconstructing
36- dataclasses and ` from_json() ` -capable types. The raw ` serialized_* ` fields are
37- retained.
38- - Objects exposing a ` to_json() ` method are now JSON-serializable when passed as
39- activity/orchestrator inputs or outputs.
40- - ` enum.Enum ` values now serialize (to their underlying ` .value ` ) and, when a
41- target type is supplied, deserialize back to the enum member. This covers
42- string-valued and other non-` int ` enums as activity/orchestrator/entity inputs
43- and outputs, including as dataclass fields and inside ` list ` / ` dict ` /
44- ` tuple ` containers. (` IntEnum ` / ` IntFlag ` already serialized as integers.)
45- - A ` from_json() ` classmethod may now optionally accept the active
46- ` DataConverter ` as a second parameter (` from_json(cls, value, converter) ` ),
47- letting it reconstruct nested typed values via ` converter.coerce(...) ` /
48- ` converter.deserialize(...) ` . The single-argument form remains supported.
49- - ` DataConverter ` now exposes an overridable ` can_reconstruct(target_type) `
50- method that controls which annotated input/return types the SDK reconstructs
51- on the inbound path. A custom converter can override it to recognize its own
52- types (for example ` pydantic.BaseModel ` subclasses), so that orchestrator /
53- activity / entity inputs annotated with those types are reconstructed by the
54- converter instead of arriving as raw JSON. The default behavior is unchanged
55- (dataclasses and ` from_json() ` -capable types, plus ` Optional ` / ` list `
56- wrappers, are reconstructable; builtins are not).
57- - Added ` EntityMetadata.get_typed_state(intended_type=...) ` , which deserializes
58- the entity's persisted state and reconstructs dataclasses and
59- ` from_json() ` -capable types. The existing ` get_state() ` is unchanged: with no
60- argument it returns the raw serialized JSON payload, and ` get_state(some_type) `
61- applies constructor-based coercion (` some_type(raw) ` ).
62- - Entity runtime state retrieval (` EntityContext.get_state(intended_type=...) ` /
63- ` DurableEntity.get_state(...) ` ) now also reconstructs dataclasses and
64- ` from_json() ` -capable types, in addition to the existing constructor-based
65- coercion.
18+ example one backed by pydantic) is fully opt-in.
19+ - Custom objects can participate in serialization by exposing a ` to_json() `
20+ method and a ` from_json(value) ` classmethod. Both are honored recursively, so
21+ nested custom objects round-trip through their own hooks.
22+ - Payloads are reconstructed into a caller-supplied type — dataclasses
23+ (including nested fields), ` from_json() ` -capable types, and ` enum.Enum `
24+ members, recursing through ` list ` , ` dict ` , ` tuple ` , and ` Optional ` /` Union `
25+ hints. The type comes from a function's annotations, from an explicit
26+ ` return_type ` on ` call_activity ` / ` call_sub_orchestrator ` / ` call_entity `
27+ (or ` data_type ` on ` wait_for_external_event ` ), or from the typed accessors
28+ ` get_input() ` / ` get_output() ` / ` get_custom_status() ` on
29+ ` client.OrchestrationState ` and ` EntityMetadata.get_typed_state(...) ` . It is
30+ never inferred from the payload. Which annotated types are eligible is decided
31+ by the converter via the overridable ` DataConverter.can_reconstruct(...) ` ; a
32+ custom converter can override it to recognize its own types (for example
33+ ` pydantic.BaseModel ` subclasses).
6634
6735CHANGED
6836
6937- Custom objects (dataclasses, ` SimpleNamespace ` , namedtuples) are now
7038 serialized as plain JSON. Decoding such a payload * without* a type hint now
7139 yields a plain ` dict ` (previously a ` SimpleNamespace ` ; a namedtuple now
72- round-trips as a JSON array). To get the original type back, pass the new
73- ` return_type ` / ` data_type ` arguments, annotate the consuming function's
74- parameter or return type, or use the typed client accessors. Payloads produced
75- by older SDK versions still deserialize — including into a ` SimpleNamespace `
76- when no type is supplied — so in-flight orchestrations continue to replay
77- across an upgrade.
40+ round-trips as a JSON array). To get the original type back, supply a type via
41+ one of the mechanisms above. Payloads produced by older SDK versions still
42+ deserialize — including into a ` SimpleNamespace ` when no type is supplied — so
43+ in-flight orchestrations continue to replay across an upgrade.
7844- JSON serialization failures now raise a ` TypeError ` that chains the original
7945 error (` __cause__ ` ) and names the offending type.
8046- ` EntityContext.get_state() ` / ` DurableEntity.get_state() ` now return a freshly
8147 reconstructed value on every call rather than a reference to a single cached
82- object. As a result, mutating a value returned by ` get_state() ` in place no
83- longer affects the persisted entity state — write the change back with
84- ` set_state() ` to persist it. The entity's state is also serialized eagerly at
85- ` set_state() ` time, so a value that cannot be serialized surfaces the error
86- inside the failing operation (which rolls back) instead of after the batch has
87- run.
48+ object, so mutating the returned value in place no longer affects persisted
49+ state — write it back with ` set_state() ` . State is also serialized eagerly at
50+ ` set_state() ` time, so a non-serializable value fails inside the operation
51+ (which rolls back) instead of after the batch has run.
8852
8953FIXED
9054
91- - A dataclass or ` SimpleNamespace ` that defines a ` to_json() ` hook now uses it
92- when serialized. Previously the built-in dataclass / ` SimpleNamespace `
93- handling ran first, so the hook was ignored — and a dataclass with a field
94- that was not JSON-serializable on its own would fail to serialize even when it
95- provided a ` to_json() ` hook to handle that field. The serialize side now
96- prefers ` to_json() ` , mirroring the deserialize side, which already prefers
97- ` from_json() ` .
98- - Nested ` to_json() ` hooks are now honored when an object is serialized inside a
99- dataclass. Custom objects (including nested dataclasses with their own
100- ` to_json() ` ) are now encoded recursively instead of being flattened to their
101- raw fields, so values that reshape themselves via ` to_json() ` round-trip
102- correctly.
103- - Type-directed deserialization now recurses into ` dict ` /` Mapping ` values and
104- ` tuple ` elements, in addition to the existing ` list ` , ` Optional ` /` Union ` , and
105- dataclass-field recursion. A ` dict[str, Foo] ` or ` tuple[Foo, ...] ` hint now
106- reconstructs the contained ` Foo ` values.
10755- Falsy entity states (` 0 ` , ` "" ` , ` [] ` , ` {} ` ) are no longer dropped when an
10856 entity batch is persisted. Previously a falsy current state was treated as
10957 "no state" and written as ` None ` , effectively deleting it; only an actual
@@ -127,9 +75,6 @@ subclassing the public abstract types — may need to update their code:
12775 and ` wait_for_external_event ` gained new keyword-only parameters
12876 (` return_type ` / ` data_type ` ). Subclasses overriding these methods should add
12977 the parameter to match the base signature.
130- - ` client.OrchestrationState ` gained a non-public ` _data_converter ` field
131- (excluded from equality and ` repr ` ). Code constructing ` OrchestrationState `
132- positionally should pass it via the new field or rely on its default.
13378
13479## v1.6.0
13580
0 commit comments