From 5c8051e96340321bfccceb71feac1350303f8ab7 Mon Sep 17 00:00:00 2001 From: 555aaditya <107977627+555aaditya@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:02:09 +0530 Subject: [PATCH] GH-55298: [Python] Support lists of view, decimal32/64 and fixed_size_binary types in to_pandas ListTypeSupported did not list string_view, binary_view, decimal32, decimal64 or fixed_size_binary, so converting lists of these types to pandas raised NotImplementedError even though the object conversion of the values already supports them. Since uuid uses fixed_size_binary storage, lists of uuid now convert too. Keep the uuid extension values in that case so they come out as uuid.UUID objects, like a top-level uuid array, instead of raw bytes. --- .../pyarrow/src/arrow/python/arrow_to_pandas.cc | 10 ++++++++-- python/pyarrow/tests/test_extension_type.py | 13 +++++++++++++ python/pyarrow/tests/test_pandas.py | 17 +++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/python/pyarrow/src/arrow/python/arrow_to_pandas.cc b/python/pyarrow/src/arrow/python/arrow_to_pandas.cc index eb60d9d54f85..9da449c49fea 100644 --- a/python/pyarrow/src/arrow/python/arrow_to_pandas.cc +++ b/python/pyarrow/src/arrow/python/arrow_to_pandas.cc @@ -193,12 +193,17 @@ static inline bool ListTypeSupported(const DataType& type) { case Type::HALF_FLOAT: case Type::FLOAT: case Type::DOUBLE: + case Type::DECIMAL32: + case Type::DECIMAL64: case Type::DECIMAL128: case Type::DECIMAL256: case Type::BINARY: case Type::LARGE_BINARY: + case Type::BINARY_VIEW: + case Type::FIXED_SIZE_BINARY: case Type::STRING: case Type::LARGE_STRING: + case Type::STRING_VIEW: case Type::DATE32: case Type::DATE64: case Type::STRUCT: @@ -781,7 +786,8 @@ enable_if_list_like ConvertListsLike(PandasOptions options, // flattened_values array different. std::shared_ptr flattened_values = arr.values()->Slice( arr.value_offset(0), arr.value_offset(arr.length()) - arr.value_offset(0)); - if (arr.value_type()->id() == Type::EXTENSION) { + if (arr.value_type()->id() == Type::EXTENSION && + !IsUuidExtension(*arr.value_type())) { const auto& arr_ext = checked_cast(*flattened_values); value_arrays.emplace_back(arr_ext.storage()); } else { @@ -792,7 +798,7 @@ enable_if_list_like ConvertListsLike(PandasOptions options, using ListArrayType = typename ListArrayT::TypeClass; const auto& list_type = checked_cast(*data.type()); auto value_type = list_type.value_type(); - if (value_type->id() == Type::EXTENSION) { + if (value_type->id() == Type::EXTENSION && !IsUuidExtension(*value_type)) { value_type = checked_cast(*value_type).storage_type(); } diff --git a/python/pyarrow/tests/test_extension_type.py b/python/pyarrow/tests/test_extension_type.py index a9d15cfec10a..a186fefe8fc9 100644 --- a/python/pyarrow/tests/test_extension_type.py +++ b/python/pyarrow/tests/test_extension_type.py @@ -1454,6 +1454,19 @@ def test_uuid_to_pandas(): ) +@pytest.mark.pandas +@pytest.mark.parametrize("list_array_class", [pa.ListArray, pa.LargeListArray]) +def test_list_of_uuid_to_pandas(list_array_class): + # GH-55298 + import pandas as pd + import pandas.testing as tm + values = [uuid4(), None, uuid4()] + array = list_array_class.from_arrays( + [0, 2, 3, 3], pa.array(values, type=pa.uuid())) + expected = pd.Series([values[:2], values[2:], []]) + tm.assert_series_equal(array.to_pandas(), expected) + + @pytest.mark.pandas def test_uuid_to_pandas_options(): values = [uuid4(), uuid4()] * 2 diff --git a/python/pyarrow/tests/test_pandas.py b/python/pyarrow/tests/test_pandas.py index 2391cb260009..90633a205137 100644 --- a/python/pyarrow/tests/test_pandas.py +++ b/python/pyarrow/tests/test_pandas.py @@ -2505,6 +2505,23 @@ def test_large_binary_list(self): s, pd.Series([["aa", "bb"], None, ["cc"], []]), check_names=False) + @pytest.mark.parametrize(("value_type", "values"), [ + (pa.string_view(), ["aa", None, "bb"]), + (pa.binary_view(), [b"aa", None, b"bb"]), + (pa.binary(2), [b"aa", None, b"bb"]), + (pa.decimal32(3, 1), [decimal.Decimal("1.5"), None, decimal.Decimal("-2.0")]), + (pa.decimal64(3, 1), [decimal.Decimal("1.5"), None, decimal.Decimal("-2.0")]), + ]) + def test_list_of_view_decimal_and_fixed_size_binary(self, value_type, values): + # GH-55298 + expected = [values, None, []] + for list_type_factory in (pa.list_, pa.large_list, pa.list_view): + arr = pa.array(expected, type=list_type_factory(value_type)) + tm.assert_series_equal(arr.to_pandas(), pd.Series(expected), + check_names=False) + tm.assert_frame_equal(pa.table({"a": arr}).to_pandas(), + pd.DataFrame({"a": expected})) + def test_list_of_dictionary(self): child = pa.array(["foo", "bar", None, "foo"]).dictionary_encode() arr = pa.ListArray.from_arrays([0, 1, 3, 3, 4], child)