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