Skip to content

fix(sqla): wrap TypeError from virtual dataset Jinja rendering as a validation error - #44312

Merged
rusackas merged 1 commit into
masterfrom
fix-filter-values-typeerror-get-rendered-sql
Sep 22, 2026
Merged

rusackas merged 1 commit into
masterfrom
fix-filter-values-typeerror-get-rendered-sql

Conversation

@eschutho

Copy link
Copy Markdown
Member

SUMMARY

Fixes a production Sentry crash (SUPERSET-PYTHON-16YX) — TypeError: sequence item 0: expected str instance, int found, surfacing as an unhandled 500 from ChartDataRestApi.data.

Root cause

ExploreMixin.get_rendered_sql (superset/models/helpers.py) renders a virtual dataset's custom SQL through the sandboxed Jinja engine and already catches Jinja-specific exceptions — UndefinedError, TemplateError, and SupersetSyntaxErrorException — re-raising them as a friendly QueryObjectValidationError (400). The gap: a TypeError raised by a Python builtin invoked from within the template is none of those exception types, so it escapes the guard as an unhandled 500.

The concrete trigger is the documented filter_values() usage pattern (from the BaseTemplateProcessor.filter_values() docstring in superset/jinja_context.py):

WHERE action in ({{ "'" + "','".join(filter_values('action_type')) + "'" }})

filter_values() returns list[Any] — filter values come from dashboard/native filters and are not coerced to str. When the filtered column holds numeric values (e.g. an int ID column), filter_values() returns list[int] and str.join() raises TypeError: sequence item 0: expected str instance, int found — exactly this issue's message.

Evidence (Sentry traceback, top of stack first):

superset/common/query_actions.py            _get_full
superset/common/query_context.py            get_df_payload
superset/common/query_context_processor.py  get_df_payload / get_query_result
superset/connectors/sqla/models.py          query
superset/models/helpers.py                  get_query_str_extended / get_sqla_query
superset/connectors/sqla/models.py          get_from_clause
superset/models/helpers.py                  get_from_clause -> get_rendered_sql
superset/jinja_context.py                   process_template
jinja2/environment.py                       render
<template>:16                               top-level template code
jinja2/sandbox.py                           call   <-- str.join on list[int]

Event count / issue: https://preset-inc.sentry.io/issues/7733609759/ (Sentry issue SUPERSET-PYTHON-16YX).

The fix

Add a TypeError except clause in get_rendered_sql alongside the existing Jinja guards, wrapping it in QueryObjectValidationError using the existing message style ("Error while rendering virtual dataset query: %(msg)s"). This turns the crash into the same friendly 400 that Jinja-syntax errors already produce. This mirrors the guard pattern established by #44172 for get_timestamp_expression/convert_tbl_column_to_sqla_col in the same class (which did not touch get_rendered_sql).

TESTING INSTRUCTIONS

Added a regression unit test test_get_rendered_sql_wraps_type_error in tests/unit_tests/connectors/sqla/models_test.py, matching the existing style there (ExploreMixin.get_rendered_sql.__get__(datasource), template_processor.process_template.side_effect = TypeError(...), assert QueryObjectValidationError).

Test run:

$ pytest tests/unit_tests/connectors/sqla/models_test.py -q
86 passed in 2.82s

Lint:

$ ruff check superset/models/helpers.py tests/unit_tests/connectors/sqla/models_test.py
All checks passed!
$ ruff format --check superset/models/helpers.py tests/unit_tests/connectors/sqla/models_test.py
2 files already formatted

To reproduce manually: create a virtual dataset whose SQL uses {{ "','".join(filter_values('some_numeric_col')) }}, apply a dashboard filter on a numeric column, and load the chart. Before this change the request returns a 500; after it returns a 400 QueryObjectValidationError.

TRADEOFFS

  • The guard is intentionally scoped to TypeError only. Other non-Jinja exception types (e.g. ValueError, KeyError) raised from Python builtins invoked inside user template macros will still surface as 500s. Broadening get_rendered_sql to a catch-all Exception would be a larger behavioral change (it could mask genuinely unexpected server errors as user-facing validation errors) and is out of scope here — flagged as a possible future follow-up, not fixed in this PR.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

Shortcut: https://app.shortcut.com/preset/story/121034

🤖 Generated with Claude Code

@bito-code-review

bito-code-review Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #cd5e21

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 13b5b83..13b5b83
    • superset/models/helpers.py
    • tests/unit_tests/connectors/sqla/models_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

@eschutho

Copy link
Copy Markdown
Member Author

@rebenitez1802 — assigning you as reviewer here for continuity on the Jinja template-error / raw-exception-leak bug-class you've carried end-to-end (#42366, #42401, and most recently #44172, also in ExploreMixin/superset/models/helpers.py).

What it is: ExploreMixin.get_rendered_sql only caught UndefinedError/TemplateError/SupersetSyntaxErrorException, so a TypeError from str.join() on the list[int] that filter_values() returns (virtual-dataset SQL template joining a numeric-column filter) escaped as an unhandled 500 (Sentry SUPERSET-PYTHON-16YX, culprit ChartDataRestApi.data). The fix adds a narrowly-scoped except TypeError around the template_processor.process_template(sql) call, mapping it to the QueryObjectValidationError (400) its sibling exception types already get. +32/-0 across superset/models/helpers.py and a regression test.

