Skip to content

feat(config): add EXTRA_PANDAS_POSTPROCESSING_OPS extension point - #43337

Open
madhushreeag wants to merge 12 commits into
apache:masterfrom
madhushreeag:charts-post-processing
Open

feat(config): add EXTRA_PANDAS_POSTPROCESSING_OPS extension point#43337
madhushreeag wants to merge 12 commits into
apache:masterfrom
madhushreeag:charts-post-processing

Conversation

@madhushreeag

@madhushreeag madhushreeag commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

SUMMARY

Following the removal of viz.py in #41750, Superset's migration from legacy v0 charts to the modern query context API (v1) removes the ability to inject arbitrary Python dataframe transformations that some custom charts previously relied on. Charts performing custom post-query data manipulation in Python had no equivalent hook in the v1 pipeline.

This PR introduces EXTRA_PANDAS_POSTPROCESSING_OPS, a config-level extension point that allows operators to register custom post-processing functions alongside Superset's built-in pandas post-processing operations. A chart's post_processing query context can reference these by name, and the marshmallow schema validator is updated to accept them at validation time.

How it works

  • EXTRA_PANDAS_POSTPROCESSING_OPS: list[Callable[..., Any]] = [] — add any callable to this list in your superset_config.py
  • The function receives the query result DataFrame and any options passed in the chart's post_processing spec
  • The marshmallow schema lazily resolves allowed operation names from the Flask app config, so custom ops pass API validation

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

TESTING INSTRUCTIONS

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

Comment thread superset/models/helpers.py Outdated
@bito-code-review

Copy link
Copy Markdown
Contributor

The update to superset/models/helpers.py correctly sets result.sql_rowcount to len(df.index) after the post-processing operations have been executed. This ensures the row count on the result object reflects the final state of the data frame.

superset/models/helpers.py

# Update result with processed data
        result.df = df
        result.sql_rowcount = len(df.index)
        result.query = query

@madhushreeag
madhushreeag marked this pull request as ready for review August 19, 2026 16:41
@dosubot dosubot Bot added change:backend Requires changing the backend install:config Installation - Configuration settings labels Aug 19, 2026
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 62.06897% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.73%. Comparing base (b8fca21) to head (bdf1390).

Files with missing lines Patch % Lines
superset/common/query_object.py 14.28% 6 Missing ⚠️
superset/initialization/__init__.py 57.14% 2 Missing and 1 partial ⚠️
superset/charts/schemas.py 77.77% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #43337      +/-   ##
==========================================
- Coverage   66.73%   66.73%   -0.01%     
==========================================
  Files        2876     2876              
  Lines      164194   164220      +26     
  Branches    37883    37887       +4     
==========================================
+ Hits       109573   109589      +16     
- Misses      52465    52473       +8     
- Partials     2156     2158       +2     
Flag Coverage Δ
hive 38.11% <51.72%> (+<0.01%) ⬆️
mysql 57.76% <62.06%> (+<0.01%) ⬆️
postgres 57.80% <62.06%> (+<0.01%) ⬆️
presto 40.05% <51.72%> (+<0.01%) ⬆️
python 59.18% <62.06%> (+<0.01%) ⬆️
sqlite 57.44% <62.06%> (+<0.01%) ⬆️
unit 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.

@villebro villebro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good - One minor nit. Also, could we add a test that patches the config with custom op and validate that it mutates the result expectedly?

Comment thread superset/models/helpers.py Outdated
Comment thread superset/charts/schemas.py Outdated
Comment thread superset/charts/schemas.py Outdated
Comment thread superset/config.py
@pull-request-size pull-request-size Bot added size/L and removed size/M labels Aug 19, 2026

@villebro villebro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@bito-code-review

bito-code-review Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #7989d1

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset/charts/schemas.py - 1
    • Redundant inline import · Line 994-994
      The inline `from flask import current_app` import at line 994 is redundant — `current_app` is already imported at module level on line 22. Per BITO.md rule [12745], imports should be moved to module-level unless a circular dependency exists and is documented, which is not the case here.
Review Details
  • Files reviewed - 4 · Commit Range: c446eee..d17c6d3
    • superset/charts/schemas.py
    • superset/common/query_object.py
    • superset/config.py
    • tests/unit_tests/queries/query_object_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 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

Copy link
Copy Markdown
Member

A couple of small things worth cleaning up sometime, neither blocking: the getattr(fn, "__name__", None) extraction is duplicated between schemas.py and query_object.py, could be one shared helper. Also, a custom op that happens to share a name with a builtin (say someone registers their own aggregate) silently never fires, since builtins are checked first. Might be worth a startup-time warning eventually. Feel free to leave both for a follow-up.

@rusackas

Copy link
Copy Markdown
Member

Good catch by codeant on the escape_separator/unescape_separator allowlist gap, that one's real. It's pre-existing on master though, not something you introduced, so opened #43345 to fix it separately rather than holding this up. Thanks for engaging with all the review feedback so thoroughly, this was a good one to work through!

@rusackas rusackas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM — nice, contained fix for a real gap. Left a couple of non-blocking nits and opened #43345 for the pre-existing allowlist gap codeant flagged.

@rusackas rusackas added the merge-if-green If approved and tests are green, please go ahead and merge it for me label Aug 19, 2026
@madhushreeag

Copy link
Copy Markdown
Contributor Author

A couple of small things worth cleaning up sometime, neither blocking: the getattr(fn, "__name__", None) extraction is duplicated between schemas.py and query_object.py, could be one shared helper. Also, a custom op that happens to share a name with a builtin (say someone registers their own aggregate) silently never fires, since builtins are checked first. Might be worth a startup-time warning eventually. Feel free to leave both for a follow-up.

Made both the changes - they were quick and valid enough to come through together in this PR. Thanks for pointing these out!

@bito-code-review

bito-code-review Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #af7023

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset/initialization/__init__.py - 1
    • Silent runtime conflict handling · Line 1449-1455
      The startup warning claims the custom op "will never fire" but the runtime code (query_object.py) silently falls back to the built-in without raising an error. Users deploying conflicting ops would see a startup warning but no runtime error — the built-in just silently wins. Consider failing fast at startup or documenting the silent fallback behavior.
Review Details
  • Files reviewed - 4 · Commit Range: d17c6d3..bdf1390
    • superset/charts/schemas.py
    • superset/common/query_object.py
    • superset/initialization/__init__.py
    • superset/utils/pandas_postprocessing/__init__.py
  • Files skipped - 1
    • UPDATING.md - Reason: Filter setting
  • 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 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


extra_op_names = list(
pandas_postprocessing.build_extra_ops_map(
current_app.config.get("EXTRA_PANDAS_POSTPROCESSING_OPS", [])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Validating even built-in operations now dereferences current_app, so callers that load chart query schemas outside an app context fail with RuntimeError where they previously succeeded. Could this only read the extra-op config when needed, or handle the missing context like the neighboring config-backed validators?

@@ -545,12 +546,19 @@ def exec_post_processing(self, df: DataFrame) -> DataFrame:
_("`operation` property of post processing object undefined")
)
if not hasattr(pandas_postprocessing, operation):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

An extra callable named build_extra_ops_map passes validation and the startup collision check, but hasattr treats the helper as built-in and invokes it instead of the configured callable. Could this dispatch only names in __all__ before looking in extra_ops?

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

Labels

change:backend Requires changing the backend install:config Installation - Configuration settings merge-if-green If approved and tests are green, please go ahead and merge it for me size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants