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