From 79a3bdc5c090b1720ed4c98bb296d7e11201fdcd Mon Sep 17 00:00:00 2001 From: 1fanwang <1fannnw@gmail.com> Date: Fri, 4 Sep 2026 23:56:54 -0400 Subject: [PATCH] GH-51042: [Python] Reject invalid Arrow wrapper types Generated-by: GitHub Copilot CLI (GPT-5.6 Sol) Signed-off-by: 1fanwang <1fannnw@gmail.com> --- python/pyarrow/_compute.pyx | 4 ++-- python/pyarrow/_dataset_parquet.pyx | 2 ++ python/pyarrow/_parquet.pyx | 4 +++- python/pyarrow/_substrait.pyx | 5 +++-- python/pyarrow/tensor.pxi | 8 ++++---- python/pyarrow/tests/parquet/test_parquet_file.py | 9 +++++++++ python/pyarrow/tests/test_compute.py | 3 +++ python/pyarrow/tests/test_dataset.py | 3 +++ python/pyarrow/tests/test_sparse_tensor.py | 11 +++++++++++ python/pyarrow/tests/test_substrait.py | 6 ++++++ 10 files changed, 46 insertions(+), 9 deletions(-) diff --git a/python/pyarrow/_compute.pyx b/python/pyarrow/_compute.pyx index 1c98bbfdea2c..d26b6af5aab2 100644 --- a/python/pyarrow/_compute.pyx +++ b/python/pyarrow/_compute.pyx @@ -1668,7 +1668,7 @@ class CountOptions(_CountOptions): cdef class _IndexOptions(FunctionOptions): - def _set_options(self, scalar): + def _set_options(self, Scalar scalar not None): self.wrapped.reset(new CIndexOptions(pyarrow_unwrap_scalar(scalar))) @@ -1682,7 +1682,7 @@ class IndexOptions(_IndexOptions): The value to search for. """ - def __init__(self, value): + def __init__(self, Scalar value not None): self._set_options(value) diff --git a/python/pyarrow/_dataset_parquet.pyx b/python/pyarrow/_dataset_parquet.pyx index 534f7790923a..32530ba0fa34 100644 --- a/python/pyarrow/_dataset_parquet.pyx +++ b/python/pyarrow/_dataset_parquet.pyx @@ -547,6 +547,8 @@ cdef class ParquetReadOptions(_Weakrefable): @binary_type.setter def binary_type(self, ty): if ty is not None: + if not isinstance(ty, DataType): + raise TypeError(f"DataType expected, got {type(ty)!r}") self._binary_type = pyarrow_unwrap_data_type(ty).get().id() else: self._binary_type = _Type_BINARY diff --git a/python/pyarrow/_parquet.pyx b/python/pyarrow/_parquet.pyx index 932632a50410..c9b0700ef146 100644 --- a/python/pyarrow/_parquet.pyx +++ b/python/pyarrow/_parquet.pyx @@ -26,7 +26,7 @@ from cython.operator cimport dereference as deref from pyarrow.includes.common cimport * from pyarrow.includes.libarrow cimport * from pyarrow.includes.libarrow_python cimport * -from pyarrow.lib cimport (_Weakrefable, Buffer, Schema, +from pyarrow.lib cimport (_Weakrefable, Buffer, DataType, Schema, check_status, MemoryPool, maybe_unbox_memory_pool, Table, KeyValueMetadata, @@ -1656,6 +1656,8 @@ cdef class ParquetReader(_Weakrefable): properties.set_page_checksum_verification(page_checksum_verification) if binary_type is not None: + if not isinstance(binary_type, DataType): + raise TypeError(f"DataType expected, got {type(binary_type)!r}") c_binary_type = pyarrow_unwrap_data_type(binary_type) arrow_props.set_binary_type(c_binary_type.get().id()) diff --git a/python/pyarrow/_substrait.pyx b/python/pyarrow/_substrait.pyx index d9359c8e77d0..2bd55779fc94 100644 --- a/python/pyarrow/_substrait.pyx +++ b/python/pyarrow/_substrait.pyx @@ -218,7 +218,7 @@ class SubstraitSchema: return py_substrait.proto.ExtendedExpression.FromString(self.expression) -def serialize_schema(schema): +def serialize_schema(Schema schema not None): """ Serialize a schema into a SubstraitSchema object. @@ -293,7 +293,8 @@ def deserialize_schema(buf): return pyarrow_wrap_schema(c_schema) -def serialize_expressions(exprs, names, schema, *, allow_arrow_extensions=False): +def serialize_expressions(exprs, names, Schema schema not None, *, + allow_arrow_extensions=False): """ Serialize a collection of expressions into Substrait diff --git a/python/pyarrow/tensor.pxi b/python/pyarrow/tensor.pxi index 521ee0c3f44f..9b2490716a89 100644 --- a/python/pyarrow/tensor.pxi +++ b/python/pyarrow/tensor.pxi @@ -564,7 +564,7 @@ shape: {self.shape}""" return pyarrow_wrap_sparse_coo_tensor(csparse_tensor) @staticmethod - def from_tensor(obj): + def from_tensor(Tensor obj not None): """ Convert arrow::Tensor to arrow::SparseCOOTensor. @@ -862,7 +862,7 @@ shape: {self.shape}""" return pyarrow_wrap_sparse_csr_matrix(csparse_tensor) @staticmethod - def from_tensor(obj): + def from_tensor(Tensor obj not None): """ Convert arrow::Tensor to arrow::SparseCSRMatrix. @@ -1133,7 +1133,7 @@ shape: {self.shape}""" return pyarrow_wrap_sparse_csc_matrix(csparse_tensor) @staticmethod - def from_tensor(obj): + def from_tensor(Tensor obj not None): """ Convert arrow::Tensor to arrow::SparseCSCMatrix @@ -1406,7 +1406,7 @@ shape: {self.shape}""" return pyarrow_wrap_sparse_csf_tensor(csparse_tensor) @staticmethod - def from_tensor(obj): + def from_tensor(Tensor obj not None): """ Convert arrow::Tensor to arrow::SparseCSFTensor diff --git a/python/pyarrow/tests/parquet/test_parquet_file.py b/python/pyarrow/tests/parquet/test_parquet_file.py index a62b5c3298c9..bcbecf6ff1b5 100644 --- a/python/pyarrow/tests/parquet/test_parquet_file.py +++ b/python/pyarrow/tests/parquet/test_parquet_file.py @@ -46,6 +46,15 @@ pytestmark = pytest.mark.parquet +def test_parquet_file_rejects_invalid_binary_type(): + sink = io.BytesIO() + pq.write_table(pa.table({"value": [b"data"]}), sink) + sink.seek(0) + + with pytest.raises(TypeError, match="DataType expected"): + pq.ParquetFile(sink, binary_type=0) + + @pytest.mark.pandas def test_pass_separate_metadata(): # ARROW-471 diff --git a/python/pyarrow/tests/test_compute.py b/python/pyarrow/tests/test_compute.py index d350c8115799..77e538d8a9b9 100644 --- a/python/pyarrow/tests/test_compute.py +++ b/python/pyarrow/tests/test_compute.py @@ -240,6 +240,9 @@ def test_option_class_equality(request): assert repr(pc.ArraySortOptions()) == \ "ArraySortOptions(order=Ascending, null_placement=AtEnd)" + with pytest.raises(TypeError, match="Argument 'value' has incorrect type"): + pc.IndexOptions(0) + def test_list_functions(): assert len(pc.list_functions()) > 10 diff --git a/python/pyarrow/tests/test_dataset.py b/python/pyarrow/tests/test_dataset.py index 09d7cfb9d9dd..e0de8455f02d 100644 --- a/python/pyarrow/tests/test_dataset.py +++ b/python/pyarrow/tests/test_dataset.py @@ -988,6 +988,9 @@ def test_parquet_read_options(): assert opts5.list_type is pa.LargeListType assert opts5 != opts1 + with pytest.raises(TypeError, match="DataType expected"): + ds.ParquetReadOptions(binary_type=0) + @pytest.mark.parquet def test_parquet_file_format_read_options(): diff --git a/python/pyarrow/tests/test_sparse_tensor.py b/python/pyarrow/tests/test_sparse_tensor.py index eca8090d77a9..46756a11941e 100644 --- a/python/pyarrow/tests/test_sparse_tensor.py +++ b/python/pyarrow/tests/test_sparse_tensor.py @@ -89,6 +89,17 @@ def test_sparse_tensor_attrs(sparse_tensor_type): assert wr() is None +@pytest.mark.parametrize('sparse_tensor_type', [ + pa.SparseCSRMatrix, + pa.SparseCSCMatrix, + pa.SparseCOOTensor, + pa.SparseCSFTensor, +]) +def test_sparse_tensor_from_tensor_rejects_invalid_type(sparse_tensor_type): + with pytest.raises(TypeError, match="Argument 'obj' has incorrect type"): + sparse_tensor_type.from_tensor(0) + + def test_sparse_coo_tensor_base_object(): expected_data = np.array([[8, 2, 5, 3, 4, 6]]).T expected_coords = np.array([ diff --git a/python/pyarrow/tests/test_substrait.py b/python/pyarrow/tests/test_substrait.py index fcd1c8d48c5f..ba5fd95a4494 100644 --- a/python/pyarrow/tests/test_substrait.py +++ b/python/pyarrow/tests/test_substrait.py @@ -1100,6 +1100,12 @@ def test_serializing_schema(): returned = pa.substrait.deserialize_expressions(arrow_substrait_schema.expression) assert returned.schema == expected_schema + with pytest.raises(TypeError, match="Argument 'schema' has incorrect type"): + pa.substrait.serialize_schema(0) + + with pytest.raises(TypeError, match="Argument 'schema' has incorrect type"): + pa.substrait.serialize_expressions([], [], 0) + def test_bound_expression_from_Message(): class FakeMessage: