Skip to content

fix(serialization): support dict, Mapping, and tuple container traversal in can_reconstruct - #346

Merged
Bernd Verst (berndverst) merged 10 commits into
microsoft:mainfrom
hardikkaurani:fix/can-reconstruct-dict-tuple
Nov 10, 2026
Merged

Bernd Verst (berndverst) merged 10 commits into
microsoft:mainfrom
hardikkaurani:fix/can-reconstruct-dict-tuple

Conversation

@hardikkaurani

@hardikkaurani Hardik K (hardikkaurani) commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary of Changes

Closes #344

While _coerce_generic() in durabletask/serialization.py already supports type-directed coercion for dict / Mapping and tuple containers, _can_reconstruct() previously omitted support for these types.

As a result, converter.can_reconstruct() returned False for annotations such as dict[str, MyModel], Mapping[str, MyModel], tuple[MyModel, ...], and tuple[MyModel, int].

Consequently, inbound type discovery in type_discovery.activity_input_type and orchestrator_input_type discarded these annotations and returned None, causing activities and orchestrators to receive uncoerced dictionaries and lists instead of typed model instances.

Changes Across Commits

  1. Commit 1: fix(serialization): add dict and Mapping container support to can_reconstruct

    • Added an origin in (dict, Mapping) check in _can_reconstruct() to recursively inspect the value type (args[1]) when len(args) == 2.
  2. Commit 2: fix(serialization): add homogeneous and heterogeneous tuple container support to can_reconstruct

    • Added an origin is tuple check in _can_reconstruct(), supporting both homogeneous tuple[T, ...] and fixed-length heterogeneous tuple[T1, T2, ...] annotations.
  3. Commit 3: test(serialization): verify can_reconstruct with dict, Mapping, and tuple types

    • Added unit tests verifying that can_reconstruct() returns True for dictionaries, mappings, and tuples of dataclasses, including nested container compositions.
  4. Commit 4: test(worker): verify type discovery and activity input reconstruction for dict and tuple containers

    • Added worker execution tests verifying that activities annotated with dict[str, Order] and tuple[Order, ...] correctly receive reconstructed dataclass instances.
  5. Commit 5: docs(serialization): document dict, Mapping, and tuple traversal in can_reconstruct

    • Updated the _can_reconstruct() docstring to document container recursion across wrapped type hints.

Verification

  • pytest tests/durabletask/test_serialization.py tests/durabletask/test_type_discovery.py
  • 137 tests passed.

…onstruct

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
… support to can_reconstruct

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
…uple types

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
… for dict and tuple containers

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
…an_reconstruct

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
Copilot AI balanced review requested due to automatic review settings November 5, 2026 17:26
@hardikkaurani

Hardik K (hardikkaurani) commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi Bernd Verst (@berndverst) andystaples,

I've opened this PR to resolve #344 across five focused commits.

_can_reconstruct() has been updated to traverse dict/Mapping value types and support both homogeneous (tuple[T, ...]) and heterogeneous (tuple[T1, T2, ...]) tuple containers.

This ensures that inbound type discovery in activity and orchestrator executors preserves these annotations, allowing _coerce_generic() to reconstruct nested models and dataclasses as intended.

Comprehensive unit and worker-level tests have also been added to verify the behavior.

Thank you for your time!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@andystaples andystaples left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a replay-breaking bug fix that can be enabled by default without opt-in, provided the compatibility impact is explicitly scoped and covered. The accepted replay incompatibility is where previously rejected container annotations now cause recorded payloads to be reconstructed differently, including orchestrator inputs and inferred activity results; this does not mean every affected orchestration was already failing.

Please update the core and Azure Managed Unreleased changelogs following the existing Replay-breaking bug fix precedent: describe the affected annotations, warn about replay failures or changed behavior, and include guidance for keeping affected instances on the previous SDK. Azure Functions overrides can_reconstruct() to always return True, so this change does not alter its discovery behavior and does not need a changelog entry claiming otherwise.

Please add regression tests demonstrating the affected old-history replay behavior for orchestration inputs and inferred activity results, and confirming unaffected annotations, explicit reconstruction, and custom converter policies remain unchanged. Include coverage verifying that Azure Functions remains unaffected.

… for dict and tuple containers

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
…ions unaffected coverage

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
… core and azuremanaged changelogs

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
@hardikkaurani

Copy link
Copy Markdown
Contributor Author

Hi andystaples,

