Repository navigation
fix(chart-data): return 400 for malformed post-processing options - #44953
SEPURI-SAI-KRISHNA wants to merge 2 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #44953 +/- ##
==========================================
- Coverage 83.41% 83.31% -0.11%
==========================================
Files 3040 3039 -1
Lines 198718 198296 -422
Branches 46381 46180 -201
==========================================
- Hits 165764 165202 -562
- Misses 29730 29870 +140
Partials 3224 3224
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:
|
There was a problem hiding this comment.
Code Review Agent Run #0d1040
Actionable Suggestions - 1
-
superset/models/helpers.py - 1
- KeyError Branch Unlogged · Line 2693-2704
Additional Suggestions - 3
-
tests/unit_tests/models/helpers_test.py - 1
-
Duplicated test setup block · Line 5899-5917This setup block (imports, `SqlaTable`, DataFrame, `QueryResult`, both `patch.object` calls) duplicates `test_get_query_result_wraps_post_processing_type_error` (line 5782) verbatim. Extract a shared fixture/helper — or fold the sibling's resample case in as a fifth `pytest.param` — so a change to `QueryResult` or the mock setup needs only one edit.
-
-
superset/semantic_layers/models.py - 1
-
Broad catch masks server faults · Line 398-410The broad `except (TypeError, ValueError, AttributeError, pd.errors.DataError)` converts any genuine fault inside a post-processing operation into a 400 bad-request, masking real server bugs from the error path (only the `logger.warning` reveals them). The comment acknowledges this tradeoff, but a genuine operation bug now reaches users as a misleading "bad request". Consider narrowing to pandas-specific errors or re-raising non-option faults.
-
-
superset/models/helpers.py - 1
-
Custom Op KeyError Mislabeled · Line 2698-2704`ChartDataPostProcessingOperationSchema` and `exec_post_processing` also dispatch `EXTRA_PANDAS_POSTPROCESSING_OPS` custom operations, for which a `KeyError` may come from the op's own logic rather than a missing column; the fixed message would then mislabel the cause. Consider neutral wording or logging the operation name. No in-repo custom op triggers this today.
-
Review Details
-
Files reviewed - 5 · Commit Range:
0d7bdb8..0d7bdb8- superset/models/helpers.py
- superset/semantic_layers/models.py
- superset/translations/messages.pot
- tests/unit_tests/models/helpers_test.py
- tests/unit_tests/semantic_layers/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
0d7bdb8 to
4ebf204
Compare
|
Fixed the Both reviews land on the same gap: a custom op registered through One correction: Keeping the catch broad. Narrowing to pandas errors would drop the numpy Leaving the duplicated test setup. Deduping means editing the sibling test, and |
There was a problem hiding this comment.
Code Review Agent Run #96245a
Actionable Suggestions - 1
-
superset/models/helpers.py - 1
- Broad except masks server faults · Line 2712-2717
Additional Suggestions - 2
-
superset/semantic_layers/models.py - 1
-
Duplicated warning logging · Line 394-397The `KeyError` handler (394-397) and the `(TypeError, ValueError, AttributeError, DataError)` handler (414-417) repeat the identical `logger.warning(..., exc_info=True)` call. A single combined except that logs once and branches on `isinstance(ex, KeyError)` for the raised message removes the copy and keeps log wording/level in one place.
-
-
tests/unit_tests/models/helpers_test.py - 1
-
Version-coupled error strings · Line 5833-5872The parametrized cases assert on pandas/numpy internal error strings (`periods must be an integer`, `has no attribute 'items'`, `Percentiles must be in the range [0, 100]`). These are library-internal and version-dependent; a pandas/numpy upgrade that rewords them breaks the test even though the wrapping behavior in `get_query_result` is unchanged. Consider asserting primarily on the `QueryObjectValidationError` type and matching the message loosely.
-
Review Details
-
Files reviewed - 5 · Commit Range:
4ebf204..4ebf204- superset/models/helpers.py
- superset/semantic_layers/models.py
- superset/translations/messages.pot
- tests/unit_tests/models/helpers_test.py
- tests/unit_tests/semantic_layers/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
4ebf204 to
d0478a2
Compare
CodeAnt PR Risk: Medium Risk
Assessed commit: |
d0478a2 to
c8edf7a
Compare
|
Fair point, and bito raised the same thing earlier in the thread, so I have restructured rather than argued it. The handling now lives inside except (AttributeError, DataError, KeyError, TypeError, ValueError) as ex:
if not is_builtin:
raise
...
raise InvalidPostProcessingError(...) from exThree things fell out of it:
The PR is 3 files instead of 5, and the suites run clean at 881 passed. One thing I did not change: #44502's call-site handler for |
c8edf7a to
33e804b
Compare
|
Right on both counts, and it is now fixed rather than documented as out of scope.
I had expected this to need #44502's and #44463's tests edited, which is why the previous body deferred it. It does not. Both drive a real built-in operation, One test is added at each call site for the behaviour that did change: a custom operation registered through
|
33e804b to
02ad69e
Compare
02ad69e to
fa97a14
Compare
fa97a14 to
0611643
Compare
…ng-request-errors
SUMMARY
A malformed
post_processingoption returned HTTP 500 where the contract pinned bytest_chart_data_invalid_post_processingis 400. Fuzzing found 39 distinct sites that escape toapp.errorhandler(Exception); this PR converts the 38 that are bad requests and leaves the 1 that is genuinely a server-side condition. Background and the full measurement in #44952.#44463 widened the guard at
superset/models/helpers.pyso raw pandasTypeErrorandpandas.errors.DataErrorbecomeQueryObjectValidationError, and #44502 did the same forsuperset/semantic_layers/models.py.ValueError,KeyErrorandAttributeErrorescape the same way: not caught there, not caught byget_df_payload(which handles onlyQueryObjectValidationError), andChartDataRestApi.datacarries no@safe, so they land inapp.errorhandler(Exception)whosejson_error_responsedefaults tostatus=500.The handling goes where the failing operation is known, inside
QueryObject.exec_post_processing's loop, rather than at the two call sites:Both call sites already map
InvalidPostProcessingErrortoQueryObjectValidationError, so neither needed a line added. Three things follow from doing it here instead:EXTRA_PANDAS_POSTPROCESSING_OPSis the operator's own code, which this module cannot reason about, so aValueErrorfrom its internals is a fault in that operation rather than a malformed request. Only the loop knows which kind of operation it is holding.rankwith an unknown metric answers "Therankpost-processing operation references a column or level that is not in the query result: does_not_exist", where the previous shape of this fix could only saydoes_not_exist.KeyErrorgets its own message becausestr(KeyError("x"))is just"'x'", so reusing the shared one would answer a request withError: 'does_not_exist'. All tenKeyErrorsites name a column or MultiIndex level:boxplot(metrics),rank(metric),rank(group_by),histogram(column),histogram(groupby),flatten(drop_levels),geohash_decode(geohash),geohash_encode(latitude),geohash_encode(longitude),geodetic_parse(geodetic), so the specific wording is accurate for every one. Since only built-in operations reach it, the case where that wording would be a guess no longer arises.No operator internals are modified and no option is newly rejected, only the status and message of a request that already failed.
ImportErroris deliberately left out. The one case isrollingwith awin_type, which needs scipy; a missing optional dependency is a deployment matter rather than a bad request, so 500 remains correct there. That is the single site this PR does not convert, and a test pins it.Why these values reach pandas at all:
ChartDataPostProcessingOperationSchema.optionsis a barefields.Dict(schemas.py:1123), so no option is validated before dispatch. The 11ChartData*OptionsSchemaclasses are referenced only fromCHART_SCHEMASfor OpenAPI output and validate nothing, and 8 operations have no options schema at all. Wiring those up would be the root-cause fix, but it risks rejecting payloads that work today, so it is left out of this PR.The trade-off, stated plainly.
ValueErrorandAttributeErrorare broad: anAttributeErrorreading'NoneType' object has no attribute ...is the classic signature of a genuine bug, and for a built-in operation it now answers 400 rather than 500. Two things keep that acceptable:TypeError, which is no narrower.get_df_payloadrecordsstr(ex)on the response and never logs a traceback, so a mis-classified internal fault would vanish silently. This logs withexc_info=Truefirst, so the stack trace still reaches operators.If reviewers would rather keep
AttributeErroras a 500, dropping it from the tuple is a one-line change and costs 7 of the 38 converted sites.The call-site handlers are removed. #44463 and #44502 each added
except (TypeError, pd.errors.DataError)next to theInvalidPostProcessingErrorhandler. Built-in operations no longer need them, since_exec_operationwraps those two types before they get there, and leaving them in would mean a custom operation'sTypeErrorstays a 400 while itsValueErrorandAttributeErrorbecome 500, which is an odd split to leave behind. So both come out (-2and-3lines, the third beingsemantic_layers/models.py's now-unusedimport pandas as pd).Neither #44463's nor #44502's test needed editing. Both drive a real built-in operation (
resampleon an object-dtype column) and assert only thatQueryObjectValidationErroris raised, which still holds through theInvalidPostProcessingErrorpath. A new test at each call site pins the part that did change: a custom operation'sTypeErrorpropagates instead of being converted. Both fail if either handler is put back.Translations. Two new translatable strings, so
superset/translations/messages.potgains both entries with theirpython-formatflags (+8/-0). Without that the requiredbabel-extractcheck fails onscripts/translations/check_pot_drift.py. No.pocatalogue is touched, matching how other string-adding PRs land.BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A: backend only. The error response for an already-failing request changes from an opaque 500 to a 400 carrying the underlying message.
rankmetric: "does_not_exist"rankpost-processing operation references a column or level that is not in the query result: does_not_exist"diffperiods: 1.5diffpost-processing operation failed: periods must be an integer"ValueErrorTypeErrorTESTING INSTRUCTIONS
pytest tests/unit_tests/queries/query_object_test.py \ tests/unit_tests/models/helpers_test.py \ tests/unit_tests/semantic_layers/models_test.pyTen cases are added, eight of which fail on master:
..._reports_a_malformed_option_as_a_bad_requestdiffperiods,rollingwindow, both real operations..._names_a_column_the_result_does_not_haverankwith an unknown metric..._wraps_the_remaining_pandas_errorsAttributeError,DataError,TypeError..._leaves_a_custom_operation_fault_unwrappedValueErrorpropagates..._leaves_a_missing_dependency_unwrappedImportErrorpropagatestest_get_query_result_leaves_a_custom_operation_fault_unwrappedTypeErroris not converted at the dataset call sitetest_semantic_view_get_query_result_leaves_a_custom_operation_fault_unwrappedThe first three drive real built-in operations rather than mocks. No existing test changed behaviour:
tests/unit_tests/{queries,models,semantic_layers,pandas_postprocessing,charts}run clean at 883 passed, 2 xfailed (both pre-existing).ruff check,ruff format --checkandpre-commit run mypyare clean, andpylint --rcfile=.pylintrcrates all three changed source files 10.00/10.To size the change I fuzzed every built-in operation's options one at a time away from a known-good baseline, 910 calls across all 19 operations, classifying each failure by whether the guard catches it. Before: 39 distinct unhandled-exception sites across 14 of the 19 operations (ValueError 21, KeyError 10, AttributeError 7, ImportError 1). After: 1, the scipy
ImportErrorabove. That sweep is not exhaustive over every possible option value, so it is a lower bound.ADDITIONAL INFORMATION