Skip to content

fix(reports): catch TypeError when validating non-string extra.dashboard.anchor - #44404

Open
eschutho wants to merge 2 commits into
masterfrom
fix-report-extra-anchor-typeerror
Open

eschutho wants to merge 2 commits into
masterfrom
fix-report-extra-anchor-typeerror

Conversation

@eschutho

@eschutho eschutho commented Sep 17, 2026

Copy link
Copy Markdown
Member

SUMMARY

_validate_report_extra in superset/commands/report/base.py reads extra.dashboard.anchor from a report/alert schedule's extra field. That field is an untyped marshmallow Dict (superset/reports/schemas.py: extra = fields.Dict(dump_default=None)), so an API caller can send any JSON value for anchor, not just a string.

The code does:

if anchor := dashboard_state.get("anchor"):
    try:
        anchor_list: list[str] = json.loads(anchor)
        if _invalid_tab_ids := set(anchor_list) - set(position_data.keys()):
            invalid_tab_ids.update(_invalid_tab_ids)
    except json.JSONDecodeError:
        ...

PROBLEM

json.loads() requires a str/bytes/bytearray. When anchor is a non-string scalar (int, float, bool), json.loads(anchor) raises a raw TypeError, which is not a subclass of json.JSONDecodeError (JSONDecodeError is a ValueError; TypeError is unrelated). A related gap: when anchor is a string that parses to a non-iterable scalar (e.g. "42"42), set(anchor_list) also raises TypeError. Neither is caught by except json.JSONDecodeError.

A further gap in the same bug class: for a non-empty anchor that is a list or dict, json.loads(anchor) raises TypeError, but the except body's anchor not in position_data membership test (and the subsequent set.add) then raises a fresh, uncaught TypeError: unhashable type: 'list', since invalid_tab_ids is a set[str] and dict/set membership requires hashing the key.

The uncaught TypeError propagates out of _validate_report_extravalidate()CreateReportScheduleCommand.run() / UpdateReportScheduleCommand.run(). The REST API layer (superset/reports/api.py) only catches ReportScheduleInvalidError/ReportScheduleNotFoundError/ReportScheduleCreateFailedError, so this surfaces as an opaque 500 instead of the 422 validation response every other malformed-input case in this same method produces.

FIX

Widen the except to also catch TypeError:

except (json.JSONDecodeError, TypeError):

and guard the except body on isinstance(anchor, str) so a non-string (unhashable) anchor is recorded via its string form instead of being hashed in the membership test / set.add:

except (json.JSONDecodeError, TypeError):
    anchor_id = anchor if isinstance(anchor, str) else str(anchor)
    if not isinstance(anchor, str) or anchor not in position_data:
        invalid_tab_ids.add(anchor_id)

All failure modes then fall through to the existing branch and are collected as a ValidationError on the extra field, yielding a proper 422. Behavior is unchanged for string/int/float/bool anchors (all hashable, isinstance(anchor, str) short-circuits correctly); only the previously-crashing list/dict case changes, turning into a clean ValidationError like every other malformed-anchor case in this method.

This is the same sibling-site-drift pattern as #42401 and the validation-time precedent already established in this same file for position_json/json_metadata (the json.JSONDecodeError handling a few lines above). Note: superset/commands/report/execute.py's own anchor handling (_get_url) catches only json.JSONDecodeError and has the identical unguarded gap for a non-string anchor — left as a follow-up, out of scope for this PR.

TESTING INSTRUCTIONS

  • Added test_validate_report_extra_anchor_non_string_type in tests/unit_tests/commands/report/create_test.py ("extra": {"dashboard": {"anchor": 42}}), asserting one ValidationError on the extra field. Confirmed it raises TypeError on pre-fix code and passes after the fix.
  • Added test_validate_report_extra_anchor_non_string_unhashable_type ("extra": {"dashboard": {"anchor": [1, 2]}}), asserting one ValidationError on the extra field. Confirmed it raises TypeError: unhashable type: 'list' on the pre-fix code and passes after the fix.
  • Full file green: pytest tests/unit_tests/commands/report/create_test.py → 15 passed.
  • ruff check, ruff format --check, and mypy clean on the changed files.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
  • Introduces new feature or API
  • Removes existing feature or API

…ard.anchor

extra.dashboard.anchor comes from the untyped marshmallow Dict `extra` field
on a report/alert schedule, so an API caller can send any JSON value. When it
is a non-string scalar (int, float, bool) or a JSON string that parses to a
scalar, json.loads(anchor)/set(...) raises a raw TypeError that is not a
subclass of json.JSONDecodeError, so it escapes _validate_report_extra and
surfaces as an opaque 500 instead of the 422 every other malformed-input case
here produces. Widen the except to (json.JSONDecodeError, TypeError), matching
the existing precedent in execute.py's _get_url for the same field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bito-code-review

bito-code-review Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #4e30ce

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: b83ba26..b83ba26
    • superset/commands/report/base.py
    • tests/unit_tests/commands/report/create_test.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@netlify

netlify Bot commented Sep 17, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit b83ba26
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6aac6487986c43000869d51e
😎 Deploy Preview https://deploy-preview-44404--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.40%. Comparing base (a01f507) to head (c38d4e3).
⚠️ Report is 58 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #44404      +/-   ##
==========================================
+ Coverage   80.28%   80.40%   +0.11%     
==========================================
  Files        2928     2934       +6     
  Lines      174122   174991     +869     
  Branches    40404    40547     +143     
==========================================
+ Hits       139790   140693     +903     
+ Misses      31675    31633      -42     
- Partials     2657     2665       +8     
Flag Coverage Δ
hive 37.25% <0.00%> (+0.08%) ⬆️
mysql 56.50% <75.00%> (-0.09%) ⬇️
postgres 56.52% <75.00%> (-0.10%) ⬇️
presto 39.14% <0.00%> (-0.10%) ⬇️
python 84.87% <100.00%> (+0.18%) ⬆️
sqlite 56.23% <75.00%> (-0.08%) ⬇️
unit 76.61% <100.00%> (+0.32%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@eschutho

Copy link
Copy Markdown
Member Author

@richardfogaca routing this one your way. You reviewed and approved #44204, which added the except json.JSONDecodeError: in _validate_report_extra that this PR widens to except (json.JSONDecodeError, TypeError): — so you already have the context on this exact block.

One behavioral change to ratify (PR is agent-assisted): a non-string extra.dashboard.anchor (e.g. int 42, or a string like "42" that json.loads parses to a non-iterable) now surfaces as a 422 ValidationError instead of an unhandled 500 — bringing the validate-time path in line with the execute.py sibling that already catches both. No other behavior change; a regression test that fails on the pre-fix code is included, and CI is green.

You're carrying a few reviews already — glad to re-route if you'd rather pass.

…ation

A non-string anchor (list/dict) makes json.loads raise TypeError, and the
except body then hashed the raw value via `anchor not in position_data`
(and set.add), leaking a fresh, uncaught TypeError for the same bug class
this PR targets. Guard on isinstance(anchor, str) so non-string anchors are
recorded via their string form and surface a clean ValidationError.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@pull-request-size pull-request-size Bot added size/M and removed size/S labels Sep 18, 2026
@bito-code-review

bito-code-review Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #736ab9

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: b83ba26..c38d4e3
    • superset/commands/report/base.py
    • tests/unit_tests/commands/report/create_test.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant