Add FailureDetails.is_caused_by() and fully-qualified error types - #194
Merged
Conversation
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
Contributor
There was a problem hiding this comment.
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/errorTypeemission to fully-qualifiedmodule.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. |
berndverst
reviewed
Jul 24, 2026
berndverst
approved these changes
Jul 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #162.
What
Mirrors .NET's
TaskFailureDetails.IsCausedBy<T>()and Java'sFailureDetails.isCausedBy(Class)in the Python SDK.New API —
FailureDetails.is_caused_by(error_type: str | type)Trueif the failure's type iserror_typeor 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 raisesTypeError."module.ClassName") or unqualified ("ClassName").Fully-qualified error types (breaking) —
FailureDetails.error_type, and theerrorTypevalue sent over the wire, is now the fully-qualifiedmodule.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 newget_qualified_name()helper is wired intonew_failure_detailsand 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 letsis_caused_by()disambiguate reliably.Compatibility notes
error_typeagainst a bare name (e.g.== "ValueError") must switch to the qualified name or, preferably, toFailureDetails.is_caused_by().is_caused_by()accepts both bare and qualified forms.durabletask.azuremanagedinherits the change; its changelog references the core notes.Design decision worth a look
Builtin exceptions keep their
builtins.prefix (ValueError→builtins.ValueError), faithful to .NET/Java keepingSystem./java.lang.. Easy to strip if reviewers prefer the shorter form.Testing
tests/durabletask/test_failure_details.py(22 cases: exact/base-type/string/legacy-bare/FQN-disambiguation/error paths + end-to-end viaTaskFailedError.details).