@@ -52,65 +52,71 @@ ADDED
5252 uses a deterministic instance ID (` export-job-{job_id} ` , exposed via
5353 ` orchestrator_instance_id_for(...) ` ) so callers can correlate a job ID
5454 with its orchestrator for logging, monitoring, and restart.
55- - Added a pluggable ` DataConverter ` abstraction (` durabletask.serialization ` )
56- consumed by both the worker and the client. ` TaskHubGrpcWorker ` ,
57- ` TaskHubGrpcClient ` , and ` AsyncTaskHubGrpcClient ` accept a ` data_converter `
58- argument; every payload serialization boundary (inputs, outputs, events,
55+ - Added a pluggable ` DataConverter ` (` durabletask.serialization ` ) accepted by
56+ ` TaskHubGrpcWorker ` , ` TaskHubGrpcClient ` , and ` AsyncTaskHubGrpcClient ` via a
57+ ` data_converter ` argument. Every payload boundary (inputs, outputs, events,
5958 custom status, entity state) routes through it. The default
60- ` JsonDataConverter ` preserves existing behavior, so supplying a custom
61- converter (for example, one backed by pydantic) is purely opt-in. Custom
62- objects opt in via a ` to_json() ` hook -- invoked as ` type(obj).to_json(obj) `
63- so both instance methods and ` @staticmethod ` hooks work -- and a
64- ` from_json(value) ` classmethod, matching the ` azure-functions-durable `
65- convention.
66- - Type-aware deserialization of payloads. ` OrchestrationContext.call_activity ` ,
67- ` call_sub_orchestrator ` , and ` call_entity ` accept an optional ` return_type ` ,
68- and ` wait_for_external_event ` accepts an optional ` data_type ` . When provided,
69- the result/event payload is coerced to that type: dataclasses are
70- reconstructed from their dict payloads (including nested dataclass, ` Optional ` ,
71- and ` list ` fields), and types exposing a ` from_json() ` classmethod are rebuilt
72- via that hook. When omitted, the raw deserialized JSON is returned as before.
73- The ` return_type ` / ` data_type ` argument also refines the static type of the
74- returned task (e.g. ` call_activity(..., return_type=Foo) ` is typed as
75- ` CompletableTask[Foo] ` ).
59+ ` JsonDataConverter ` preserves existing behavior, so a custom converter (for
60+ example one backed by pydantic) is opt-in. Custom objects can opt in via a
61+ ` to_json() ` hook and a ` from_json(value) ` classmethod.
62+ - ` OrchestrationContext.call_activity ` , ` call_sub_orchestrator ` , and
63+ ` call_entity ` accept an optional ` return_type ` , and ` wait_for_external_event `
64+ accepts an optional ` data_type ` . When provided, the result/event payload is
65+ reconstructed as that type (dataclasses — including nested dataclass,
66+ ` Optional ` , and ` list ` fields — and ` from_json() ` -capable types) and the
67+ returned task is typed accordingly (e.g. ` call_activity(..., return_type=Foo) `
68+ yields ` CompletableTask[Foo] ` ). When omitted, the raw deserialized JSON is
69+ returned as before.
7670- Inbound payloads are reconstructed from function type annotations. When an
77- orchestrator, activity, or entity operation annotates its input parameter with
78- a dataclass or a ` from_json() ` -capable type, the incoming payload is
79- automatically coerced to that type. Coercion is best-effort: builtins and
80- unannotated/unknown types are passed through unchanged, and a payload that
81- cannot be coerced to the requested type falls back to the raw deserialized
82- value (logged at debug level) rather than raising. This best-effort policy is
83- owned by the default ` JsonDataConverter ` ; a stricter converter can change it.
84- - ` call_activity ` results are reconstructed from the activity's return
85- annotation. When an activity function reference is passed (not a string name)
86- and its return type is annotated with a dataclass or ` from_json() ` -capable
87- type, the result is automatically coerced to that type. An explicit
88- ` return_type ` argument takes precedence over the discovered annotation.
71+ orchestrator, activity, or entity operation annotates its input parameter (or
72+ an activity its return value) with a dataclass or ` from_json() ` -capable type,
73+ the payload is reconstructed as that type. Builtins and unannotated/unknown
74+ types are passed through unchanged. An explicit ` return_type ` takes precedence
75+ over a discovered annotation.
8976- Added typed accessors to ` client.OrchestrationState ` : ` get_input() ` ,
9077 ` get_output() ` , and ` get_custom_status() ` each accept an optional
91- ` expected_type ` and deserialize the corresponding ` serialized_* ` payload,
92- reconstructing dataclasses and ` from_json() ` -capable types. The raw
93- ` serialized_input ` / ` serialized_output ` / ` serialized_custom_status ` string
94- fields are retained.
78+ ` expected_type ` and deserialize the corresponding payload, reconstructing
79+ dataclasses and ` from_json() ` -capable types. The raw ` serialized_* ` fields are
80+ retained.
9581- Objects exposing a ` to_json() ` method are now JSON-serializable when passed as
9682 activity/orchestrator inputs or outputs.
97- - Entity state retrieval (` get_state(intended_type=...) ` ) now reconstructs
98- dataclasses from their stored dict payloads and supports types exposing a
99- ` from_json() ` classmethod, in addition to the existing constructor-based
83+ - Added ` EntityMetadata.get_typed_state(intended_type=...) ` , which deserializes
84+ the entity's persisted state and reconstructs dataclasses and
85+ ` from_json() ` -capable types. The existing ` get_state() ` is unchanged: with no
86+ argument it returns the raw serialized JSON payload, and ` get_state(some_type) `
87+ applies constructor-based coercion (` some_type(raw) ` ).
88+ - Entity runtime state retrieval (` EntityContext.get_state(intended_type=...) ` /
89+ ` DurableEntity.get_state(...) ` ) now also reconstructs dataclasses and
90+ ` from_json() ` -capable types, in addition to the existing constructor-based
10091 coercion.
10192
10293CHANGED
10394
104- - Custom objects (dataclasses, ` SimpleNamespace ` ) are now serialized as plain
105- JSON without an internal type marker. Decoding without a ` return_type ` /
106- ` data_type ` therefore yields a plain ` dict ` (previously a ` SimpleNamespace `
107- for marked payloads). Pass the new type arguments to reconstruct the original
108- type. Payloads produced by older SDK versions (carrying the legacy marker)
109- continue to deserialize, including into a ` SimpleNamespace ` when no type is
110- supplied.
95+ - Custom objects (dataclasses, ` SimpleNamespace ` , namedtuples) are now
96+ serialized as plain JSON. Decoding such a payload * without* a type hint now
97+ yields a plain ` dict ` (previously a ` SimpleNamespace ` ; a namedtuple now
98+ round-trips as a JSON array). To get the original type back, pass the new
99+ ` return_type ` / ` data_type ` arguments, annotate the consuming function's
100+ parameter or return type, or use the typed client accessors. Payloads produced
101+ by older SDK versions still deserialize — including into a ` SimpleNamespace `
102+ when no type is supplied — so in-flight orchestrations continue to replay
103+ across an upgrade.
111104- JSON serialization failures now raise a ` TypeError ` that chains the original
112- error (` __cause__ ` ) and names the offending type, making serialization issues
113- easier to diagnose.
105+ error (` __cause__ ` ) and names the offending type.
106+
107+ BREAKING CHANGES (type-level only — no runtime impact for typical users)
108+
109+ These changes do not alter runtime behavior, but because the package ships
110+ ` py.typed ` , consumers running strict type checkers (pyright/mypy) — or
111+ subclassing the public abstract types — may need to update their code:
112+
113+ - ` OrchestrationContext.call_activity ` , ` call_sub_orchestrator ` , ` call_entity ` ,
114+ and ` wait_for_external_event ` gained new keyword-only parameters
115+ (` return_type ` / ` data_type ` ). Subclasses overriding these methods should add
116+ the parameter to match the base signature.
117+ - ` client.OrchestrationState ` gained a non-public ` _data_converter ` field
118+ (excluded from equality and ` repr ` ). Code constructing ` OrchestrationState `
119+ positionally should pass it via the new field or rely on its default.
114120
115121## v1.5.0
116122
0 commit comments