Note given #44172: the self-review empirically verified (live MRO/dispatch check under an initialized app) that the patched ExploreMixin.get_rendered_sql is the method actually dispatched on the real path (SqlaTable → ExploreMixin, no override) — i.e. this is not a repeat of the #44172 dead-code-guard trap where the guard sat on a class the runtime never hit. Worth confirming independently since you're the one who caught that last time.

Load flag: you're carrying the highest active-review count on the roster right now — happy to re-route this to another roster reviewer if you'd rather not take it; just say the word.

@rebenitez1802 rebenitez1802 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.

Clean, correctly-scoped defensive fix — turns a real production 500 into a 400, tested, no security-boundary impact. Approving; two non-blocking nits below.

This adds an except TypeError clause to ExploreMixin.get_rendered_sql alongside the existing UndefinedError / (TemplateError, SupersetSyntaxErrorException) guards, so a TypeError from a builtin invoked inside a virtual-dataset template (the documented "','".join(filter_values('numeric_col')) pattern, where filter_values() returns list[int]) surfaces as a QueryObjectValidationError (400) instead of an unhandled 500. Clause ordering is safe (TypeError isn't a subclass of the earlier-caught types), and pytest / ruff check / ruff format --check pass locally. Security-wise it's neutral: str(ex) for a TypeError is a generic Python message (no data leak, same exposure as the adjacent TemplateError branch), and converting 500→400 doesn't touch auth or RLS (RLS is applied after get_rendered_sql in get_from_clause).

🟢 Low — Same TypeError→500 still escapes at sibling process_template call sites
The guard only covers the virtual-dataset FROM clause. The identical filter_values()-into-str.join macro used in a calculated column, timestamp expression, metric, or fetch-values predicate still crashes as a raw 500, because those catch narrower sets: TableColumn.get_sqla_col (superset/connectors/sqla/models.py:1221), TableColumn.get_timestamp_expression (:1296) and SqlMetric.get_sqla_col (:1434) catch only SupersetSyntaxErrorException; SqlaTable.get_fetch_values_predicate (:1885) catches (TemplateError, SupersetSyntaxErrorException) but not TypeError. This is pre-existing behavior and an error-quality parity gap (not a correctness or security bug), so scoping this PR to the reported crash is fine. Follow-up idea: centralize template-error→QueryObjectValidationError mapping in one shared helper used by every process_template(user_sql) site, or note in the description that the sibling call sites are out of scope.

🟢 Low — Test is slightly tautological
test_get_rendered_sql_wraps_type_error mocks process_template.side_effect = TypeError(...), so it verifies the wrapper re-raises but not that the real filter_values()→str.join path actually produces the TypeError. It matches the style of the neighboring test_get_fetch_values_predicate_wraps_undefined_error, so it's acceptable as-is. Optionally strengthen it by asserting the message text and exception chaining (excinfo.value.__cause__ is the original TypeError), or add an integration-style case that renders the documented macro against a numeric filter value end-to-end.

@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.72%. Comparing base (d0edf75) to head (70150d4).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff             @@
##           master   #44312       +/-   ##
===========================================
+ Coverage   57.23%   80.72%   +23.49%     
===========================================
  Files        2944     2944               
  Lines      176454   176425       -29     
  Branches    40926    40916       -10     
===========================================
+ Hits       100993   142419    +41426     
+ Misses      74486    31328    -43158     
- Partials      975     2678     +1703     
Flag Coverage Δ
hive 37.00% <0.00%> (-0.01%) ⬇️
mysql 56.25% <0.00%> (?)
postgres 56.26% <0.00%> (?)
presto 38.96% <0.00%> (-0.01%) ⬇️
python 85.06% <100.00%> (+46.05%) ⬆️
sqlite 55.98% <0.00%> (?)
unit 77.03% <100.00%> (?)

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 eschutho closed this Sep 18, 2026
@eschutho eschutho reopened this Sep 18, 2026
@eschutho eschutho closed this Sep 19, 2026
@eschutho eschutho reopened this Sep 19, 2026
…1034)

`ExploreMixin.get_rendered_sql` catches Jinja-specific exceptions
(`UndefinedError`, `TemplateError`, `SupersetSyntaxErrorException`) and
re-raises them as `QueryObjectValidationError` (400). But a `TypeError`
raised by a Python builtin invoked from *within* the sandboxed template
is none of those, so it escapes as an unhandled 500.

This happens when a virtual dataset's custom SQL uses the documented
`filter_values()` pattern, e.g.
`... in ({{ "'" + "','".join(filter_values('action_type')) + "'" }})`,
and the filtered column holds numeric values. `filter_values()` returns
`list[int]`, and `str.join` raises
`TypeError: sequence item 0: expected str instance, int found`.

Add a `TypeError` except clause alongside the existing ones, wrapping it
in `QueryObjectValidationError` with the existing message style. Scoped
to `TypeError` only.

Fixes SUPERSET-PYTHON-16YX

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@eschutho
eschutho force-pushed the fix-filter-values-typeerror-get-rendered-sql branch from 13b5b83 to 70150d4 Compare September 21, 2026 22:09
@bito-code-review

bito-code-review Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #24ff80

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 70150d4..70150d4
    • superset/models/helpers.py
    • tests/unit_tests/connectors/sqla/models_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

@rusackas
rusackas merged commit df6e0d0 into master Sep 22, 2026
77 checks passed
@rusackas
rusackas deleted the fix-filter-values-typeerror-get-rendered-sql branch September 22, 2026 17:35
villebro pushed a commit that referenced this pull request Sep 22, 2026
…alidation error (#44312)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit df6e0d0)
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.

3 participants