Skip to main content

minarrow_pyo3/
types.rs

1// Copyright 2025 Peter Garfield Bower
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # Type Wrappers for minarrow-pyo3
16//!
17//! Provides transparent wrapper types around MinArrow types that implement
18//! PyO3 conversion traits for seamless Python interoperability.
19
20use minarrow::{Array, Field, FieldArray, SuperArray, SuperTable, Table};
21use pyo3::prelude::*;
22use std::sync::Arc;
23
24use crate::ffi::{to_py, to_rust};
25
26// PyArray - Wrapper around MinArrow's FieldArray
27
28/// Transparent wrapper around MinArrow's FieldArray.
29///
30/// Enables zero-copy conversion to/from PyArrow arrays via the Arrow C Data Interface.
31/// Preserves exact Arrow type metadata (e.g., Timestamp vs Date64) through the conversion.
32///
33/// # Example (Rust)
34/// ```ignore
35/// use minarrow_pyo3::PyArray;
36/// use minarrow::FieldArray;
37///
38/// #[pyfunction]
39/// fn process_array(arr: PyArray) -> PyResult<PyArray> {
40///     let field_array: FieldArray = arr.into();
41///     // Process...
42///     Ok(PyArray::from(field_array))
43/// }
44/// ```
45#[repr(transparent)]
46#[derive(Debug, Clone)]
47pub struct PyArray(pub FieldArray);
48
49impl PyArray {
50    /// Creates a new PyArray from a FieldArray.
51    pub fn new(field_array: FieldArray) -> Self {
52        Self(field_array)
53    }
54
55    /// Returns a reference to the inner MinArrow Array.
56    pub fn inner(&self) -> &Array {
57        &self.0.array
58    }
59
60    /// Returns a reference to the inner MinArrow FieldArray.
61    pub fn field_array(&self) -> &FieldArray {
62        &self.0
63    }
64
65    /// Returns a reference to the Field metadata.
66    pub fn field(&self) -> &Field {
67        &self.0.field
68    }
69
70    /// Consumes self and returns the inner FieldArray.
71    pub fn into_inner(self) -> FieldArray {
72        self.0
73    }
74}
75
76impl From<FieldArray> for PyArray {
77    fn from(field_array: FieldArray) -> Self {
78        Self(field_array)
79    }
80}
81
82impl From<Arc<Array>> for PyArray {
83    fn from(array: Arc<Array>) -> Self {
84        let field = Field::from_array("", &array, None);
85        Self(FieldArray::new(field, (*array).clone()))
86    }
87}
88
89impl From<Array> for PyArray {
90    fn from(array: Array) -> Self {
91        let field = Field::from_array("", &array, None);
92        Self(FieldArray::new(field, array))
93    }
94}
95
96impl From<PyArray> for FieldArray {
97    fn from(value: PyArray) -> Self {
98        value.0
99    }
100}
101
102impl From<PyArray> for Arc<Array> {
103    fn from(value: PyArray) -> Self {
104        Arc::new(value.0.array)
105    }
106}
107
108impl AsRef<Array> for PyArray {
109    fn as_ref(&self) -> &Array {
110        &self.0.array
111    }
112}
113
114impl<'py> FromPyObject<'py> for PyArray {
115    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
116        let field_array = to_rust::array_to_rust(ob)?;
117        Ok(PyArray(field_array))
118    }
119}
120
121impl<'py> IntoPyObject<'py> for PyArray {
122    type Target = PyAny;
123    type Output = Bound<'py, Self::Target>;
124    type Error = PyErr;
125
126    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
127        // Use the preserved Field metadata for correct Arrow type export
128        to_py::array_to_py(Arc::new(self.0.array), &self.0.field, py)
129    }
130}
131
132// PyRecordBatch - Wrapper around MinArrow's Table
133
134/// Transparent wrapper around MinArrow's Table.
135///
136/// Enables conversion to/from PyArrow RecordBatch. Equivalent to an Arrow RecordBatch
137/// which is a collection of equal-length arrays with schema metadata.
138///
139/// # Example (Rust)
140/// ```ignore
141/// use minarrow_pyo3::PyRecordBatch;
142/// use minarrow::Table;
143///
144/// #[pyfunction]
145/// fn process_batch(batch: PyRecordBatch) -> PyResult<PyRecordBatch> {
146///     let table: Table = batch.into();
147///     // Process...
148///     Ok(PyRecordBatch::from(table))
149/// }
150/// ```
151#[repr(transparent)]
152#[derive(Debug, Clone)]
153pub struct PyRecordBatch(pub Table);
154
155impl PyRecordBatch {
156    /// Creates a new PyRecordBatch from a Table.
157    pub fn new(table: Table) -> Self {
158        Self(table)
159    }
160
161    /// Returns a reference to the inner MinArrow Table.
162    pub fn inner(&self) -> &Table {
163        &self.0
164    }
165
166    /// Consumes self and returns the inner Table.
167    pub fn into_inner(self) -> Table {
168        self.0
169    }
170}
171
172impl From<Table> for PyRecordBatch {
173    fn from(table: Table) -> Self {
174        Self(table)
175    }
176}
177
178impl From<PyRecordBatch> for Table {
179    fn from(value: PyRecordBatch) -> Self {
180        value.0
181    }
182}
183
184impl AsRef<Table> for PyRecordBatch {
185    fn as_ref(&self) -> &Table {
186        &self.0
187    }
188}
189
190impl<'py> FromPyObject<'py> for PyRecordBatch {
191    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
192        let table = to_rust::record_batch_to_rust(ob)?;
193        Ok(PyRecordBatch(table))
194    }
195}
196
197impl<'py> IntoPyObject<'py> for PyRecordBatch {
198    type Target = PyAny;
199    type Output = Bound<'py, Self::Target>;
200    type Error = PyErr;
201
202    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
203        to_py::table_to_py(&self.0, py)
204    }
205}
206
207// PyField - Wrapper around MinArrow's Field
208
209/// Transparent wrapper around MinArrow's Field.
210///
211/// Represents column-level schema metadata including name, type, and nullability.
212#[repr(transparent)]
213#[derive(Debug, Clone)]
214pub struct PyField(pub Field);
215
216impl PyField {
217    /// Creates a new PyField from a Field.
218    pub fn new(field: Field) -> Self {
219        Self(field)
220    }
221
222    /// Returns a reference to the inner MinArrow Field.
223    pub fn inner(&self) -> &Field {
224        &self.0
225    }
226
227    /// Consumes self and returns the inner Field.
228    pub fn into_inner(self) -> Field {
229        self.0
230    }
231}
232
233impl From<Field> for PyField {
234    fn from(field: Field) -> Self {
235        Self(field)
236    }
237}
238
239impl From<PyField> for Field {
240    fn from(value: PyField) -> Self {
241        value.0
242    }
243}
244
245impl AsRef<Field> for PyField {
246    fn as_ref(&self) -> &Field {
247        &self.0
248    }
249}
250
251// PyTable - Wrapper around MinArrow's SuperTable (PyArrow Table)
252
253/// Transparent wrapper around MinArrow's SuperTable.
254///
255/// Enables conversion to/from PyArrow Table. A PyArrow Table is a collection
256/// of RecordBatches (chunked columns), equivalent to MinArrow's SuperTable.
257///
258/// # Example (Rust)
259/// ```ignore
260/// use minarrow_pyo3::PyTable;
261/// use minarrow::SuperTable;
262///
263/// #[pyfunction]
264/// fn process_table(table: PyTable) -> PyResult<PyTable> {
265///     let super_table: SuperTable = table.into();
266///     // Process...
267///     Ok(PyTable::from(super_table))
268/// }
269/// ```
270#[repr(transparent)]
271#[derive(Debug, Clone)]
272pub struct PyTable(pub SuperTable);
273
274impl PyTable {
275    /// Creates a new PyTable from a SuperTable.
276    pub fn new(table: SuperTable) -> Self {
277        Self(table)
278    }
279
280    /// Returns a reference to the inner MinArrow SuperTable.
281    pub fn inner(&self) -> &SuperTable {
282        &self.0
283    }
284
285    /// Consumes self and returns the inner SuperTable.
286    pub fn into_inner(self) -> SuperTable {
287        self.0
288    }
289}
290
291impl From<SuperTable> for PyTable {
292    fn from(table: SuperTable) -> Self {
293        Self(table)
294    }
295}
296
297impl From<PyTable> for SuperTable {
298    fn from(value: PyTable) -> Self {
299        value.0
300    }
301}
302
303impl AsRef<SuperTable> for PyTable {
304    fn as_ref(&self) -> &SuperTable {
305        &self.0
306    }
307}
308
309impl<'py> FromPyObject<'py> for PyTable {
310    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
311        let table = to_rust::table_to_rust(ob)?;
312        Ok(PyTable(table))
313    }
314}
315
316impl<'py> IntoPyObject<'py> for PyTable {
317    type Target = PyAny;
318    type Output = Bound<'py, Self::Target>;
319    type Error = PyErr;
320
321    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
322        to_py::super_table_to_py(&self.0, py)
323    }
324}
325
326// PyChunkedArray - Wrapper around MinArrow's SuperArray
327
328/// Transparent wrapper around MinArrow's SuperArray.
329///
330/// Enables conversion to/from PyArrow ChunkedArray. A PyArrow ChunkedArray
331/// contains multiple array chunks, equivalent to MinArrow's SuperArray.
332///
333/// # Example (Rust)
334/// ```ignore
335/// use minarrow_pyo3::PyChunkedArray;
336/// use minarrow::SuperArray;
337///
338/// #[pyfunction]
339/// fn process_chunked(arr: PyChunkedArray) -> PyResult<PyChunkedArray> {
340///     let super_array: SuperArray = arr.into();
341///     // Process...
342///     Ok(PyChunkedArray::from(super_array))
343/// }
344/// ```
345#[repr(transparent)]
346#[derive(Debug, Clone)]
347pub struct PyChunkedArray(pub SuperArray);
348
349impl PyChunkedArray {
350    /// Creates a new PyChunkedArray from a SuperArray.
351    pub fn new(array: SuperArray) -> Self {
352        Self(array)
353    }
354
355    /// Returns a reference to the inner MinArrow SuperArray.
356    pub fn inner(&self) -> &SuperArray {
357        &self.0
358    }
359
360    /// Consumes self and returns the inner SuperArray.
361    pub fn into_inner(self) -> SuperArray {
362        self.0
363    }
364}
365
366impl From<SuperArray> for PyChunkedArray {
367    fn from(array: SuperArray) -> Self {
368        Self(array)
369    }
370}
371
372impl From<PyChunkedArray> for SuperArray {
373    fn from(value: PyChunkedArray) -> Self {
374        value.0
375    }
376}
377
378impl AsRef<SuperArray> for PyChunkedArray {
379    fn as_ref(&self) -> &SuperArray {
380        &self.0
381    }
382}
383
384impl<'py> FromPyObject<'py> for PyChunkedArray {
385    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
386        let array = to_rust::chunked_array_to_rust(ob)?;
387        Ok(PyChunkedArray(array))
388    }
389}
390
391impl<'py> IntoPyObject<'py> for PyChunkedArray {
392    type Target = PyAny;
393    type Output = Bound<'py, Self::Target>;
394    type Error = PyErr;
395
396    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
397        to_py::super_array_to_py(&self.0, py)
398    }
399}