Repository navigation
fix(sqla): move process_template inside try block in get_fetch_values_predicate - #44246
Conversation
…_predicate
The process_template call was outside the try block that contains the
except (TemplateError, SupersetSyntaxErrorException) handler written
to catch it. A bare jinja2.exceptions.UndefinedError (e.g. from
{{ foo.bar }} attribute access on an undefined variable) would escape
uncaught, producing an opaque 500 instead of a clean 400 with the
intended "Error in jinja expression in fetch values predicate" message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Code Review Agent Run #420c8cActionable Suggestions - 0Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #44246 +/- ##
=======================================
Coverage 80.17% 80.18%
=======================================
Files 2925 2925
Lines 172696 172696
Branches 40092 40092
=======================================
+ Hits 138467 138472 +5
+ Misses 31631 31625 -6
- Partials 2598 2599 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
rebenitez1802
left a comment
There was a problem hiding this comment.
Approve — correct, minimal, well-scoped fix; security model intact and no regressions. The core mechanism checks out: {{ foo.bar }} raises a raw jinja2 UndefinedError, which is a TemplateError subclass (MRO: UndefinedError → TemplateRuntimeError → TemplateError), so moving process_template inside the try lets the existing except (TemplateError, SupersetSyntaxErrorException) clause wrap it into a 400 instead of leaking a 500. TemplateError is already imported and getattr(ex, "message", …) resolves correctly for UndefinedError. Only Low-severity, optional suggestions below — none blocking.
🟢 Low — Test pins the exception type but not the helpful message the fix delivers
test_get_fetch_values_predicate_wraps_undefined_error uses a bare pytest.raises(QueryObjectValidationError). Since the PR's whole purpose is a 400 carrying "Error in jinja expression in fetch values predicate: 'foo' is undefined", a later change that garbles getattr(ex, "message", str(ex)), edits the clause text, or routes the error through the first (…failed SQL validation) clause would keep the test green. The sibling test this PR cites, test_get_dataset_include_rendered_sql_handles_undefined_error, does assert its wrapped message, so this is worth matching. The core scoping fix itself is protected (reverting the move surfaces an uncaught UndefinedError and fails this test) — so this is message-quality hardening only. Fix: with pytest.raises(QueryObjectValidationError, match="Error in jinja expression in fetch values predicate"): and bind as exc to also assert "'foo' is undefined" in str(exc.value).
🟢 Low — Sibling render paths still surface the same jinja error as an opaque 500 (pre-existing, optional follow-up)
The same bug class lives in four adjacent query-time render sites that catch only SupersetSyntaxErrorException: TableColumn.get_sqla_col, get_timestamp_expression, SqlMetric.get_sqla_col, and _render_adhoc_expression_for_metadata_lookup. A calculated column/metric containing {{ foo.bar }} raises a raw UndefinedError there too, which isn't a SupersetSyntaxErrorException, so it escapes to a 500 — exactly what this PR fixes for fetch-values. Not touched or introduced by this diff, so not blocking; consider broadening those clauses to (TemplateError, SupersetSyntaxErrorException) here or in a follow-up, for consistency with this fix, the RLS path, and datasets/api.py.
🟢 Low — Undefined function calls ({{ foo() }}) still return 422, not the new 400 (pre-existing edge, optional)
An undefined function call raises UndefinedTemplateFunctionException (a SupersetTemplateException, status 422), which is neither TemplateError nor SupersetSyntaxErrorException, so it bypasses the new wrapper — the user gets a 422 with a bare message rather than the contextual 400 the undefined-variable case now produces. Behavior is identical before and after this diff (not a regression), and 422 is already a meaningful typed error rather than an opaque 500, so this is only a minor UX inconsistency versus the case being fixed. If you want them aligned, add SupersetTemplateException to the second except tuple (its .message works with the existing getattr).
SUMMARY
Problem: In
SqlaTable.get_fetch_values_predicate, thetemplate_processor.process_template()call sits outside thetry:block whoseexcept (TemplateError, SupersetSyntaxErrorException)clause was written to catch Jinja template errors from it. A barejinja2.exceptions.UndefinedError(aTemplateErrorsubclass) raised by attribute access on an undefined variable (e.g.{{ foo.bar }}) escapes uncaught, propagating as an opaque 500 (GENERIC_BACKEND_ERROR) instead of the intended 400 with a helpful "Error in jinja expression in fetch values predicate" message.Fix: Move the
process_templatecall inside the existingtry:block so the pre-existing except clause actually covers it. No new except clauses, no refactoring — just correcting the scope of the existing handler.Precedent: This is the same bug class as the fix in #42401 (
catch TemplateError alongside SupersetSyntaxErrorException in models.py) —UndefinedErrorescapingprocess_templatedue to misscoped or missing error handling. The existing testtest_get_dataset_include_rendered_sql_handles_undefined_errorcovers the analogous fix in thedatasets/api.pycall path; this PR covers theget_fetch_values_predicatecall path.BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A — backend-only error handling fix.
TESTING INSTRUCTIONS
test_get_fetch_values_predicate_wraps_undefined_errorintests/unit_tests/connectors/sqla/models_test.py:process_templateto raiseUndefinedError("'foo' is undefined")get_fetch_values_predicatewraps it inQueryObjectValidationErrorUndefinedErrorescapes), passes after the fixruff check,ruff format --check, andmypyall passADDITIONAL INFORMATION