I've pushed an update addressing the feedback across three focused commits:

  1. Replay Regression & Non-Determinism Tests (test_orchestration_executor.py):

    • Demonstrated old-history replay reconstruction for dict[str, Model] and tuple[Model, ...] across orchestrator inputs and inferred activity return types.
    • Covered the replay incompatibility where existing workflows branching on uncoerced dict/tuple payloads evaluate differently and fail with NonDeterminismError.
    • Confirmed that unaffected builtin container annotations (dict[str, int]) and explicit return_type overrides remain unchanged during replay.
  2. Custom Converter Policy & Azure Functions Coverage (test_type_discovery.py, test_converters.py):

    • Verified that custom converter subclasses overriding can_reconstruct() (such as selectively denying tuple or extending to custom models) have their policies respected during type discovery.
    • Added tests in azure-functions-durable confirming FunctionsDataConverter.can_reconstruct() continues to return True uniformly, keeping Azure Functions discovery behavior unchanged.
  3. Changelogs (CHANGELOG.md, durabletask-azuremanaged/CHANGELOG.md):

    • Updated the core and Azure Managed Unreleased changelogs adhering to the Replay-breaking bug fix precedent.
    • Documented the affected container annotations (dict, Mapping, and tuple wrapping reconstructable types), warned about replay impact and failure modes, scoped unaffected annotations and Azure Functions, and provided instance draining/pinning guidance.

All unit and executor tests pass cleanly. Whenever convenient, please take another look!

@andystaples andystaples left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The replay regression coverage addresses the earlier concerns. The remaining correction is to shorten and accurately scope the changelog warning, following the repository guidance to narrow an overly broad claim rather than expand it into a catalogue of exceptions.

Please remove the assurance that list, Optional, and custom converter policies remain unchanged. Discovery is recursive, so annotations such as list[dict[str, Order]] and Optional[dict[str, Order]] are affected too, as are custom converters wherever they delegate to the base policy. Rather than documenting all the exceptions, keep the entry focused on the user-visible fix and actionable upgrade risk. The discussion of whether affected orchestrations were already failing can also be removed from the changelog.

Suggested core warning:

Warning

Replay-breaking bug fix: Container annotations involving dict, Mapping,
or tuple wrapping reconstructable types, including nested annotations, now
reconstruct orchestration inputs and inferred activity results by default.
Existing instances that relied on raw dictionaries or lists can change behavior
or fail replay with NonDeterminismError.

Drain affected instances on the previous SDK, or keep their task hub on the
previous SDK and use a separate task hub for new instances. Pin or lock the core
durabletask dependency, including when using the Azure Managed provider.

The Azure Managed entry can briefly describe the inherited fix and link to the core warning rather than repeat it. This is a documentation correction to the accepted replay-breaking fix, not a request for opt-in. I found no new implementation blocker in the updated diff.

…r reconstruction warning

Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
@hardikkaurani

Copy link
Copy Markdown
Contributor Author

Hi andystaples,

Updated both changelogs per the feedback:

  • Refocused the core changelog warning on the user-visible fix and actionable upgrade risks, adopting the suggested warning wording and removing the catalogue of exceptions and claims regarding unaffected containers or custom policies.
  • Updated the Azure Managed changelog entry to concisely describe the inherited fix and link directly to the core warning for replay-breaking upgrade guidance and dependency pinning recommendations.

@berndverst Bernd Verst (berndverst) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the complete diff against the target base. The dict/Mapping and tuple discovery paths now match the existing reconstruction support, including nested annotations and converter policy delegation. I found no actionable introduced defects or material performance concern. This intentionally changes replay behavior for affected existing instances; the regression coverage and core/Azure Managed upgrade warnings now document that risk and the required draining or dependency-pinning guidance. The latest changelog revision addresses the outstanding review feedback.

andystaples
andystaples previously approved these changes Nov 10, 2026
@andystaples

andystaples commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Hardik K (@hardikkaurani) Looks like it fails lint, please update that and any other CI findings and we can merge :)
Edit: We'll take care of it, thanks for the continued contributions

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@andystaples
andystaples dismissed stale reviews from Bernd Verst (berndverst) and themself via 22125b1 November 10, 2026 00:53
@berndverst
Bernd Verst (berndverst) merged commit 4d82397 into microsoft:main Nov 10, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JsonDataConverter.can_reconstruct omits dict/Mapping and tuple containers, skipping type-directed input reconstruction

4 participants