Skip to content

fix(dbt): skip unusable models/columns in seed payloads - #2549

Closed
Bartok9 wants to merge 1 commit into
Canner:mainfrom
Bartok9:fix/dbt-seed-payload-guards
Closed

fix(dbt): skip unusable models/columns in seed payloads#2549
Bartok9 wants to merge 1 commit into
Canner:mainfrom
Bartok9:fix/dbt-seed-payload-guards

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • _seed_model_payload / _build_dbt_query_pairs assumed dict models and named columns.
  • Partial dbt imports with junk rows raised TypeError/KeyError during seed pair generation.
  • Return None for unusable models/relationships, skip bad columns, harden _camelize_props.

Verification

$ cd core/wren && .venv/bin/python -m pytest tests/unit/test_dbt_seed_payload_guards.py -q
5 passed

Apache-2.0: core/wren/** only.

Summary by CodeRabbit

  • Bug Fixes
    • Improved dbt seed-query generation by safely handling missing or malformed model, relationship, column, and property data.
    • Invalid model/relationship inputs are now skipped, preventing crashes during manifest construction.
    • Ensures only valid entries contribute to generated seed queries, while preserving correct source and data source annotations.
  • Tests
    • Added unit tests covering defensive payload guards and tolerance behaviors for malformed inputs.

@github-actions github-actions Bot added python Pull requests that update Python code core labels Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e1de7552-75d2-48eb-8c97-8a0e960fdd6a

📥 Commits

Reviewing files that changed from the base of the PR and between 12abbed and 3cb64a0.

📒 Files selected for processing (2)
  • core/wren/src/wren/dbt.py
  • core/wren/tests/unit/test_dbt_seed_payload_guards.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/wren/tests/unit/test_dbt_seed_payload_guards.py
  • core/wren/src/wren/dbt.py

Walkthrough

dbt seed-query generation now validates model, relationship, column, and property inputs. Invalid payloads are skipped, malformed properties are normalized, and tests cover helper guards plus filtered query-pair generation.

Changes

dbt seed payload guards

Layer / File(s) Summary
Validate seed payload inputs
core/wren/src/wren/dbt.py, core/wren/tests/unit/test_dbt_seed_payload_guards.py
Seed helpers reject malformed models and relationships, filter invalid columns, tolerate malformed properties, and test these behaviors.
Filter manifest entries before query generation
core/wren/src/wren/dbt.py, core/wren/tests/unit/test_dbt_seed_payload_guards.py
Manifest construction omits invalid helper results, while query-pair tests verify the dbt source and datasource metadata.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • Canner/WrenAI#2424: Adds defensive handling for malformed dbt relationship data during seed-query generation.
  • Canner/WrenAI#2514: Filters malformed column entries in related seed-query generation logic.
  • Canner/WrenAI#2542: Hardens seed-query generation against invalid model, column, and relationship inputs.

Suggested reviewers: goldmedal

Poem

I nibbled bad shapes from the dbt tray,
And skipped crooked columns on the way.
Valid seeds hopped into the query stream,
While None stayed tucked inside a dream.
The manifest now bounds with care—
A tidy burrow everywhere! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main change: hardening dbt seed payload generation by skipping unusable models and columns.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
core/wren/tests/unit/test_dbt_seed_payload_guards.py (1)

50-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add type ignore for intentionally malformed test data.

Other tests explicitly suppress static typing warnings when intentionally bypassing the function signatures with non-dictionary data. Passing None and "bad" inside these lists will likely trigger a mypy error against the list[dict[str, Any]] arguments expected by _build_dbt_query_pairs.

🔕 Proposed fix to silence mypy
     pairs = _build_dbt_query_pairs(
-        [None, {"name": "ok", "columns": []}, "bad"],
-        [None, {"name": "r1"}],
+        [None, {"name": "ok", "columns": []}, "bad"],  # type: ignore[list-item]
+        [None, {"name": "r1"}],  # type: ignore[list-item]
         datasource="postgres",
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/wren/tests/unit/test_dbt_seed_payload_guards.py` around lines 50 - 54,
Add a targeted type-ignore annotation to the _build_dbt_query_pairs test call
for the intentionally malformed None and string entries, matching the
suppression style used by nearby tests while preserving the invalid payloads
needed by this guard test.
core/wren/src/wren/dbt.py (1)

1278-1305: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove redundant dictionary checks for properties.

Since _camelize_props has been hardened to accept dict | None and already returns {} when the input is not a dictionary (lines 1319-1321), the inline validation for props and col_props is redundant and can be safely removed.

♻️ Proposed refactor
 def _seed_model_payload(model: dict[str, Any]) -> dict[str, Any] | None:
     """Build seed payload for one imported model, or None if unusable."""
     if not isinstance(model, dict) or not model.get("name"):
         return None
-    props = model.get("properties") or {}
-    if not isinstance(props, dict):
-        props = {}
     columns_out: list[dict[str, Any]] = []
     for column in model.get("columns") or []:
         if not isinstance(column, dict) or not column.get("name"):
             continue
-        col_props = column.get("properties") or {}
-        if not isinstance(col_props, dict):
-            col_props = {}
         columns_out.append(
             {
                 "name": column["name"],
                 "type": column.get("type"),
                 "isCalculated": column.get("is_calculated", False),
-                "properties": _camelize_props(col_props),
+                "properties": _camelize_props(column.get("properties")),
             }
         )
     return {
         "name": model["name"],
         "primaryKey": model.get("primary_key"),
-        "properties": _camelize_props(props),
+        "properties": _camelize_props(model.get("properties")),
         "columns": columns_out,
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/wren/src/wren/dbt.py` around lines 1278 - 1305, Remove the redundant
dictionary type checks and fallback assignments for props and col_props in
_seed_model_payload; pass model.get("properties") and column.get("properties")
directly to _camelize_props, relying on its existing dict-or-None handling while
preserving the current payload behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@core/wren/src/wren/dbt.py`:
- Around line 1278-1305: Remove the redundant dictionary type checks and
fallback assignments for props and col_props in _seed_model_payload; pass
model.get("properties") and column.get("properties") directly to
_camelize_props, relying on its existing dict-or-None handling while preserving
the current payload behavior.

In `@core/wren/tests/unit/test_dbt_seed_payload_guards.py`:
- Around line 50-54: Add a targeted type-ignore annotation to the
_build_dbt_query_pairs test call for the intentionally malformed None and string
entries, matching the suppression style used by nearby tests while preserving
the invalid payloads needed by this guard test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e2f7b5aa-ebd9-4ddb-a490-a245076a112f

📥 Commits

Reviewing files that changed from the base of the PR and between 2b0b335 and 12abbed.

📒 Files selected for processing (2)
  • core/wren/src/wren/dbt.py
  • core/wren/tests/unit/test_dbt_seed_payload_guards.py

_seed_model_payload assumed every column was a dict with name; junk
import rows crashed dbt→seed-query generation. Filter and camelize safely.
@Bartok9
Bartok9 force-pushed the fix/dbt-seed-payload-guards branch from 12abbed to 3cb64a0 Compare July 20, 2026 12:15
@goldmedal

Copy link
Copy Markdown
Collaborator

Suggesting we close this one — and here the guard doesn't just fail to fire, it would hide a bug if it ever did.

Both inputs are produced by our own code, not by dbt. At the only callsite (dbt.py:351):

(imported_models, ...) = _build_imported_models(artifacts)
relationships, test_events = _apply_dbt_test_enrichment(artifacts, imported_models)
query_pairs = _build_dbt_query_pairs(imported_models, relationships, datasource=...)

The dbt artifacts are user input, yes — but imported_models and relationships are dicts we construct from them. So isinstance(model, dict) and not model.get("name") in _seed_model_payload are asserting that _build_imported_models didn't emit a malformed row. Same argument as #2572: a guard that can only fire when our own builder is broken belongs as an invariant, not as per-consumer re-validation.

And the failure mode gets worse, not better. Today a nameless model raises KeyError: 'name' at model["name"] — loud, with a stack trace pointing at the payload builder. After this change _seed_model_payload returns None and the model is silently dropped from the seed manifest, so a real defect in _build_imported_models would surface as "some seed queries are missing" long after the import. Turning a crash into silent data loss is the wrong direction for something that is by construction unreachable.

If there's an actual reproduction where _build_imported_models yields a row without a name, that's the bug worth a PR — in _build_imported_models, with a test driving it from a real dbt artifact fixture.

@Bartok9

Bartok9 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Good point — imported_models/relationships are dicts we construct, so the guard can only fire if _build_imported_models is itself broken, and turning today's loud KeyError: 'name' into a silently-dropped seed model is the wrong direction. Closing. If a real dbt-artifact fixture ever produces a nameless row, the fix belongs in _build_imported_models with a test driving it from that artifact. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core python Pull requests that update Python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants