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/06/09 19:51:40 UTC

(superset) branch master updated: chore(deps): bump pyarrow from 20.0.0 to 24.0.0 (#39756)

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 543ad04ca05 chore(deps): bump pyarrow from 20.0.0 to 24.0.0 (#39756)
543ad04ca05 is described below

commit 543ad04ca059454771c90285bf8ef711ed321417
Author: dependabot[bot] <49...@users.noreply.github.com>
AuthorDate: Tue Jun 9 12:51:33 2026 -0700

    chore(deps): bump pyarrow from 20.0.0 to 24.0.0 (#39756)
    
    Signed-off-by: dependabot[bot] <su...@github.com>
    Co-authored-by: dependabot[bot] <49...@users.noreply.github.com>
    Co-authored-by: Evan <ev...@preset.io>
    Co-authored-by: Claude Sonnet 4.6 <no...@anthropic.com>
---
 pyproject.toml                      |  3 +-
 requirements/base.in                |  2 +-
 requirements/base.txt               |  2 +-
 requirements/development.txt        |  2 +-
 superset/result_set.py              | 34 ++++++++++++++++++-
 superset/semantic_layers/mapper.py  |  7 ++--
 tests/unit_tests/result_set_test.py | 67 ++++++++++++++++++++++++++++++++++++-
 7 files changed, 108 insertions(+), 9 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 4f9723262ed..63d81910542 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -89,7 +89,7 @@ dependencies = [
     "python-dateutil",
     "python-dotenv", # optional dependencies for Flask but required for Superset, see https://flask.palletsprojects.com/en/stable/installation/#optional-dependencies
     "pygeohash",
-    "pyarrow>=16.1.0, <21", # before upgrading pyarrow, check that all db dependencies support this, see e.g. https://github.com/apache/superset/pull/34693
+    "pyarrow>=24.0.0, <25", # before upgrading pyarrow, check that all db dependencies support this, see e.g. https://github.com/apache/superset/pull/34693
     "pyyaml>=6.0.0, <7.0.0",
     "PyJWT>=2.4.0, <3.0",
     "redis>=5.0.0, <6.0",
@@ -447,6 +447,7 @@ requirement_txt_file = "requirements/base.txt"
 authorized_licenses = [
     "academic free license (afl)",
     "any-osi",
+    "apache-2.0",
     "apache license 2.0",
     "apache software",
     "apache software, bsd",
diff --git a/requirements/base.in b/requirements/base.in
index cad39dc543c..c1b56438aa3 100644
--- a/requirements/base.in
+++ b/requirements/base.in
@@ -30,7 +30,7 @@ cryptography>=46.0.7,<47.0.0
 # Security: Snyk - XSS vulnerability in Mako templates
 mako>=1.3.11,<2.0.0
 # Security: CVE-2024-52338 (CRITICAL) - Deserialization of untrusted data in IPC/Parquet readers
-pyarrow>=20.0.0,<21.0.0
+pyarrow>=24.0.0,<25.0.0
 # Security: CVE-2026-27459 - pyopenssl certificate validation
 pyopenssl>=26.0.0,<27.0.0
 # Security: CVE-2026-25645 (MEDIUM) - Insecure Temporary File
diff --git a/requirements/base.txt b/requirements/base.txt
index 6f3268d18e5..211a0a4db3c 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -294,7 +294,7 @@ prison==0.2.1
     # via flask-appbuilder
 prompt-toolkit==3.0.51
     # via click-repl
-pyarrow==20.0.0
+pyarrow==24.0.0
     # via
     #   -r requirements/base.in
     #   apache-superset (pyproject.toml)
diff --git a/requirements/development.txt b/requirements/development.txt
index 9f10b2bf86d..c547be8c443 100644
--- a/requirements/development.txt
+++ b/requirements/development.txt
@@ -715,7 +715,7 @@ psycopg2-binary==2.9.12
     # via apache-superset
 py-key-value-aio==0.4.4
     # via fastmcp
-pyarrow==20.0.0
+pyarrow==24.0.0
     # via
     #   -c requirements/base-constraint.txt
     #   apache-superset
diff --git a/superset/result_set.py b/superset/result_set.py
index d357e7d0aa5..eaa4918bef5 100644
--- a/superset/result_set.py
+++ b/superset/result_set.py
@@ -86,6 +86,32 @@ def destringify(obj: str) -> Any:
     return json.loads(obj)
 
 
+def stringify_extension_columns(table: pa.Table) -> pa.Table:
+    """
+    Replace Arrow extension-typed columns with their string representation.
+
+    Superset cannot render Arrow extension types natively (see
+    ``superset.utils.core.GenericDataType``). The most common case is the
+    canonical ``uuid`` type: PyArrow >= 21 infers Python ``uuid.UUID`` values as
+    that extension type (16-byte binary), which ``Table.to_pandas()`` surfaces as
+    raw bytes. Stringifying here keeps such columns readable (UUID values become
+    their canonical hex form). Plain binary/BLOB columns are not extension types
+    and are left untouched.
+    """
+    for index in range(table.num_columns):
+        field = table.schema.field(index)
+        if isinstance(field.type, pa.BaseExtensionType):
+            stringified = pa.array(
+                [
+                    None if value is None else str(value)
+                    for value in table.column(index).to_pylist()
+                ],
+                type=pa.string(),
+            )
+            table = table.set_column(index, field.name, stringified)
+    return table
+
+
 def convert_to_string(value: Any) -> str:
     """
     Used to ensure column names from the cursor description are strings.
@@ -238,7 +264,13 @@ class SupersetResultSet:
         if not pa_data:
             column_names = []
 
-        self.table = pa.Table.from_arrays(pa_data, names=column_names)
+        # PyArrow >= 21 infers Python `uuid.UUID` values as the Arrow `uuid`
+        # extension type rather than raising (which previously routed them
+        # through the stringification fallback above). Stringify any extension
+        # columns so they render as readable text instead of raw bytes.
+        self.table = stringify_extension_columns(
+            pa.Table.from_arrays(pa_data, names=column_names)
+        )
         self._type_dict: dict[str, Any] = {}
         try:
             # The driver may not be passing a cursor.description
diff --git a/superset/semantic_layers/mapper.py b/superset/semantic_layers/mapper.py
index b71dfeb44db..e40629c3ce2 100644
--- a/superset/semantic_layers/mapper.py
+++ b/superset/semantic_layers/mapper.py
@@ -56,6 +56,7 @@ from superset.common.utils.time_range_utils import get_since_until_from_query_ob
 from superset.connectors.sqla.models import BaseDatasource
 from superset.constants import NO_TIME_RANGE
 from superset.models.helpers import QueryResult
+from superset.result_set import stringify_extension_columns
 from superset.superset_typing import AdhocColumn
 from superset.utils.core import (
     FilterOperator,
@@ -121,7 +122,7 @@ def get_results(query_object: QueryObject) -> QueryResult:
     main_query = queries[0]
     main_result = dispatcher(main_query)
 
-    main_df = main_result.results.to_pandas()
+    main_df = stringify_extension_columns(main_result.results).to_pandas()
 
     # Collect all requests (SQL queries, HTTP requests, etc.) for troubleshooting
     all_requests = list(main_result.requests)
@@ -155,7 +156,7 @@ def get_results(query_object: QueryObject) -> QueryResult:
         # Add this query's requests to the collection
         all_requests.extend(result.requests)
 
-        offset_df = result.results.to_pandas()
+        offset_df = stringify_extension_columns(result.results).to_pandas()
 
         # Handle empty results - add NaN columns directly instead of merging
         # This avoids dtype mismatch issues with empty DataFrames
@@ -229,7 +230,7 @@ def map_semantic_result_to_query_result(
 
     return QueryResult(
         # Core data
-        df=semantic_result.results.to_pandas(),
+        df=stringify_extension_columns(semantic_result.results).to_pandas(),
         query=query_str,
         duration=duration,
         # Template filters - not applicable to semantic layers
diff --git a/tests/unit_tests/result_set_test.py b/tests/unit_tests/result_set_test.py
index b751845a6a9..c2bd409ecba 100644
--- a/tests/unit_tests/result_set_test.py
+++ b/tests/unit_tests/result_set_test.py
@@ -25,7 +25,11 @@ from numpy.core.multiarray import array
 from pytest_mock import MockerFixture
 
 from superset.db_engine_specs.base import BaseEngineSpec
-from superset.result_set import stringify_values, SupersetResultSet
+from superset.result_set import (
+    stringify_extension_columns,
+    stringify_values,
+    SupersetResultSet,
+)
 from superset.superset_typing import DbapiResult
 from superset.utils import json as superset_json
 
@@ -385,3 +389,64 @@ def test_array_data_type_preserved() -> None:
     assert df["arr"].iloc[0] == [1, 2, 3]
     assert isinstance(df["arr"].iloc[0], list)
     assert df["arr"].iloc[2] is None
+
+
+def test_uuid_column_is_stringified() -> None:
+    """
+    UUID columns must render as readable strings, not raw bytes.
+
+    PyArrow >= 21 infers Python ``uuid.UUID`` values as the canonical ``uuid``
+    extension type (16-byte binary) instead of raising while building the array.
+    That bypasses the stringification fallback, so without explicit handling the
+    values surface in the results grid as garbled bytes / ``[bytes]``.
+    ``SupersetResultSet`` must stringify any Arrow extension type.
+
+    Regression test for the pyarrow 20 -> 24 upgrade.
+    """
+    import uuid
+
+    ids = [
+        uuid.UUID("f4787a4f-2541-4f8a-9b5e-1e2d3c4b5a6f"),
+        uuid.UUID("00000000-0000-0000-0000-000000000000"),
+    ]
+    data = [(ids[0],), (ids[1],)]
+    description = [("uuid", "uuid", None, None, None, None, True)]
+    result_set = SupersetResultSet(data, description, BaseEngineSpec)  # type: ignore
+
+    df = result_set.to_pandas_df()
+    assert df["uuid"].tolist() == [str(i) for i in ids]
+    # values are readable UUID strings, not raw bytes
+    assert all(value is None or isinstance(value, str) for value in df["uuid"].tolist())
+
+
+def test_stringify_extension_columns() -> None:
+    """
+    ``stringify_extension_columns`` converts Arrow extension columns (e.g. the
+    canonical ``uuid`` type) to readable strings while leaving plain binary and
+    other columns untouched. This is the shared helper used by both
+    ``SupersetResultSet`` and the semantic-layers mapper.
+    """
+    import uuid
+
+    import pyarrow as pa
+
+    first = uuid.UUID("f4787a4f-2541-4f8a-9b5e-1e2d3c4b5a6f")
+    uuid_col = pa.ExtensionArray.from_storage(
+        pa.uuid(), pa.array([first.bytes, None], pa.binary(16))
+    )
+    table = pa.table(
+        {
+            "id": uuid_col,
+            "blob": pa.array([b"\x89PNG", None], pa.binary()),
+            "n": pa.array([1, 2]),
+        }
+    )
+
+    result = stringify_extension_columns(table)
+
+    # uuid extension -> readable string column
+    assert pa.types.is_string(result.schema.field("id").type)
+    assert result.column("id").to_pylist() == [str(first), None]
+    # plain binary BLOBs and other types are left untouched
+    assert pa.types.is_binary(result.schema.field("blob").type)
+    assert pa.types.is_integer(result.schema.field("n").type)