Skip to content

Commit 8e3bb37

Browse files
committed
Fix FailureDetails JSON serialization and cut v1.7.2
Convert task.FailureDetails to a frozen dataclass so failed orchestrations serialize cleanly through the history_export module. Previously FailureDetails was a plain class, so HistoryEvent.to_dict() left it as a raw object and json.dumps raised TypeError for ExecutionCompleted, TaskFailed, SubOrchestrationInstanceFailed, and EntityOperationFailed events. Bump both packages to v1.7.2 and update changelogs.
1 parent 465c4de commit 8e3bb37

6 files changed

Lines changed: 43 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## Unreleased
99

10+
## v1.7.2
11+
12+
FIXED
13+
14+
- Fixed history export failing for orchestrations that completed with an error. Events carrying failure information (orchestration completion, activity failure, sub-orchestration failure, and entity operation failure) were not JSON-serializable, causing the `durabletask.extensions.history_export` module to raise `TypeError: Object of type FailureDetails is not JSON serializable`. `FailureDetails` now serializes to a JSON object with `message`, `error_type`, and `stack_trace` fields.
15+
1016
## v1.7.1
1117

1218
ADDED

durabletask-azuremanaged/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## Unreleased
99

10+
## v1.7.2
11+
12+
- Updates base dependency to durabletask v1.7.2.
13+
1014
## v1.7.1
1115

1216
- Updates base dependency to durabletask v1.7.1

durabletask-azuremanaged/pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
99

1010
[project]
1111
name = "durabletask.azuremanaged"
12-
version = "1.7.1"
12+
version = "1.7.2"
1313
description = "Durable Task Python SDK provider implementation for the Azure Durable Task Scheduler"
1414
keywords = [
1515
"durable",
@@ -26,13 +26,13 @@ requires-python = ">=3.10"
2626
license = {file = "LICENSE"}
2727
readme = "README.md"
2828
dependencies = [
29-
"durabletask>=1.7.1",
29+
"durabletask>=1.7.2",
3030
"azure-identity>=1.19.0"
3131
]
3232

3333
[project.optional-dependencies]
3434
azure-blob-payloads = [
35-
"durabletask[azure-blob-payloads]>=1.7.1"
35+
"durabletask[azure-blob-payloads]>=1.7.2"
3636
]
3737

3838
[project.urls]

durabletask/task.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import math
99
from abc import ABC, abstractmethod
1010
from collections.abc import Callable, Generator, Sequence
11+
from dataclasses import dataclass
1112
from datetime import datetime, timedelta, timezone
1213
from typing import TYPE_CHECKING, Any, Generic, TypeAlias, TypeVar, cast, overload
1314

@@ -449,23 +450,11 @@ def isEnabledFor(self, level: int) -> bool:
449450
return self.logger.isEnabledFor(level)
450451

451452

453+
@dataclass(frozen=True)
452454
class FailureDetails:
453-
def __init__(self, message: str, error_type: str, stack_trace: str | None):
454-
self._message = message
455-
self._error_type = error_type
456-
self._stack_trace = stack_trace
457-
458-
@property
459-
def message(self) -> str:
460-
return self._message
461-
462-
@property
463-
def error_type(self) -> str:
464-
return self._error_type
465-
466-
@property
467-
def stack_trace(self) -> str | None:
468-
return self._stack_trace
455+
message: str
456+
error_type: str
457+
stack_trace: str | None
469458

470459

471460
class TaskFailedError(Exception):

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
99

1010
[project]
1111
name = "durabletask"
12-
version = "1.7.1"
12+
version = "1.7.2"
1313
description = "A Durable Task Client SDK for Python"
1414
keywords = [
1515
"durable",

tests/durabletask/extensions/history_export/test_serialization.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,30 @@ def test_includes_type_discriminator(self) -> None:
6666
assert d["event_id"] == 0
6767
assert d["timestamp"].startswith("2025-01-01")
6868

69+
def test_failure_details_is_json_safe(self) -> None:
70+
# Regression: FailureDetails-bearing events must serialize to a
71+
# JSON-safe dict rather than a raw FailureDetails object.
72+
e = history.ExecutionCompletedEvent(
73+
event_id=-1,
74+
timestamp=datetime(2025, 1, 1, tzinfo=timezone.utc),
75+
orchestration_status=3,
76+
result=None,
77+
failure_details=task.FailureDetails(
78+
message="boom", error_type="RuntimeError", stack_trace="trace",
79+
),
80+
)
81+
d = event_to_dict(e)
82+
assert d["failure_details"] == {
83+
"message": "boom",
84+
"error_type": "RuntimeError",
85+
"stack_trace": "trace",
86+
}
87+
# The full document must be JSON-serializable.
88+
fmt = ExportFormat(kind=ExportFormatKind.JSON)
89+
out = serialize_history([e], instance_id="fail-1", fmt=fmt)
90+
doc = json.loads(out)
91+
assert doc["events"][0]["failure_details"]["error_type"] == "RuntimeError"
92+
6993

7094
class TestSerializeJson:
7195
def test_envelope_fields(self) -> None:

0 commit comments

Comments
 (0)