Skip to content

Commit fe1ea6b

Browse files
Sourabhchrs93claude
andcommitted
fix: [AI-7435] accept dbt 2.0 reused run status in run_results v6 parser
dbt 2.0 emits `status="reused"` for unchanged models. The `run_results` v6 `Status` enum only knew `success`/`error`/`skipped`/`partial_success`, so `Result` validation raised `ValidationError` and the ingestion worker silently dropped the entire `run_results.json` — run status/timing never persisted while the UI still showed a clean successful sync. - Add `reused` to `Status` and make it a `str, Enum`. - Add `Status._missing_` so future unknown dbt statuses surface as real members instead of failing validation (forward-compatibility). - Reorder `Result.status` union to `Union[Status1, Status2, Status]` so the now permissive run `Status` is tried last, keeping test/freshness statuses (`pass`/`fail`/`warn`/`runtime error`) resolving via the strict enums. - Add regression tests covering `reused`, known statuses, unknown-future status, test/freshness statuses, and full-file `parse_run_results`. - Bump version to `0.3.2` (`0.3.1` already released without this fix). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e347ea4 commit fe1ea6b

4 files changed

Lines changed: 92 additions & 4 deletions

File tree

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def read(*names, **kwargs):
1313

1414
setup(
1515
name="altimate-datapilot-cli",
16-
version="0.3.1",
16+
version="0.3.2",
1717
license="MIT",
1818
description="Assistant for Data Teams",
1919
long_description="{}\n{}".format(

src/datapilot/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.3.1"
1+
__version__ = "0.3.2"

src/vendor/dbt_artifacts_parser/parsers/run_results/run_results_v6.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,23 @@ class Metadata(BaseParserModel):
2525
env: Optional[dict[str, str]] = None
2626

2727

28-
class Status(Enum):
28+
class Status(str, Enum):
2929
success = "success"
3030
error = "error"
3131
skipped = "skipped"
3232
partial_success = "partial success"
33+
reused = "reused"
34+
35+
@classmethod
36+
def _missing_(cls, value):
37+
# Forward-compatibility: dbt periodically introduces new run statuses
38+
# (e.g. "reused" in dbt 2.0). Surface unknown values as real members so
39+
# downstream `.value` access keeps working instead of failing validation
40+
# and silently dropping the entire run_results.json.
41+
member = str.__new__(cls, value)
42+
member._name_ = str(value)
43+
member._value_ = value
44+
return member
3345

3446

3547
class Status1(Enum):
@@ -74,7 +86,7 @@ class Result(BaseParserModel):
7486
model_config = ConfigDict(
7587
extra="allow",
7688
)
77-
status: Union[Status, Status1, Status2]
89+
status: Union[Status1, Status2, Status]
7890
timing: list[TimingItem]
7991
thread_id: str
8092
execution_time: float
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Tests for run_results v6 parser, specifically the resilient run `Status` enum.
2+
3+
Regression coverage for dbt 2.0 emitting ``status="reused"`` (and future unknown
4+
statuses), which previously raised a ``ValidationError`` and caused the entire
5+
run_results.json to be silently dropped during ingestion.
6+
"""
7+
from vendor.dbt_artifacts_parser.parser import parse_run_results
8+
from vendor.dbt_artifacts_parser.parsers.run_results.run_results_v6 import Result, Status
9+
10+
V6_SCHEMA = "https://schemas.getdbt.com/dbt/run-results/v6.json"
11+
12+
13+
def _result(status: str, unique_id: str) -> dict:
14+
return {
15+
"status": status,
16+
"timing": [],
17+
"thread_id": "Thread-1",
18+
"execution_time": 0.1,
19+
"adapter_response": {},
20+
"unique_id": unique_id,
21+
}
22+
23+
24+
def _run_results(*statuses: str) -> dict:
25+
return {
26+
"metadata": {
27+
"dbt_schema_version": V6_SCHEMA,
28+
"dbt_version": "2.0.0",
29+
"invocation_id": "test-invocation-123",
30+
},
31+
"elapsed_time": 1.5,
32+
"args": {},
33+
"results": [_result(s, f"model.proj.m{i}") for i, s in enumerate(statuses)],
34+
}
35+
36+
37+
class TestRunResultStatus:
38+
"""The run `Status` enum must accept new/unknown dbt statuses without failing."""
39+
40+
def test_reused_status_parses(self):
41+
"""dbt 2.0 emits `reused` for unchanged models; it must not fail validation."""
42+
result = Result(**_result("reused", "model.proj.a"))
43+
assert result.status.value == "reused"
44+
assert result.status is Status.reused
45+
46+
def test_known_statuses_preserve_value(self):
47+
"""Known run statuses still resolve with the correct `.value`."""
48+
for status in ("success", "error", "skipped", "partial success"):
49+
assert Result(**_result(status, "model.proj.a")).status.value == status
50+
51+
def test_unknown_future_status_parses(self):
52+
"""Forward-compat: a status dbt has not shipped yet still parses via `_missing_`."""
53+
result = Result(**_result("some_future_status", "model.proj.a"))
54+
assert result.status.value == "some_future_status"
55+
56+
def test_test_and_freshness_statuses_keep_their_value(self):
57+
"""Test/freshness statuses must keep their `.value` (resolved via Status1/Status2)."""
58+
for status in ("pass", "fail", "warn", "runtime error"):
59+
assert Result(**_result(status, "test.proj.t")).status.value == status
60+
61+
62+
class TestParseRunResultsEntryPoint:
63+
"""The public `parse_run_results` must parse a full file containing `reused`."""
64+
65+
def test_file_with_reused_result_parses_fully(self):
66+
run_results = parse_run_results(_run_results("success", "reused", "skipped", "pass", "error", "no-op"))
67+
# Previously this whole file was dropped because of the single `reused` row.
68+
assert len(run_results.results) == 6
69+
assert {r.status.value for r in run_results.results} == {
70+
"success",
71+
"reused",
72+
"skipped",
73+
"pass",
74+
"error",
75+
"no-op",
76+
}

0 commit comments

Comments
 (0)