Skip to content

Add FailureDetails.is_caused_by() and fully-qualified error types - #194

Merged
berndverst merged 1 commit into
mainfrom
andystaples-add-is-caused-by
Jul 27, 2026
Merged

Add FailureDetails.is_caused_by() and fully-qualified error types#194
berndverst merged 1 commit into
mainfrom
andystaples-add-is-caused-by

Conversation

@andystaples

Copy link
Copy Markdown
Contributor

Fixes #162.

What

Mirrors .NET's TaskFailureDetails.IsCausedBy<T>() and Java's FailureDetails.isCausedBy(Class) in the Python SDK.

  • New API — FailureDetails.is_caused_by(error_type: str | type)

    • Passing an exception type performs a base-type-aware match: it returns True if the failure's type is error_type or any of its subclasses. It does this by walking the target type's already-imported subclass tree (visited-marked for diamond inheritance) and never imports a type by name, so it cannot execute arbitrary code. The safe trade-off is that a subclass whose module hasn't been imported yet won't be matched. Passing a non-exception type raises TypeError.
    • Passing a string matches by name only (no base-type awareness); the name may be qualified ("module.ClassName") or unqualified ("ClassName").
  • Fully-qualified error types (breaking)FailureDetails.error_type, and the errorType value sent over the wire, is now the fully-qualified module.ClassName (e.g. builtins.ValueError, durabletask.task.TaskFailedError) instead of the bare class name. This matches how the .NET (GetType().ToString()) and Java (getClass().getName()) SDKs already emit error types. A new get_qualified_name() helper is wired into new_failure_details and the in-memory backend. Protocol sentinels such as "VersionMismatch" are unchanged.

Why fully-qualified names

The other SDKs store fully-qualified names, which is exactly how they avoid confusing same-named exceptions from different modules. Python previously stored only the bare type(ex).__name__, so this aligns the Python SDK with the rest and lets is_caused_by() disambiguate reliably.

Compatibility notes

  • Breaking: code comparing error_type against a bare name (e.g. == "ValueError") must switch to the qualified name or, preferably, to FailureDetails.is_caused_by().
  • Back-compat: because this value is persisted and crosses the orchestration boundary, failures produced by older workers may still carry a bare name; is_caused_by() accepts both bare and qualified forms.
  • durabletask.azuremanaged inherits the change; its changelog references the core notes.

Design decision worth a look

Builtin exceptions keep their builtins. prefix (ValueErrorbuiltins.ValueError), faithful to .NET/Java keeping System./java.lang.. Easy to strip if reviewers prefer the shorter form.

Testing

  • Added tests/durabletask/test_failure_details.py (22 cases: exact/base-type/string/legacy-bare/FQN-disambiguation/error paths + end-to-end via TaskFailedError.details).
  • Updated existing assertions that expected bare names.
  • Core suite: 759 passed, 9 skipped. flake8 clean; pyright (strict) clean on changed source.

Mirrors .NET's TaskFailureDetails.IsCausedBy<T>() and Java's
FailureDetails.isCausedBy(Class). Adds FailureDetails.is_caused_by(),
which is base-type aware for exception types (via a subclass walk over
already-imported classes, with no arbitrary imports) and name-based for
strings.

BREAKING: FailureDetails.error_type (and the errorType wire value) now
uses the fully-qualified type name (e.g. builtins.ValueError,
durabletask.task.TaskFailedError) instead of the bare class name, to
match the .NET and Java SDKs. Bare names produced by older/foreign
workers are still matched for back-compat.

Fixes #162

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 95968239-5578-4d49-a049-31ab007b18d8
Copilot AI review requested due to automatic review settings July 24, 2026 21:16

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.

Pull request overview

Adds a failure-introspection helper to the Durable Task Python SDK and aligns error type serialization with other Durable Task SDKs by switching to fully-qualified exception type names. This improves reliability when diagnosing failures (especially for same-named exceptions across modules) and provides a first-class alternative to string comparisons.

Changes:

  • Added FailureDetails.is_caused_by(error_type: str | type) with base-type-aware matching for exception types (without importing types by name).
  • Changed failure error_type/errorType emission to fully-qualified module.ClassName (e.g., builtins.ValueError) while keeping protocol sentinels (e.g., VersionMismatch) unchanged.
  • Updated/added tests and documented the breaking behavior change in both core and azuremanaged changelogs.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
durabletask/task.py Adds FailureDetails.is_caused_by() and supporting matching helpers; TaskFailedError.details now naturally reflects the qualified errorType.
durabletask/internal/helpers.py Introduces get_qualified_name() and uses it to emit fully-qualified errorType values from new_failure_details().
durabletask/testing/in_memory_backend.py Updates in-memory backend failure emission to use fully-qualified errorType names.
tests/durabletask/test_failure_details.py Adds focused unit tests for get_qualified_name() and FailureDetails.is_caused_by() (including legacy bare-name back-compat and error paths).
tests/durabletask/test_worker_activity_hooks.py Updates assertion to expect fully-qualified error type for builtin exceptions.
tests/durabletask/test_orchestration_executor.py Updates multiple failure-type assertions to expect fully-qualified error type strings.
tests/durabletask/test_orchestration_e2e.py Updates end-to-end failure assertions to expect fully-qualified FailureDetails.error_type.
tests/durabletask/entities/test_entity_failure_handling.py Updates entity failure assertions to expect fully-qualified FailureDetails.error_type.
tests/durabletask-azuremanaged/test_dts_orchestration_e2e.py Updates azuremanaged E2E assertions to expect fully-qualified FailureDetails.error_type.
tests/durabletask-azuremanaged/test_dts_async_orchestration_e2e.py Updates async E2E assertions (including qualified JsonEncodeOutputException).
tests/durabletask-azuremanaged/entities/test_dts_entity_failure_handling.py Updates entity failure assertions under azuremanaged to expect fully-qualified FailureDetails.error_type.
CHANGELOG.md Documents the new API and the breaking change to fully-qualified failure type names under ## Unreleased.
durabletask-azuremanaged/CHANGELOG.md Notes inherited breaking change and new helper, pointing to core changelog for details.

Comment thread durabletask/internal/helpers.py
@berndverst
berndverst merged commit 4d87430 into main Jul 27, 2026
19 checks passed
@berndverst
berndverst deleted the andystaples-add-is-caused-by branch July 27, 2026 05:43
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.

Add FailureDetails.is_caused_by() to mirror .NET's TaskFailureDetails.IsCausedBy<T>()

3 participants