You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/09/03 18:48:37 UTC

(superset) branch master updated: fix(charts): exclude string helpers from post_processing operation allowlist (#43345)

This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 9828281e7fd fix(charts): exclude string helpers from post_processing operation allowlist (#43345)
9828281e7fd is described below

commit 9828281e7fdface10353841427b9e2b6a0c14202
Author: Evan Rusackas <ev...@preset.io>
AuthorDate: Thu Sep 3 11:48:29 2026 -0700

    fix(charts): exclude string helpers from post_processing operation allowlist (#43345)
    
    Co-authored-by: Superset Dev <de...@superset.apache.org>
    Co-authored-by: Claude Opus 4.8 <no...@anthropic.com>
---
 superset/charts/schemas.py                       | 10 +++++++++-
 superset/common/query_object.py                  |  8 ++++----
 superset/utils/pandas_postprocessing/__init__.py | 10 ++++++++++
 tests/unit_tests/charts/test_schemas.py          | 11 +++++++++++
 tests/unit_tests/queries/query_object_test.py    | 17 +++++++++++++++++
 5 files changed, 51 insertions(+), 5 deletions(-)

diff --git a/superset/charts/schemas.py b/superset/charts/schemas.py
index 458320aac48..74c42caa862 100644
--- a/superset/charts/schemas.py
+++ b/superset/charts/schemas.py
@@ -1033,7 +1033,15 @@ class ChartDataGeodeticParseOptionsSchema(
 
 
 class ChartDataPostProcessingOperationSchema(Schema):
-    _builtin_ops = pandas_postprocessing.__all__
+    # OPERATIONS excludes escape_separator/unescape_separator: those are
+    # internal str -> str helpers used by flatten, not DataFrame
+    # post-processing operations, so dispatching one against a DataFrame
+    # raises a confusing TypeError instead of the intended clean validation
+    # error. No field-level `validate=` here: it would run before, and thus
+    # reject, any EXTRA_PANDAS_POSTPROCESSING_OPS-registered custom
+    # operation, which `validate_operation` below is responsible for
+    # allowing.
+    _builtin_ops = pandas_postprocessing.OPERATIONS
 
     operation = fields.String(
         metadata={
diff --git a/superset/common/query_object.py b/superset/common/query_object.py
index e67a470b069..389bd828678 100644
--- a/superset/common/query_object.py
+++ b/superset/common/query_object.py
@@ -644,10 +644,10 @@ class QueryObject:  # pylint: disable=too-many-instance-attributes
                     raise InvalidPostProcessingError(
                         _("`operation` property of post processing object undefined")
                     )
-                # ``__all__`` is the authoritative list of built-in operations.
-                # ``hasattr`` would also match module internals (helpers, imported
-                # submodules, typing aliases), shadowing a like-named custom op.
-                if operation in pandas_postprocessing.__all__:
+                # ``OPERATIONS`` is the authoritative list of built-in operations;
+                # excludes escape_separator/unescape_separator (str -> str helpers
+                # used by flatten, not DataFrame post-processing operations).
+                if operation in pandas_postprocessing.OPERATIONS:
                     func = getattr(pandas_postprocessing, operation)
                 else:
                     extra_ops = pandas_postprocessing.build_extra_ops_map(
diff --git a/superset/utils/pandas_postprocessing/__init__.py b/superset/utils/pandas_postprocessing/__init__.py
index d7400d7c236..7da9c6e750d 100644
--- a/superset/utils/pandas_postprocessing/__init__.py
+++ b/superset/utils/pandas_postprocessing/__init__.py
@@ -80,3 +80,13 @@ def build_extra_ops_map(
     spec.
     """
     return {name: fn for fn in extra if (name := getattr(fn, "__name__", None))}
+
+
+# Operations that can be requested via a chart's `post_processing` spec.
+# Excludes `escape_separator`/`unescape_separator`: those are internal
+# str -> str helpers used by `flatten`, not DataFrame post-processing
+# operations, so dispatching one against a DataFrame raises a confusing
+# TypeError instead of the intended clean validation error.
+OPERATIONS = [
+    name for name in __all__ if name not in ("escape_separator", "unescape_separator")
+]
diff --git a/tests/unit_tests/charts/test_schemas.py b/tests/unit_tests/charts/test_schemas.py
index e22d8cb928c..6610ab18147 100644
--- a/tests/unit_tests/charts/test_schemas.py
+++ b/tests/unit_tests/charts/test_schemas.py
@@ -601,3 +601,14 @@ def test_chart_data_adhoc_metric_schema_accepts_extended_aggregates(
         }
     )
     assert result["aggregate"] == aggregate
+
+
+@pytest.mark.parametrize("operation", ["escape_separator", "unescape_separator"])
+def test_post_processing_operation_schema_rejects_string_helpers(
+    app_context: None, operation: str
+) -> None:
+    """`escape_separator`/`unescape_separator` are internal str -> str helpers,
+    not DataFrame post-processing operations, and shouldn't validate as one."""
+    schema = ChartDataPostProcessingOperationSchema()
+    with pytest.raises(ValidationError):
+        schema.load({"operation": operation, "options": {}})
diff --git a/tests/unit_tests/queries/query_object_test.py b/tests/unit_tests/queries/query_object_test.py
index 9d66f576095..dcbcb95ce62 100644
--- a/tests/unit_tests/queries/query_object_test.py
+++ b/tests/unit_tests/queries/query_object_test.py
@@ -718,3 +718,20 @@ def test_post_processing_keeps_an_entry_without_an_operation():
     query_object = QueryObject(row_limit=1, post_processing=post_processing)
 
     assert query_object.post_processing == post_processing
+
+
+@pytest.mark.parametrize("operation", ["escape_separator", "unescape_separator"])
+def test_exec_post_processing_rejects_string_helpers(
+    app_context: None, operation: str
+) -> None:
+    """`escape_separator`/`unescape_separator` are str -> str helpers used by
+    `flatten`, not DataFrame post-processing operations, and must not be
+    reachable as a `post_processing` operation name."""
+    df = pd.DataFrame({"value": [1, 2, 3]})
+    query_object = QueryObject(
+        row_limit=10,
+        post_processing=[{"operation": operation, "options": {}}],
+    )
+
+    with pytest.raises(InvalidPostProcessingError):
+        query_object.exec_post_processing(df)