From e8d7dbfc4af4e89d3b2668f6243c7fb07763efb4 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Mon, 3 Nov 2025 15:17:02 -0500 Subject: [PATCH 1/2] Pass Field information back and forth when using scalar UDFs --- python/datafusion/user_defined.py | 39 +++++-- src/udf.rs | 183 ++++++++++++++++++++++-------- 2 files changed, 168 insertions(+), 54 deletions(-) diff --git a/python/datafusion/user_defined.py b/python/datafusion/user_defined.py index 21b2de634..dede22f19 100644 --- a/python/datafusion/user_defined.py +++ b/python/datafusion/user_defined.py @@ -31,7 +31,7 @@ if TYPE_CHECKING: _R = TypeVar("_R", bound=pa.DataType) - from collections.abc import Callable + from collections.abc import Callable, Sequence class Volatility(Enum): @@ -78,6 +78,27 @@ def __str__(self) -> str: return self.name.lower() +def data_type_or_field_to_field(value: pa.DataType | pa.Field, name: str) -> pa.Field: + """Helper function to return a Field from either a Field or DataType.""" + if isinstance(value, pa.Field): + return value + return pa.field(name, type=value) + + +def data_types_or_fields_to_field_list( + inputs: Sequence[pa.Field | pa.DataType] | pa.Field | pa.DataType, +) -> list[pa.Field]: + """Helper function to return a list of Fields.""" + if isinstance(inputs, pa.DataType): + return [pa.field("value", type=inputs)] + if isinstance(inputs, pa.Field): + return [inputs] + + return [ + data_type_or_field_to_field(v, f"value_{idx}") for (idx, v) in enumerate(inputs) + ] + + class ScalarUDFExportable(Protocol): """Type hint for object that has __datafusion_scalar_udf__ PyCapsule.""" @@ -95,7 +116,7 @@ def __init__( self, name: str, func: Callable[..., _R], - input_types: pa.DataType | list[pa.DataType], + input_types: list[pa.Field], return_type: _R, volatility: Volatility | str, ) -> None: @@ -128,8 +149,8 @@ def __call__(self, *args: Expr) -> Expr: @overload @staticmethod def udf( - input_types: list[pa.DataType], - return_type: _R, + input_types: Sequence[pa.DataType | pa.Field] | pa.DataType | pa.Field, + return_type: pa.DataType | pa.Field, volatility: Volatility | str, name: str | None = None, ) -> Callable[..., ScalarUDF]: ... @@ -138,8 +159,8 @@ def udf( @staticmethod def udf( func: Callable[..., _R], - input_types: list[pa.DataType], - return_type: _R, + input_types: Sequence[pa.DataType | pa.Field] | pa.DataType | pa.Field, + return_type: pa.DataType | pa.Field, volatility: Volatility | str, name: str | None = None, ) -> ScalarUDF: ... @@ -192,8 +213,8 @@ def double_udf(x): def _function( func: Callable[..., _R], - input_types: list[pa.DataType], - return_type: _R, + input_types: Sequence[pa.DataType | pa.Field] | pa.DataType | pa.Field, + return_type: pa.DataType | pa.Field, volatility: Volatility | str, name: str | None = None, ) -> ScalarUDF: @@ -205,6 +226,8 @@ def _function( name = func.__qualname__.lower() else: name = func.__class__.__name__.lower() + input_types = data_types_or_fields_to_field_list(input_types) + return_type = data_type_or_field_to_field(return_type, "value") return ScalarUDF( name=name, func=func, diff --git a/src/udf.rs b/src/udf.rs index a9249d6c8..43edf4ac0 100644 --- a/src/udf.rs +++ b/src/udf.rs @@ -15,71 +15,160 @@ // specific language governing permissions and limitations // under the License. -use std::sync::Arc; - -use datafusion_ffi::udf::{FFI_ScalarUDF, ForeignScalarUDF}; -use pyo3::types::PyCapsule; -use pyo3::{prelude::*, types::PyTuple}; - -use datafusion::arrow::array::{make_array, Array, ArrayData, ArrayRef}; -use datafusion::arrow::datatypes::DataType; -use datafusion::arrow::pyarrow::FromPyArrow; -use datafusion::arrow::pyarrow::{PyArrowType, ToPyArrow}; -use datafusion::error::DataFusionError; -use datafusion::logical_expr::function::ScalarFunctionImplementation; -use datafusion::logical_expr::ScalarUDF; -use datafusion::logical_expr::{create_udf, ColumnarValue}; - use crate::errors::to_datafusion_err; use crate::errors::{py_datafusion_err, PyDataFusionResult}; use crate::expr::PyExpr; use crate::utils::{parse_volatility, validate_pycapsule}; +use arrow::datatypes::{Field, FieldRef}; +use arrow::ffi::{FFI_ArrowArray, FFI_ArrowSchema}; +use datafusion::arrow::array::{make_array, Array, ArrayData, ArrayRef}; +use datafusion::arrow::datatypes::DataType; +use datafusion::arrow::pyarrow::FromPyArrow; +use datafusion::arrow::pyarrow::PyArrowType; +use datafusion::error::DataFusionError; +use datafusion::logical_expr::{ColumnarValue, Volatility}; +use datafusion::logical_expr::{ + ReturnFieldArgs, ScalarFunctionArgs, ScalarUDF, ScalarUDFImpl, Signature, +}; +use datafusion_ffi::udf::{FFI_ScalarUDF, ForeignScalarUDF}; +use pyo3::ffi::Py_uintptr_t; +use pyo3::types::PyCapsule; +use pyo3::{prelude::*, types::PyTuple}; +use std::any::Any; +use std::hash::{Hash, Hasher}; +use std::ptr::addr_of; +use std::sync::Arc; -/// Create a Rust callable function from a python function that expects pyarrow arrays -fn pyarrow_function_to_rust( +/// This struct holds the Python written function that is a +/// ScalarUDF. +#[derive(Debug)] +struct PythonFunctionScalarUDF { + name: String, func: PyObject, -) -> impl Fn(&[ArrayRef]) -> Result { - move |args: &[ArrayRef]| -> Result { + signature: Signature, + return_field: FieldRef, +} + +impl PythonFunctionScalarUDF { + fn new( + name: String, + func: PyObject, + input_fields: Vec, + return_field: Field, + volatility: Volatility, + ) -> Self { + let input_types = input_fields.iter().map(|f| f.data_type().clone()).collect(); + let signature = Signature::exact(input_types, volatility); + Self { + name, + func, + signature, + return_field: Arc::new(return_field), + } + } +} + +impl Eq for PythonFunctionScalarUDF {} +impl PartialEq for PythonFunctionScalarUDF { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + && self.signature == other.signature + && self.return_field == other.return_field + && Python::with_gil(|py| self.func.bind(py).eq(other.func.bind(py)).unwrap_or(false)) + } +} + +impl Hash for PythonFunctionScalarUDF { + fn hash(&self, state: &mut H) { + self.name.hash(state); + self.signature.hash(state); + self.return_field.hash(state); + + Python::with_gil(|py| { + let py_hash = self.func.bind(py).hash().unwrap_or(0); // Handle unhashable objects + + state.write_isize(py_hash); + }); + } +} + +fn array_to_pyarrow_with_field( + py: Python, + array: ArrayRef, + field: &FieldRef, +) -> PyResult { + let array = FFI_ArrowArray::new(&array.to_data()); + let schema = FFI_ArrowSchema::try_from(field).map_err(py_datafusion_err)?; + + let module = py.import("pyarrow")?; + let class = module.getattr("Array")?; + let array = class.call_method1( + "_import_from_c", + ( + addr_of!(array) as Py_uintptr_t, + addr_of!(schema) as Py_uintptr_t, + ), + )?; + Ok(array.unbind()) +} + +impl ScalarUDFImpl for PythonFunctionScalarUDF { + fn as_any(&self) -> &dyn Any { + self + } + + fn name(&self) -> &str { + &self.name + } + + fn signature(&self) -> &Signature { + &self.signature + } + + fn return_type(&self, _arg_types: &[DataType]) -> datafusion::common::Result { + unimplemented!() + } + + fn return_field_from_args( + &self, + _args: ReturnFieldArgs, + ) -> datafusion::common::Result { + Ok(Arc::clone(&self.return_field)) + } + + fn invoke_with_args( + &self, + args: ScalarFunctionArgs, + ) -> datafusion::common::Result { + let num_rows = args.number_rows; + // let args = args.args.into_iter().zip(args.arg_fields.into_iter()); Python::with_gil(|py| { // 1. cast args to Pyarrow arrays let py_args = args - .iter() - .map(|arg| { - arg.into_data() - .to_pyarrow(py) - .map_err(|e| DataFusionError::Execution(format!("{e:?}"))) + .args + .into_iter() + .zip(args.arg_fields) + .map(|(arg, field)| { + let array = arg.to_array(num_rows)?; + array_to_pyarrow_with_field(py, array, &field).map_err(to_datafusion_err) }) .collect::, _>>()?; let py_args = PyTuple::new(py, py_args).map_err(to_datafusion_err)?; // 2. call function - let value = func + let value = self + .func .call(py, py_args, None) .map_err(|e| DataFusionError::Execution(format!("{e:?}")))?; // 3. cast to arrow::array::Array let array_data = ArrayData::from_pyarrow_bound(value.bind(py)) .map_err(|e| DataFusionError::Execution(format!("{e:?}")))?; - Ok(make_array(array_data)) + Ok(ColumnarValue::Array(make_array(array_data))) }) } } -/// Create a DataFusion's UDF implementation from a python function -/// that expects pyarrow arrays. This is more efficient as it performs -/// a zero-copy of the contents. -fn to_scalar_function_impl(func: PyObject) -> ScalarFunctionImplementation { - // Make the python function callable from rust - let pyarrow_func = pyarrow_function_to_rust(func); - - // Convert input/output from datafusion ColumnarValue to arrow arrays - Arc::new(move |args: &[ColumnarValue]| { - let array_refs = ColumnarValue::values_to_arrays(args)?; - let array_result = pyarrow_func(&array_refs)?; - Ok(array_result.into()) - }) -} - /// Represents a PyScalarUDF #[pyclass(frozen, name = "ScalarUDF", module = "datafusion", subclass)] #[derive(Debug, Clone)] @@ -92,19 +181,21 @@ impl PyScalarUDF { #[new] #[pyo3(signature=(name, func, input_types, return_type, volatility))] fn new( - name: &str, + name: String, func: PyObject, - input_types: PyArrowType>, - return_type: PyArrowType, + input_types: PyArrowType>, + return_type: PyArrowType, volatility: &str, ) -> PyResult { - let function = create_udf( + let py_function = PythonFunctionScalarUDF::new( name, + func, input_types.0, return_type.0, parse_volatility(volatility)?, - to_scalar_function_impl(func), ); + let function = ScalarUDF::new_from_impl(py_function); + Ok(Self { function }) } From be7daf5fb186f3b4dff4ee50874020426e9d2a77 Mon Sep 17 00:00:00 2001 From: Tim Saucer Date: Thu, 6 Nov 2025 22:28:24 -0500 Subject: [PATCH 2/2] Add ArrowArrayExportable class and use it to create pyarrow arrays for python UDFs --- pyproject.toml | 1 + python/tests/test_udf.py | 24 ++++++++++++ src/array.rs | 80 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/udf.rs | 31 +++------------- uv.lock | 55 ++------------------------- 6 files changed, 115 insertions(+), 77 deletions(-) create mode 100644 src/array.rs diff --git a/pyproject.toml b/pyproject.toml index 25f30b8e4..6375a205a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -141,6 +141,7 @@ dev = [ "maturin>=1.8.1", "numpy>1.25.0;python_version<'3.14'", "numpy>=2.3.2;python_version>='3.14'", + "pyarrow>=19.0.0", "pre-commit>=4.3.0", "pyyaml>=6.0.3", "pytest>=7.4.4", diff --git a/python/tests/test_udf.py b/python/tests/test_udf.py index a6c047552..380f76726 100644 --- a/python/tests/test_udf.py +++ b/python/tests/test_udf.py @@ -18,6 +18,7 @@ import pyarrow as pa import pytest from datafusion import column, udf +from datafusion import functions as f @pytest.fixture @@ -124,3 +125,26 @@ def udf_with_param(values: pa.Array) -> pa.Array: result = df2.collect()[0].column(0) assert result == pa.array([False, True, True]) + + +def test_udf_with_metadata(ctx) -> None: + from uuid import UUID + + @udf([pa.string()], pa.uuid(), "stable") + def uuid_from_string(uuid_string): + return pa.array((UUID(s).bytes for s in uuid_string.to_pylist()), pa.uuid()) + + @udf([pa.uuid()], pa.int64(), "stable") + def uuid_version(uuid): + return pa.array(s.version for s in uuid.to_pylist()) + + batch = pa.record_batch({"idx": pa.array(range(5))}) + results = ( + ctx.create_dataframe([[batch]]) + .with_column("uuid_string", f.uuid()) + .with_column("uuid", uuid_from_string(column("uuid_string"))) + .select(uuid_version(column("uuid").alias("uuid_version"))) + .collect() + ) + + assert results[0][0].to_pylist() == [4, 4, 4, 4, 4] diff --git a/src/array.rs b/src/array.rs new file mode 100644 index 000000000..3db641a4c --- /dev/null +++ b/src/array.rs @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use crate::errors::PyDataFusionResult; +use crate::utils::validate_pycapsule; +use arrow::array::{Array, ArrayRef}; +use arrow::datatypes::{Field, FieldRef}; +use arrow::ffi::{FFI_ArrowArray, FFI_ArrowSchema}; +use arrow::pyarrow::ToPyArrow; +use pyo3::prelude::{PyAnyMethods, PyCapsuleMethods}; +use pyo3::types::PyCapsule; +use pyo3::{pyclass, pymethods, Bound, PyObject, PyResult, Python}; +use std::sync::Arc; + +/// A Python object which implements the Arrow PyCapsule for importing +/// into other libraries. +#[pyclass(name = "ArrowArrayExportable", module = "datafusion", frozen)] +#[derive(Clone)] +pub struct PyArrowArrayExportable { + array: ArrayRef, + field: FieldRef, +} + +#[pymethods] +impl PyArrowArrayExportable { + #[pyo3(signature = (requested_schema=None))] + fn __arrow_c_array__<'py>( + &'py self, + py: Python<'py>, + requested_schema: Option>, + ) -> PyDataFusionResult<(Bound<'py, PyCapsule>, Bound<'py, PyCapsule>)> { + let field = if let Some(schema_capsule) = requested_schema { + validate_pycapsule(&schema_capsule, "arrow_schema")?; + + let schema_ptr = unsafe { schema_capsule.reference::() }; + let desired_field = Field::try_from(schema_ptr)?; + + Arc::new(desired_field) + } else { + Arc::clone(&self.field) + }; + + let ffi_schema = FFI_ArrowSchema::try_from(&field)?; + let schema_capsule = PyCapsule::new(py, ffi_schema, Some(cr"arrow_schema".into()))?; + + let ffi_array = FFI_ArrowArray::new(&self.array.to_data()); + let array_capsule = PyCapsule::new(py, ffi_array, Some(cr"arrow_array".into()))?; + + Ok((schema_capsule, array_capsule)) + } +} + +impl ToPyArrow for PyArrowArrayExportable { + fn to_pyarrow(&self, py: Python) -> PyResult { + let module = py.import("pyarrow")?; + let method = module.getattr("array")?; + let array = method.call((self.clone(),), None)?; + Ok(array.unbind()) + } +} + +impl PyArrowArrayExportable { + pub fn new(array: ArrayRef, field: FieldRef) -> Self { + Self { array, field } + } +} diff --git a/src/lib.rs b/src/lib.rs index 4f816d887..fe3b9edb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,7 @@ pub mod store; pub mod table; pub mod unparser; +mod array; #[cfg(feature = "substrait")] pub mod substrait; #[allow(clippy::borrow_deref_ref)] diff --git a/src/udf.rs b/src/udf.rs index 43edf4ac0..72df9086d 100644 --- a/src/udf.rs +++ b/src/udf.rs @@ -15,13 +15,14 @@ // specific language governing permissions and limitations // under the License. +use crate::array::PyArrowArrayExportable; use crate::errors::to_datafusion_err; use crate::errors::{py_datafusion_err, PyDataFusionResult}; use crate::expr::PyExpr; use crate::utils::{parse_volatility, validate_pycapsule}; use arrow::datatypes::{Field, FieldRef}; -use arrow::ffi::{FFI_ArrowArray, FFI_ArrowSchema}; -use datafusion::arrow::array::{make_array, Array, ArrayData, ArrayRef}; +use arrow::pyarrow::ToPyArrow; +use datafusion::arrow::array::{make_array, ArrayData}; use datafusion::arrow::datatypes::DataType; use datafusion::arrow::pyarrow::FromPyArrow; use datafusion::arrow::pyarrow::PyArrowType; @@ -31,12 +32,10 @@ use datafusion::logical_expr::{ ReturnFieldArgs, ScalarFunctionArgs, ScalarUDF, ScalarUDFImpl, Signature, }; use datafusion_ffi::udf::{FFI_ScalarUDF, ForeignScalarUDF}; -use pyo3::ffi::Py_uintptr_t; use pyo3::types::PyCapsule; use pyo3::{prelude::*, types::PyTuple}; use std::any::Any; use std::hash::{Hash, Hasher}; -use std::ptr::addr_of; use std::sync::Arc; /// This struct holds the Python written function that is a @@ -92,26 +91,6 @@ impl Hash for PythonFunctionScalarUDF { } } -fn array_to_pyarrow_with_field( - py: Python, - array: ArrayRef, - field: &FieldRef, -) -> PyResult { - let array = FFI_ArrowArray::new(&array.to_data()); - let schema = FFI_ArrowSchema::try_from(field).map_err(py_datafusion_err)?; - - let module = py.import("pyarrow")?; - let class = module.getattr("Array")?; - let array = class.call_method1( - "_import_from_c", - ( - addr_of!(array) as Py_uintptr_t, - addr_of!(schema) as Py_uintptr_t, - ), - )?; - Ok(array.unbind()) -} - impl ScalarUDFImpl for PythonFunctionScalarUDF { fn as_any(&self) -> &dyn Any { self @@ -150,7 +129,9 @@ impl ScalarUDFImpl for PythonFunctionScalarUDF { .zip(args.arg_fields) .map(|(arg, field)| { let array = arg.to_array(num_rows)?; - array_to_pyarrow_with_field(py, array, &field).map_err(to_datafusion_err) + PyArrowArrayExportable::new(array, field) + .to_pyarrow(py) + .map_err(to_datafusion_err) }) .collect::, _>>()?; let py_args = PyTuple::new(py, py_args).map_err(to_datafusion_err)?; diff --git a/uv.lock b/uv.lock index a3da1ce50..a5c860fab 100644 --- a/uv.lock +++ b/uv.lock @@ -253,8 +253,7 @@ wheels = [ name = "datafusion" source = { editable = "." } dependencies = [ - { name = "pyarrow", version = "18.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, - { name = "pyarrow", version = "22.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, + { name = "pyarrow" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] @@ -265,6 +264,7 @@ dev = [ { name = "numpy", version = "2.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, { name = "numpy", version = "2.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, { name = "pre-commit" }, + { name = "pyarrow" }, { name = "pygithub" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -298,6 +298,7 @@ dev = [ { name = "numpy", marker = "python_full_version < '3.14'", specifier = ">1.25.0" }, { name = "numpy", marker = "python_full_version >= '3.14'", specifier = ">=2.3.2" }, { name = "pre-commit", specifier = ">=4.3.0" }, + { name = "pyarrow", specifier = ">=19.0.0" }, { name = "pygithub", specifier = "==2.5.0" }, { name = "pytest", specifier = ">=7.4.4" }, { name = "pytest-asyncio", specifier = ">=0.23.3" }, @@ -920,60 +921,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, ] -[[package]] -name = "pyarrow" -version = "18.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and python_full_version < '3.14'", - "python_full_version == '3.11.*'", - "python_full_version < '3.11'", -] -sdist = { url = "https://files.pythonhosted.org/packages/7f/7b/640785a9062bb00314caa8a387abce547d2a420cf09bd6c715fe659ccffb/pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73", size = 1118671 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/bb/8d4a1573f66e0684f190dd2b55fd0b97a7214de8882d58a3867e777bf640/pyarrow-18.1.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e21488d5cfd3d8b500b3238a6c4b075efabc18f0f6d80b29239737ebd69caa6c", size = 29531620 }, - { url = "https://files.pythonhosted.org/packages/30/90/893acfad917533b624a97b9e498c0e8393908508a0a72d624fe935e632bf/pyarrow-18.1.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b516dad76f258a702f7ca0250885fc93d1fa5ac13ad51258e39d402bd9e2e1e4", size = 30836521 }, - { url = "https://files.pythonhosted.org/packages/a3/2a/526545a7464b5fb2fa6e2c4bad16ca90e59e1843025c534fd907b7f73e5a/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f443122c8e31f4c9199cb23dca29ab9427cef990f283f80fe15b8e124bcc49b", size = 39213905 }, - { url = "https://files.pythonhosted.org/packages/8a/77/4b3fab91a30e19e233e738d0c5eca5a8f6dd05758bc349a2ca262c65de79/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a03da7f2758645d17b7b4f83c8bffeae5bbb7f974523fe901f36288d2eab71", size = 40128881 }, - { url = "https://files.pythonhosted.org/packages/aa/e2/a88e16c5e45e562449c52305bd3bc2f9d704295322d3434656e7ccac1444/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ba17845efe3aa358ec266cf9cc2800fa73038211fb27968bfa88acd09261a470", size = 38627517 }, - { url = "https://files.pythonhosted.org/packages/6d/84/8037c20005ccc7b869726465be0957bd9c29cfc88612962030f08292ad06/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3c35813c11a059056a22a3bef520461310f2f7eea5c8a11ef9de7062a23f8d56", size = 40060187 }, - { url = "https://files.pythonhosted.org/packages/2a/38/d6435c723ff73df8ae74626ea778262fbcc2b9b0d1a4f3db915b61711b05/pyarrow-18.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9736ba3c85129d72aefa21b4f3bd715bc4190fe4426715abfff90481e7d00812", size = 25118314 }, - { url = "https://files.pythonhosted.org/packages/9e/4d/a4988e7d82f4fbc797715db4185939a658eeffb07a25bab7262bed1ea076/pyarrow-18.1.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eaeabf638408de2772ce3d7793b2668d4bb93807deed1725413b70e3156a7854", size = 29554860 }, - { url = "https://files.pythonhosted.org/packages/59/03/3a42c5c1e4bd4c900ab62aa1ff6b472bdb159ba8f1c3e5deadab7222244f/pyarrow-18.1.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:3b2e2239339c538f3464308fd345113f886ad031ef8266c6f004d49769bb074c", size = 30867076 }, - { url = "https://files.pythonhosted.org/packages/75/7e/332055ac913373e89256dce9d14b7708f55f7bd5be631456c897f0237738/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f39a2e0ed32a0970e4e46c262753417a60c43a3246972cfc2d3eb85aedd01b21", size = 39212135 }, - { url = "https://files.pythonhosted.org/packages/8c/64/5099cdb325828722ef7ffeba9a4696f238eb0cdeae227f831c2d77fcf1bd/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31e9417ba9c42627574bdbfeada7217ad8a4cbbe45b9d6bdd4b62abbca4c6f6", size = 40125195 }, - { url = "https://files.pythonhosted.org/packages/83/88/1938d783727db1b178ff71bc6a6143d7939e406db83a9ec23cad3dad325c/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:01c034b576ce0eef554f7c3d8c341714954be9b3f5d5bc7117006b85fcf302fe", size = 38641884 }, - { url = "https://files.pythonhosted.org/packages/5e/b5/9e14e9f7590e0eaa435ecea84dabb137284a4dbba7b3c337b58b65b76d95/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f266a2c0fc31995a06ebd30bcfdb7f615d7278035ec5b1cd71c48d56daaf30b0", size = 40076877 }, - { url = "https://files.pythonhosted.org/packages/4d/a3/817ac7fe0891a2d66e247e223080f3a6a262d8aefd77e11e8c27e6acf4e1/pyarrow-18.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d4f13eee18433f99adefaeb7e01d83b59f73360c231d4782d9ddfaf1c3fbde0a", size = 25119811 }, - { url = "https://files.pythonhosted.org/packages/6a/50/12829e7111b932581e51dda51d5cb39207a056c30fe31ef43f14c63c4d7e/pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d", size = 29514620 }, - { url = "https://files.pythonhosted.org/packages/d1/41/468c944eab157702e96abab3d07b48b8424927d4933541ab43788bb6964d/pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee", size = 30856494 }, - { url = "https://files.pythonhosted.org/packages/68/f9/29fb659b390312a7345aeb858a9d9c157552a8852522f2c8bad437c29c0a/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992", size = 39203624 }, - { url = "https://files.pythonhosted.org/packages/6e/f6/19360dae44200e35753c5c2889dc478154cd78e61b1f738514c9f131734d/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54", size = 40139341 }, - { url = "https://files.pythonhosted.org/packages/bb/e6/9b3afbbcf10cc724312e824af94a2e993d8ace22994d823f5c35324cebf5/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33", size = 38618629 }, - { url = "https://files.pythonhosted.org/packages/3a/2e/3b99f8a3d9e0ccae0e961978a0d0089b25fb46ebbcfb5ebae3cca179a5b3/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30", size = 40078661 }, - { url = "https://files.pythonhosted.org/packages/76/52/f8da04195000099d394012b8d42c503d7041b79f778d854f410e5f05049a/pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99", size = 25092330 }, - { url = "https://files.pythonhosted.org/packages/cb/87/aa4d249732edef6ad88899399047d7e49311a55749d3c373007d034ee471/pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b", size = 29497406 }, - { url = "https://files.pythonhosted.org/packages/3c/c7/ed6adb46d93a3177540e228b5ca30d99fc8ea3b13bdb88b6f8b6467e2cb7/pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2", size = 30835095 }, - { url = "https://files.pythonhosted.org/packages/41/d7/ed85001edfb96200ff606943cff71d64f91926ab42828676c0fc0db98963/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191", size = 39194527 }, - { url = "https://files.pythonhosted.org/packages/59/16/35e28eab126342fa391593415d79477e89582de411bb95232f28b131a769/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa", size = 40131443 }, - { url = "https://files.pythonhosted.org/packages/0c/95/e855880614c8da20f4cd74fa85d7268c725cf0013dc754048593a38896a0/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c", size = 38608750 }, - { url = "https://files.pythonhosted.org/packages/54/9d/f253554b1457d4fdb3831b7bd5f8f00f1795585a606eabf6fec0a58a9c38/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c", size = 40066690 }, - { url = "https://files.pythonhosted.org/packages/2f/58/8912a2563e6b8273e8aa7b605a345bba5a06204549826f6493065575ebc0/pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181", size = 25081054 }, - { url = "https://files.pythonhosted.org/packages/82/f9/d06ddc06cab1ada0c2f2fd205ac8c25c2701182de1b9c4bf7a0a44844431/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc", size = 29525542 }, - { url = "https://files.pythonhosted.org/packages/ab/94/8917e3b961810587ecbdaa417f8ebac0abb25105ae667b7aa11c05876976/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386", size = 30829412 }, - { url = "https://files.pythonhosted.org/packages/5e/e3/3b16c3190f3d71d3b10f6758d2d5f7779ef008c4fd367cedab3ed178a9f7/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324", size = 39119106 }, - { url = "https://files.pythonhosted.org/packages/1d/d6/5d704b0d25c3c79532f8c0639f253ec2803b897100f64bcb3f53ced236e5/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8", size = 40090940 }, - { url = "https://files.pythonhosted.org/packages/37/29/366bc7e588220d74ec00e497ac6710c2833c9176f0372fe0286929b2d64c/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9", size = 38548177 }, - { url = "https://files.pythonhosted.org/packages/c8/11/fabf6ecabb1fe5b7d96889228ca2a9158c4c3bb732e3b8ee3f7f6d40b703/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba", size = 40043567 }, -] - [[package]] name = "pyarrow" version = "22.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/30/53/04a7fdc63e6056116c9ddc8b43bc28c12cdd181b85cbeadb79278475f3ae/pyarrow-22.0.0.tar.gz", hash = "sha256:3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9", size = 1151151 } wheels = [ { url = "https://files.pythonhosted.org/packages/d9/9b/cb3f7e0a345353def531ca879053e9ef6b9f38ed91aebcf68b09ba54dec0/pyarrow-22.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:77718810bd3066158db1e95a63c160ad7ce08c6b0710bc656055033e39cdad88", size = 34223968 },