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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions