1use std::convert::{From, TryFrom};
73use std::ffi::CStr;
74use std::ptr::NonNull;
75use std::sync::Arc;
76
77use arrow_array::ffi;
78use arrow_array::ffi::{FFI_ArrowArray, FFI_ArrowSchema};
79use arrow_array::ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream};
80use arrow_array::{
81 RecordBatch, RecordBatchIterator, RecordBatchOptions, RecordBatchReader, StructArray,
82 make_array,
83};
84use arrow_data::ArrayData;
85use arrow_schema::{ArrowError, DataType, Field, Schema, SchemaRef};
86use pyo3::exceptions::{PyTypeError, PyValueError};
87use pyo3::ffi::Py_uintptr_t;
88use pyo3::prelude::*;
89use pyo3::sync::PyOnceLock;
90use pyo3::types::{PyCapsule, PyDict, PyList, PyString, PyType};
91use pyo3::{CastError, import_exception, intern};
92#[cfg(feature = "experimental-inspect")]
93use pyo3::{
94 inspect::PyStaticExpr, type_hint_identifier, type_hint_subscript, type_hint_union,
95 type_object::PyTypeInfo,
96};
97
98macro_rules! type_hint {
101 ($name:ident = $hint:expr) => {
102 #[cfg(feature = "experimental-inspect")]
103 const $name: PyStaticExpr = $hint;
104 };
105}
106
107import_exception!(pyarrow, ArrowException);
108pub type PyArrowException = ArrowException;
110
111fn to_py_err(err: ArrowError) -> PyErr {
112 PyArrowException::new_err(err.to_string())
113}
114
115#[cfg(feature = "experimental-inspect")]
123const ARRAY_STREAM_INPUT_TYPE: PyStaticExpr = type_hint_union!(
124 type_hint_identifier!("pyarrow", "RecordBatchReader"),
125 type_hint_identifier!("pyarrow", "Table")
126);
127
128pub trait FromPyArrow: Sized {
130 #[cfg(feature = "experimental-inspect")]
142 const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete");
143
144 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self>;
148}
149
150pub trait ToPyArrow {
152 #[cfg(feature = "experimental-inspect")]
157 const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete");
158
159 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>>;
161}
162
163pub trait IntoPyArrow {
165 #[cfg(feature = "experimental-inspect")]
169 const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete");
170
171 fn into_pyarrow(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>>;
173}
174
175impl<T: ToPyArrow> IntoPyArrow for T {
176 type_hint!(OUTPUT_TYPE = <T as ToPyArrow>::OUTPUT_TYPE);
177
178 fn into_pyarrow(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
179 self.to_pyarrow(py)
180 }
181}
182
183fn validate_class(expected: &Bound<PyType>, value: &Bound<PyAny>) -> PyResult<()> {
184 if !value.is_instance(expected)? {
185 return Err(PyTypeError::new_err(format!(
186 "Expected instance of {}, got {}",
187 expected.fully_qualified_name()?,
188 value.get_type().fully_qualified_name()?
189 )));
190 }
191 Ok(())
192}
193
194impl FromPyArrow for DataType {
195 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "DataType"));
196
197 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
198 if let Some(capsule) =
202 call_capsule_method_if_exists(value, intern!(value.py(), "__arrow_c_schema__"))?
203 {
204 let schema_ptr = extract_capsule::<FFI_ArrowSchema>(
205 &capsule,
206 c"arrow_schema",
207 "__arrow_c_schema__",
208 )?;
209 return unsafe { DataType::try_from(schema_ptr.as_ref()) }.map_err(to_py_err);
210 }
211
212 validate_class(data_type_class(value.py())?, value)?;
213
214 let mut c_schema = FFI_ArrowSchema::empty();
215 value.call_method1(
216 intern!(value.py(), "_export_to_c"),
217 (&raw mut c_schema as Py_uintptr_t,),
218 )?;
219 DataType::try_from(&c_schema).map_err(to_py_err)
220 }
221}
222
223impl ToPyArrow for DataType {
224 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "DataType"));
225
226 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
227 let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
228 data_type_class(py)?.call_method1(
229 intern!(py, "_import_from_c"),
230 (&raw const c_schema as Py_uintptr_t,),
231 )
232 }
233}
234
235impl FromPyArrow for Field {
236 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "Field"));
237
238 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
239 if let Some(capsule) =
243 call_capsule_method_if_exists(value, intern!(value.py(), "__arrow_c_schema__"))?
244 {
245 let schema_ptr = extract_capsule::<FFI_ArrowSchema>(
246 &capsule,
247 c"arrow_schema",
248 "__arrow_c_schema__",
249 )?;
250 return unsafe { Field::try_from(schema_ptr.as_ref()) }.map_err(to_py_err);
251 }
252
253 validate_class(field_class(value.py())?, value)?;
254
255 let mut c_schema = FFI_ArrowSchema::empty();
256 value.call_method1(
257 intern!(value.py(), "_export_to_c"),
258 (&raw mut c_schema as Py_uintptr_t,),
259 )?;
260 Field::try_from(&c_schema).map_err(to_py_err)
261 }
262}
263
264impl ToPyArrow for Field {
265 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "Field"));
266
267 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
268 let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
269 field_class(py)?.call_method1(
270 intern!(py, "_import_from_c"),
271 (&raw const c_schema as Py_uintptr_t,),
272 )
273 }
274}
275
276impl FromPyArrow for Schema {
277 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "Schema"));
278
279 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
280 if let Some(capsule) =
284 call_capsule_method_if_exists(value, intern!(value.py(), "__arrow_c_schema__"))?
285 {
286 let schema_ptr = extract_capsule::<FFI_ArrowSchema>(
287 &capsule,
288 c"arrow_schema",
289 "__arrow_c_schema__",
290 )?;
291 return unsafe { Schema::try_from(schema_ptr.as_ref()) }.map_err(to_py_err);
292 }
293
294 validate_class(schema_class(value.py())?, value)?;
295
296 let mut c_schema = FFI_ArrowSchema::empty();
297 value.call_method1(
298 intern!(value.py(), "_export_to_c"),
299 (&raw mut c_schema as Py_uintptr_t,),
300 )?;
301 Schema::try_from(&c_schema).map_err(to_py_err)
302 }
303}
304
305impl ToPyArrow for Schema {
306 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "Schema"));
307
308 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
309 let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
310 schema_class(py)?.call_method1(
311 intern!(py, "_import_from_c"),
312 (&raw const c_schema as Py_uintptr_t,),
313 )
314 }
315}
316
317impl FromPyArrow for ArrayData {
318 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "Array"));
319
320 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
321 if let Some((schema_capsule, array_capsule)) = call_arrow_c_array_method_if_exists(value)? {
325 let schema_ptr =
326 extract_capsule(&schema_capsule, c"arrow_schema", "__arrow_c_array__")?;
327 let array_ptr = extract_capsule(&array_capsule, c"arrow_array", "__arrow_c_array__")?;
328 let array = unsafe { FFI_ArrowArray::from_raw(array_ptr.as_ptr()) };
329 return unsafe { ffi::from_ffi(array, schema_ptr.as_ref()) }.map_err(to_py_err);
330 }
331
332 validate_class(array_class(value.py())?, value)?;
333
334 let mut array = FFI_ArrowArray::empty();
336 let mut schema = FFI_ArrowSchema::empty();
337
338 value.call_method1(
342 intern!(value.py(), "_export_to_c"),
343 (
344 &raw mut array as Py_uintptr_t,
345 &raw mut schema as Py_uintptr_t,
346 ),
347 )?;
348
349 unsafe { ffi::from_ffi(array, &schema) }.map_err(to_py_err)
350 }
351}
352
353impl ToPyArrow for ArrayData {
354 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "Array"));
355
356 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
357 let array = FFI_ArrowArray::new(self);
358 let schema = FFI_ArrowSchema::try_from(self.data_type()).map_err(to_py_err)?;
359 array_class(py)?.call_method1(
360 intern!(py, "_import_from_c"),
361 (
362 &raw const array as Py_uintptr_t,
363 &raw const schema as Py_uintptr_t,
364 ),
365 )
366 }
367}
368
369impl<T: FromPyArrow> FromPyArrow for Vec<T> {
370 type_hint!(
371 INPUT_TYPE = type_hint_subscript!(
372 type_hint_identifier!("collections.abc", "Iterable"),
373 <T as FromPyArrow>::INPUT_TYPE
374 )
375 );
376
377 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
378 let mut v = Vec::with_capacity(value.len().unwrap_or(0));
379 for item in value.try_iter()? {
380 v.push(T::from_pyarrow_bound(&item?)?);
381 }
382 Ok(v)
383 }
384}
385
386impl<T: ToPyArrow> ToPyArrow for Vec<T> {
387 type_hint!(
388 OUTPUT_TYPE = type_hint_subscript!(PyList::TYPE_HINT, <T as ToPyArrow>::OUTPUT_TYPE)
389 );
390
391 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
392 self.iter()
393 .map(|v| v.to_pyarrow(py))
394 .collect::<PyResult<Vec<_>>>()?
395 .into_pyobject(py)
396 }
397}
398
399impl FromPyArrow for RecordBatch {
400 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatch"));
401
402 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
403 if let Some((schema_capsule, array_capsule)) = call_arrow_c_array_method_if_exists(value)? {
408 let schema_ptr =
409 extract_capsule(&schema_capsule, c"arrow_schema", "__arrow_c_array__")?;
410 let array_ptr = extract_capsule(&array_capsule, c"arrow_array", "__arrow_c_array__")?;
411 let ffi_array = unsafe { FFI_ArrowArray::from_raw(array_ptr.as_ptr()) };
412 let array_data =
413 unsafe { ffi::from_ffi(ffi_array, schema_ptr.as_ref()) }.map_err(to_py_err)?;
414 if !matches!(array_data.data_type(), DataType::Struct(_)) {
415 return Err(PyTypeError::new_err(format!(
416 "Expected Struct type from __arrow_c_array__, found {}.",
417 array_data.data_type()
418 )));
419 }
420 let options = RecordBatchOptions::default().with_row_count(Some(array_data.len()));
421 let array = StructArray::from(array_data);
422 let schema =
425 unsafe { Arc::new(Schema::try_from(schema_ptr.as_ref()).map_err(to_py_err)?) };
426 let (_fields, columns, nulls) = array.into_parts();
427 if nulls.map(|n| n.null_count()).unwrap_or_default() != 0 {
428 return Err(PyValueError::new_err(
429 "Cannot convert nullable StructArray to RecordBatch, see StructArray documentation",
430 ));
431 }
432 return RecordBatch::try_new_with_options(schema, columns, &options).map_err(to_py_err);
433 }
434
435 validate_class(record_batch_class(value.py())?, value)?;
436 let schema = value.getattr("schema")?;
438 let schema = Arc::new(Schema::from_pyarrow_bound(&schema)?);
439
440 let arrays = value.getattr("columns")?;
441 let arrays = arrays
442 .cast::<PyList>()?
443 .iter()
444 .map(|a| Ok(make_array(ArrayData::from_pyarrow_bound(&a)?)))
445 .collect::<PyResult<_>>()?;
446
447 let row_count = value
448 .getattr("num_rows")
449 .ok()
450 .and_then(|x| x.extract().ok());
451 let options = RecordBatchOptions::default().with_row_count(row_count);
452
453 let batch =
454 RecordBatch::try_new_with_options(schema, arrays, &options).map_err(to_py_err)?;
455 Ok(batch)
456 }
457}
458
459impl ToPyArrow for RecordBatch {
460 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatch"));
461
462 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
463 let reader = RecordBatchIterator::new(vec![Ok(self.clone())], self.schema());
465 let reader: Box<dyn RecordBatchReader + Send> = Box::new(reader);
466 let py_reader = reader.into_pyarrow(py)?;
467 py_reader.call_method0(intern!(py, "read_next_batch"))
468 }
469}
470
471impl FromPyArrow for ArrowArrayStreamReader {
473 type_hint!(INPUT_TYPE = ARRAY_STREAM_INPUT_TYPE);
474
475 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
476 if let Some(capsule) =
480 call_capsule_method_if_exists(value, intern!(value.py(), "__arrow_c_stream__"))?
481 {
482 let stream_ptr =
483 extract_capsule(&capsule, c"arrow_array_stream", "__arrow_c_stream__")?;
484 let stream = unsafe { FFI_ArrowArrayStream::from_raw(stream_ptr.as_ptr()) };
485
486 let stream_reader = ArrowArrayStreamReader::try_new(stream)
487 .map_err(|err| PyValueError::new_err(err.to_string()))?;
488
489 return Ok(stream_reader);
490 }
491
492 validate_class(record_batch_reader_class(value.py())?, value)?;
493
494 let mut stream = FFI_ArrowArrayStream::empty();
496
497 value.call_method1(
501 intern!(value.py(), "_export_to_c"),
502 (&raw mut stream as Py_uintptr_t,),
503 )?;
504
505 ArrowArrayStreamReader::try_new(stream)
506 .map_err(|err| PyValueError::new_err(err.to_string()))
507 }
508}
509
510impl IntoPyArrow for Box<dyn RecordBatchReader + Send> {
512 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatchReader"));
513
514 fn into_pyarrow(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
517 let stream = FFI_ArrowArrayStream::new(self);
518 record_batch_reader_class(py)?.call_method1(
519 intern!(py, "_import_from_c"),
520 (&raw const stream as Py_uintptr_t,),
521 )
522 }
523}
524
525impl IntoPyArrow for ArrowArrayStreamReader {
527 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatchReader"));
528
529 fn into_pyarrow(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
530 let boxed: Box<dyn RecordBatchReader + Send> = Box::new(self);
531 boxed.into_pyarrow(py)
532 }
533}
534
535#[derive(Clone)]
553pub struct Table {
554 record_batches: Vec<RecordBatch>,
555 schema: SchemaRef,
556}
557
558impl Table {
559 pub fn try_new(
560 record_batches: Vec<RecordBatch>,
561 schema: SchemaRef,
562 ) -> Result<Self, ArrowError> {
563 for record_batch in &record_batches {
564 if schema != record_batch.schema() {
565 return Err(ArrowError::SchemaError(format!(
566 "All record batches must have the same schema. \
567 Expected schema: {:?}, got schema: {:?}",
568 schema,
569 record_batch.schema()
570 )));
571 }
572 }
573 Ok(Self {
574 record_batches,
575 schema,
576 })
577 }
578
579 pub fn record_batches(&self) -> &[RecordBatch] {
580 &self.record_batches
581 }
582
583 pub fn schema(&self) -> SchemaRef {
584 self.schema.clone()
585 }
586
587 pub fn into_inner(self) -> (Vec<RecordBatch>, SchemaRef) {
588 (self.record_batches, self.schema)
589 }
590}
591
592impl TryFrom<Box<dyn RecordBatchReader>> for Table {
593 type Error = ArrowError;
594
595 fn try_from(value: Box<dyn RecordBatchReader>) -> Result<Self, ArrowError> {
596 let schema = value.schema();
597 let batches = value.collect::<Result<Vec<_>, _>>()?;
598 Self::try_new(batches, schema)
599 }
600}
601
602impl FromPyArrow for Table {
604 type_hint!(INPUT_TYPE = ARRAY_STREAM_INPUT_TYPE);
605
606 fn from_pyarrow_bound(ob: &Bound<PyAny>) -> PyResult<Self> {
607 let reader: Box<dyn RecordBatchReader> =
608 Box::new(ArrowArrayStreamReader::from_pyarrow_bound(ob)?);
609 Self::try_from(reader).map_err(|err| PyValueError::new_err(err.to_string()))
610 }
611}
612
613impl IntoPyArrow for Table {
615 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "Table"));
616
617 fn into_pyarrow(self, py: Python) -> PyResult<Bound<PyAny>> {
618 let py_batches = PyList::new(py, self.record_batches.into_iter().map(PyArrowType))?;
619 let py_schema = PyArrowType(Arc::unwrap_or_clone(self.schema));
620
621 let kwargs = PyDict::new(py);
622 kwargs.set_item("schema", py_schema)?;
623
624 table_class(py)?.call_method("from_batches", (py_batches,), Some(&kwargs))
625 }
626}
627
628fn array_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
629 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
630 TYPE.import(py, "pyarrow", "Array")
631}
632
633fn record_batch_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
634 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
635 TYPE.import(py, "pyarrow", "RecordBatch")
636}
637
638fn record_batch_reader_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
639 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
640 TYPE.import(py, "pyarrow", "RecordBatchReader")
641}
642
643fn data_type_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
644 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
645 TYPE.import(py, "pyarrow", "DataType")
646}
647
648fn field_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
649 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
650 TYPE.import(py, "pyarrow", "Field")
651}
652
653fn schema_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
654 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
655 TYPE.import(py, "pyarrow", "Schema")
656}
657
658fn table_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
659 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
660 TYPE.import(py, "pyarrow", "Table")
661}
662
663#[derive(Debug)]
669pub struct PyArrowType<T>(pub T);
670
671impl<T: FromPyArrow> FromPyObject<'_, '_> for PyArrowType<T> {
672 type Error = PyErr;
673
674 type_hint!(INPUT_TYPE = <T as FromPyArrow>::INPUT_TYPE);
675
676 fn extract(value: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
677 Ok(Self(T::from_pyarrow_bound(&value)?))
678 }
679}
680
681impl<'py, T: IntoPyArrow> IntoPyObject<'py> for PyArrowType<T> {
682 type Target = PyAny;
683
684 type Output = Bound<'py, Self::Target>;
685
686 type Error = PyErr;
687
688 type_hint!(OUTPUT_TYPE = <T as IntoPyArrow>::OUTPUT_TYPE);
689
690 fn into_pyobject(self, py: Python<'py>) -> PyResult<Self::Output> {
691 self.0.into_pyarrow(py)
692 }
693}
694
695impl<T> From<T> for PyArrowType<T> {
696 fn from(s: T) -> Self {
697 Self(s)
698 }
699}
700
701fn call_capsule_method_if_exists<'py>(
702 object: &Bound<'py, PyAny>,
703 method_name: &Bound<'py, PyString>,
704) -> PyResult<Option<Bound<'py, PyCapsule>>> {
705 let Some(method) = object.getattr_opt(method_name)? else {
706 return Ok(None);
707 };
708 Ok(Some(method.call0()?.extract().map_err(
709 |e: CastError| {
710 wrapping_type_error(
711 object.py(),
712 e.into(),
713 format!("Expected {method_name} to return a capsule."),
714 )
715 },
716 )?))
717}
718
719fn call_arrow_c_array_method_if_exists<'py>(
720 object: &Bound<'py, PyAny>,
721) -> PyResult<Option<(Bound<'py, PyCapsule>, Bound<'py, PyCapsule>)>> {
722 let Some(method) = object.getattr_opt(intern!(object.py(), "__arrow_c_array__"))? else {
723 return Ok(None);
724 };
725 Ok(Some(method.call0()?.extract().map_err(|e| {
726 wrapping_type_error(
727 object.py(),
728 e,
729 "Expected __arrow_c_array__ to return a tuple of (schema, array) capsules.".into(),
730 )
731 })?))
732}
733
734fn extract_capsule<T>(
735 capsule: &Bound<PyCapsule>,
736 capsule_name: &CStr,
737 method_name: &'static str,
738) -> PyResult<NonNull<T>> {
739 Ok(capsule
740 .pointer_checked(Some(capsule_name))
741 .map_err(|e| {
742 wrapping_type_error(
743 capsule.py(),
744 e,
745 format!(
746 "Expected {method_name} to return a {} capsule.",
747 capsule_name.to_str().unwrap(),
748 ),
749 )
750 })?
751 .cast::<T>())
752}
753
754fn wrapping_type_error(py: Python<'_>, error: PyErr, message: String) -> PyErr {
755 let e = PyTypeError::new_err(message);
756 e.set_cause(py, Some(error));
757 e
758}
759
760#[cfg(all(test, feature = "experimental-inspect"))]
761mod introspection_tests {
762 use super::*;
763 use pyo3::{FromPyObject, IntoPyObject};
764
765 fn input_type<T: FromPyArrow>() -> String {
767 <PyArrowType<T> as FromPyObject<'_, '_>>::INPUT_TYPE.to_string()
768 }
769
770 fn output_type<T: IntoPyArrow>() -> String
772 where
773 PyArrowType<T>: for<'py> IntoPyObject<'py>,
774 {
775 <PyArrowType<T> as IntoPyObject<'_>>::OUTPUT_TYPE.to_string()
776 }
777
778 #[test]
779 fn scalar_types_map_to_their_pyarrow_class() {
780 assert_eq!(input_type::<DataType>(), "pyarrow.DataType");
781 assert_eq!(output_type::<DataType>(), "pyarrow.DataType");
782 assert_eq!(input_type::<Field>(), "pyarrow.Field");
783 assert_eq!(output_type::<Field>(), "pyarrow.Field");
784 assert_eq!(input_type::<Schema>(), "pyarrow.Schema");
785 assert_eq!(output_type::<Schema>(), "pyarrow.Schema");
786 assert_eq!(input_type::<RecordBatch>(), "pyarrow.RecordBatch");
787 assert_eq!(output_type::<RecordBatch>(), "pyarrow.RecordBatch");
788 }
789
790 #[test]
792 fn array_data_maps_to_pyarrow_array() {
793 assert_eq!(input_type::<ArrayData>(), "pyarrow.Array");
794 assert_eq!(output_type::<ArrayData>(), "pyarrow.Array");
795 }
796
797 #[test]
800 fn vec_is_iterable_in_and_list_out() {
801 assert_eq!(
802 input_type::<Vec<RecordBatch>>(),
803 "collections.abc.Iterable[pyarrow.RecordBatch]"
804 );
805 assert_eq!(
806 output_type::<Vec<RecordBatch>>(),
807 "builtins.list[pyarrow.RecordBatch]"
808 );
809 }
810
811 #[test]
814 fn readers_and_tables_map_to_their_pyarrow_class() {
815 assert_eq!(
816 input_type::<ArrowArrayStreamReader>(),
817 "pyarrow.RecordBatchReader | pyarrow.Table"
818 );
819 assert_eq!(
820 output_type::<ArrowArrayStreamReader>(),
821 "pyarrow.RecordBatchReader"
822 );
823 assert_eq!(
824 output_type::<Box<dyn RecordBatchReader + Send>>(),
825 "pyarrow.RecordBatchReader"
826 );
827 assert_eq!(
828 input_type::<Table>(),
829 "pyarrow.RecordBatchReader | pyarrow.Table"
830 );
831 assert_eq!(output_type::<Table>(), "pyarrow.Table");
832 }
833}