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)