From cefd1ff119001d0e897b785b3c3f46358f61b282 Mon Sep 17 00:00:00 2001 From: Nilanjan Chatterjee Date: Sun, 11 Oct 2026 14:48:33 -0500 Subject: [PATCH] GH-44712: [Python] Convert NumPy integer arrays to decimal types Decimal types derive from FixedSizeBinaryType, so NumPyConverter treated an integer ndarray as raw bytes and failed with a byte-width error. Convert integers to their Arrow type and cast to the decimal type. Co-Authored-By: Claude Opus 5.5 --- .../src/arrow/python/numpy_to_arrow.cc | 27 +++++++++++++++++++ python/pyarrow/tests/test_array.py | 16 +++++++++++ 2 files changed, 43 insertions(+) diff --git a/python/pyarrow/src/arrow/python/numpy_to_arrow.cc b/python/pyarrow/src/arrow/python/numpy_to_arrow.cc index d4e859d1c707..57cd99f9d6a1 100644 --- a/python/pyarrow/src/arrow/python/numpy_to_arrow.cc +++ b/python/pyarrow/src/arrow/python/numpy_to_arrow.cc @@ -238,6 +238,8 @@ class NumPyConverter { Status Visit(const FixedSizeBinaryType& type); + Status Visit(const DecimalType& type); + // Default case Status Visit(const DataType& type) { return TypeNotImplemented(type.ToString()); } @@ -638,6 +640,31 @@ Status NumPyConverter::Visit(const BinaryViewType& type) { return PushArray(result->data()); } +Status NumPyConverter::Visit(const DecimalType& type) { + // Decimal types derive from FixedSizeBinaryType, so without this overload + // integer arrays would be reinterpreted as raw bytes. + if (!PyTypeNum_ISINTEGER(dtype_->type_num)) { + return TypeNotImplemented(type.ToString()); + } + ARROW_ASSIGN_OR_RAISE(auto input_type, NumPyDtypeToArrow(dtype_)); + NumPyConverter converter(pool_, reinterpret_cast(arr_), + reinterpret_cast(mask_), input_type, from_pandas_, + cast_options_); + RETURN_NOT_OK(converter.Convert()); + // Go through decimal128(20, 0), which holds any 64-bit integer, so the + // final cast checks the values rather than the input type's range. + const auto wide_type = decimal128(20, 0); + compute::ExecContext context(pool_); + for (const auto& arr : converter.result()) { + ARROW_ASSIGN_OR_RAISE(auto wide, + compute::Cast(*arr, wide_type, cast_options_, &context)); + ARROW_ASSIGN_OR_RAISE(auto casted, + compute::Cast(*wide, type_, cast_options_, &context)); + out_arrays_.push_back(std::move(casted)); + } + return Status::OK(); +} + Status NumPyConverter::Visit(const FixedSizeBinaryType& type) { auto byte_width = type.byte_width(); diff --git a/python/pyarrow/tests/test_array.py b/python/pyarrow/tests/test_array.py index 74ff837e16e8..d0455cbb6ea7 100644 --- a/python/pyarrow/tests/test_array.py +++ b/python/pyarrow/tests/test_array.py @@ -1882,6 +1882,22 @@ def test_half_float_array_from_python(): arr = pa.array(['a', 3, None], type=pa.float16()) +@pytest.mark.numpy +@pytest.mark.parametrize('typ', [pa.decimal32(9, 2), pa.decimal64(18, 2), + pa.decimal128(38, 9), pa.decimal256(40, 2)]) +def test_decimal_from_numpy_integers(typ): + # GH-44712 + arr = np.array([1, -2, 3], dtype=np.int64) + expected = pa.array([1, -2, 3], type=typ) + assert pa.array(arr, type=typ).equals(expected) + + result = pa.array(arr, type=typ, mask=np.array([False, True, False])) + assert result.equals(pa.array([1, None, 3], type=typ)) + + with pytest.raises(pa.ArrowInvalid): + pa.array(np.array([10**6], dtype=np.int64), type=pa.decimal32(5, 2)) + + def test_decimal_to_int_safe(): safe_cases = [ (