Skip to content

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

Merged
Bernd Verst (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
Bernd Verst (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
CopilotAI review requested due to automatic review settings July 24, 2026 21:16

CopilotAI 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
FileDescription
durabletask/task.pyAdds FailureDetails.is_caused_by() and supporting matching helpers; TaskFailedError.details now naturally reflects the qualified errorType.
durabletask/internal/helpers.pyIntroduces get_qualified_name() and uses it to emit fully-qualified errorType values from new_failure_details().
durabletask/testing/in_memory_backend.pyUpdates in-memory backend failure emission to use fully-qualified errorType names.
tests/durabletask/test_failure_details.pyAdds 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.pyUpdates assertion to expect fully-qualified error type for builtin exceptions.
tests/durabletask/test_orchestration_executor.pyUpdates multiple failure-type assertions to expect fully-qualified error type strings.
tests/durabletask/test_orchestration_e2e.pyUpdates end-to-end failure assertions to expect fully-qualified FailureDetails.error_type.
tests/durabletask/entities/test_entity_failure_handling.pyUpdates entity failure assertions to expect fully-qualified FailureDetails.error_type.
tests/durabletask-azuremanaged/test_dts_orchestration_e2e.pyUpdates azuremanaged E2E assertions to expect fully-qualified FailureDetails.error_type.
tests/durabletask-azuremanaged/test_dts_async_orchestration_e2e.pyUpdates async E2E assertions (including qualified JsonEncodeOutputException).
tests/durabletask-azuremanaged/entities/test_dts_entity_failure_handling.pyUpdates entity failure assertions under azuremanaged to expect fully-qualified FailureDetails.error_type.
CHANGELOG.mdDocuments the new API and the breaking change to fully-qualified failure type names under ## Unreleased.
durabletask-azuremanaged/CHANGELOG.mdNotes inherited breaking change and new helper, pointing to core changelog for details.

Comment threaddurabletask/internal/helpers.py
@berndverst
Bernd Verst (berndverst) merged commit 4d87430 into mainJul 27, 2026
19 checks passed
@berndverst
Bernd Verst (berndverst) deleted the andystaples-add-is-caused-by branch July 27, 2026 05:43
Sign up for freeto 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

@andystaples@berndverst