Skip to content

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

Description

@andystaples

Summary

The .NET SDK exposes TaskFailureDetails.IsCausedBy<T>() / IsCausedBy(Type), which lets a caller introspect an orchestration/activity failure and ask "was this caused by exception type T?" (with base-type support). The Python FailureDetails (durabletask/task.py) exposes message, error_type, and stack_trace, but has no equivalent introspection helper — callers must compare failure_details.error_type against a type name string by hand.

This is a general-purpose gap in the Python SDK's failure-introspection surface, not specific to any one feature.

Background / motivation

This came up during the scheduled-tasks PR (#160). The schedule client surfaces entity-side failures (e.g. an invalid state transition) as a generic RuntimeError carrying the failure message — which matches the current .NET ScheduledTasks client behavior (it throws a generic InvalidOperationException with FailureDetails.ErrorMessage). A more Pythonic API would let callers do something like:

try:
    schedule_client.pause()
except OrchestrationError as e:
    if e.failure_details.is_caused_by(ScheduleInvalidTransitionError):
        ...

But the building block for that — failure-type introspection — doesn't exist yet. Rather than special-case it in the scheduled-tasks client, the framework primitive should land first.

Proposed API

Add to durabletask.task.FailureDetails:

def is_caused_by(self, error_type: str | type) -> bool:
    """Return True if this failure's error_type matches `error_type`.

    Accepts either an exception type or its (qualified or unqualified) name.
    """

Design notes / open questions:

  • .NET matches by attempting to load the type named in ErrorType and checking assignability (base-type aware). Python could do something analogous (import the type and use issubclass), or a simpler/safer name-based match. Worth deciding how much "load arbitrary type by name" behavior we want, given the security posture of the rest of the SDK.
  • Python's error_type is the bare class name (e.g. ScheduleInvalidTransitionError), not namespace-qualified like .NET, so a name-based comparison is straightforward but won't disambiguate same-named types from different modules.
  • Should this also consider InnerFailure/chained causes once nested failure details are exposed?

Related

Out of scope

Reconstructing fully-typed exceptions (e.g. re-raising ScheduleInvalidTransitionError with its structured fields) is a separate, larger concern — the structured fields don't survive the orchestration boundary today (only the formatted message does), so that would need an additional from_failure(message)-style constructor convention. This issue is only about the is_caused_by introspection primitive.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions