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::{FFI_ArrowArrayStream, RecordBatchStreamReader};
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 FromPyArrowUnchecked: Sized {
139 unsafe fn from_pyarrow_bound_unchecked(value: &Bound<PyAny>) -> PyResult<Self>;
146}
147
148pub trait FromPyArrow: Sized {
150 #[cfg(feature = "experimental-inspect")]
162 const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete");
163
164 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self>;
168}
169
170pub trait ToPyArrow {
172 #[cfg(feature = "experimental-inspect")]
177 const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete");
178
179 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>>;
181}
182
183pub trait IntoPyArrow {
185 #[cfg(feature = "experimental-inspect")]
189 const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete");
190
191 fn into_pyarrow(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>>;
193}
194
195impl<T: ToPyArrow> IntoPyArrow for T {
196 type_hint!(OUTPUT_TYPE = <T as ToPyArrow>::OUTPUT_TYPE);
197
198 fn into_pyarrow(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
199 self.to_pyarrow(py)
200 }
201}
202
203fn validate_class(expected: &Bound<PyType>, value: &Bound<PyAny>) -> PyResult<()> {
204 if !value.is_instance(expected)? {
205 return Err(PyTypeError::new_err(format!(
206 "Expected instance of {}, got {}",
207 expected.fully_qualified_name()?,
208 value.get_type().fully_qualified_name()?
209 )));
210 }
211 Ok(())
212}
213
214impl FromPyArrow for DataType {
215 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "DataType"));
216
217 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
218 if let Some(capsule) =
222 call_capsule_method_if_exists(value, intern!(value.py(), "__arrow_c_schema__"))?
223 {
224 let schema_ptr = extract_capsule::<FFI_ArrowSchema>(
225 &capsule,
226 c"arrow_schema",
227 "__arrow_c_schema__",
228 )?;
229 return unsafe { DataType::try_from(schema_ptr.as_ref()) }.map_err(to_py_err);
230 }
231
232 validate_class(data_type_class(value.py())?, value)?;
233
234 let mut c_schema = FFI_ArrowSchema::empty();
235 value.call_method1(
236 intern!(value.py(), "_export_to_c"),
237 (&raw mut c_schema as Py_uintptr_t,),
238 )?;
239 DataType::try_from(&c_schema).map_err(to_py_err)
240 }
241}
242
243impl ToPyArrow for DataType {
244 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "DataType"));
245
246 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
247 let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
248 data_type_class(py)?.call_method1(
249 intern!(py, "_import_from_c"),
250 (&raw const c_schema as Py_uintptr_t,),
251 )
252 }
253}
254
255impl FromPyArrow for Field {
256 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "Field"));
257
258 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
259 if let Some(capsule) =
263 call_capsule_method_if_exists(value, intern!(value.py(), "__arrow_c_schema__"))?
264 {
265 let schema_ptr = extract_capsule::<FFI_ArrowSchema>(
266 &capsule,
267 c"arrow_schema",
268 "__arrow_c_schema__",
269 )?;
270 return unsafe { Field::try_from(schema_ptr.as_ref()) }.map_err(to_py_err);
271 }
272
273 validate_class(field_class(value.py())?, value)?;
274
275 let mut c_schema = FFI_ArrowSchema::empty();
276 value.call_method1(
277 intern!(value.py(), "_export_to_c"),
278 (&raw mut c_schema as Py_uintptr_t,),
279 )?;
280 Field::try_from(&c_schema).map_err(to_py_err)
281 }
282}
283
284impl ToPyArrow for Field {
285 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "Field"));
286
287 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
288 let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
289 field_class(py)?.call_method1(
290 intern!(py, "_import_from_c"),
291 (&raw const c_schema as Py_uintptr_t,),
292 )
293 }
294}
295
296impl FromPyArrow for Schema {
297 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "Schema"));
298
299 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
300 if let Some(capsule) =
304 call_capsule_method_if_exists(value, intern!(value.py(), "__arrow_c_schema__"))?
305 {
306 let schema_ptr = extract_capsule::<FFI_ArrowSchema>(
307 &capsule,
308 c"arrow_schema",
309 "__arrow_c_schema__",
310 )?;
311 return unsafe { Schema::try_from(schema_ptr.as_ref()) }.map_err(to_py_err);
312 }
313
314 validate_class(schema_class(value.py())?, value)?;
315
316 let mut c_schema = FFI_ArrowSchema::empty();
317 value.call_method1(
318 intern!(value.py(), "_export_to_c"),
319 (&raw mut c_schema as Py_uintptr_t,),
320 )?;
321 Schema::try_from(&c_schema).map_err(to_py_err)
322 }
323}
324
325impl ToPyArrow for Schema {
326 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "Schema"));
327
328 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
329 let c_schema = FFI_ArrowSchema::try_from(self).map_err(to_py_err)?;
330 schema_class(py)?.call_method1(
331 intern!(py, "_import_from_c"),
332 (&raw const c_schema as Py_uintptr_t,),
333 )
334 }
335}
336
337impl FromPyArrowUnchecked for ArrayData {
338 unsafe fn from_pyarrow_bound_unchecked(value: &Bound<PyAny>) -> PyResult<Self> {
339 if let Some((schema_capsule, array_capsule)) = call_arrow_c_array_method_if_exists(value)? {
343 let schema_ptr =
344 extract_capsule(&schema_capsule, c"arrow_schema", "__arrow_c_array__")?;
345 let array_ptr = extract_capsule(&array_capsule, c"arrow_array", "__arrow_c_array__")?;
346 let array = unsafe { FFI_ArrowArray::from_raw(array_ptr.as_ptr()) };
347 return unsafe { ffi::from_ffi(array, schema_ptr.as_ref()) }.map_err(to_py_err);
348 }
349
350 validate_class(array_class(value.py())?, value)?;
351
352 let mut array = FFI_ArrowArray::empty();
354 let mut schema = FFI_ArrowSchema::empty();
355
356 value.call_method1(
360 intern!(value.py(), "_export_to_c"),
361 (
362 &raw mut array as Py_uintptr_t,
363 &raw mut schema as Py_uintptr_t,
364 ),
365 )?;
366
367 unsafe { ffi::from_ffi(array, &schema) }.map_err(to_py_err)
368 }
369}
370
371impl FromPyArrow for ArrayData {
372 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "Array"));
373
374 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
375 let data = unsafe { ArrayData::from_pyarrow_bound_unchecked(value)? };
376 data.validate_full().map_err(to_py_err)?;
377 Ok(data)
378 }
379}
380
381impl ToPyArrow for ArrayData {
382 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "Array"));
383
384 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
385 let array = FFI_ArrowArray::new(self);
386 let schema = FFI_ArrowSchema::try_from(self.data_type()).map_err(to_py_err)?;
387 array_class(py)?.call_method1(
388 intern!(py, "_import_from_c"),
389 (
390 &raw const array as Py_uintptr_t,
391 &raw const schema as Py_uintptr_t,
392 ),
393 )
394 }
395}
396
397impl<T: FromPyArrow> FromPyArrow for Vec<T> {
398 type_hint!(
399 INPUT_TYPE = type_hint_subscript!(
400 type_hint_identifier!("collections.abc", "Iterable"),
401 <T as FromPyArrow>::INPUT_TYPE
402 )
403 );
404
405 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
406 let mut v = Vec::with_capacity(value.len().unwrap_or(0));
407 for item in value.try_iter()? {
408 v.push(T::from_pyarrow_bound(&item?)?);
409 }
410 Ok(v)
411 }
412}
413
414impl<T: ToPyArrow> ToPyArrow for Vec<T> {
415 type_hint!(
416 OUTPUT_TYPE = type_hint_subscript!(PyList::TYPE_HINT, <T as ToPyArrow>::OUTPUT_TYPE)
417 );
418
419 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
420 self.iter()
421 .map(|v| v.to_pyarrow(py))
422 .collect::<PyResult<Vec<_>>>()?
423 .into_pyobject(py)
424 }
425}
426
427fn record_batch_from_pyarrow_bound_impl(
428 value: &Bound<PyAny>,
429 validate: bool,
430) -> PyResult<RecordBatch> {
431 if let Some((schema_capsule, array_capsule)) = call_arrow_c_array_method_if_exists(value)? {
435 let schema_ptr = extract_capsule(&schema_capsule, c"arrow_schema", "__arrow_c_array__")?;
436 let array_ptr = extract_capsule(&array_capsule, c"arrow_array", "__arrow_c_array__")?;
437 let ffi_array = unsafe { FFI_ArrowArray::from_raw(array_ptr.as_ptr()) };
438 let array_data =
439 unsafe { ffi::from_ffi(ffi_array, schema_ptr.as_ref()) }.map_err(to_py_err)?;
440 if validate {
441 array_data.validate_full().map_err(to_py_err)?;
442 }
443 if !matches!(array_data.data_type(), DataType::Struct(_)) {
444 return Err(PyTypeError::new_err(format!(
445 "Expected Struct type from __arrow_c_array__, found {}.",
446 array_data.data_type()
447 )));
448 }
449 let options = RecordBatchOptions::default().with_row_count(Some(array_data.len()));
450 let array = StructArray::from(array_data);
451 let schema = unsafe { Arc::new(Schema::try_from(schema_ptr.as_ref()).map_err(to_py_err)?) };
454 let (_fields, columns, nulls) = array.into_parts();
455 if nulls.map(|n| n.null_count()).unwrap_or_default() != 0 {
456 return Err(PyValueError::new_err(
457 "Cannot convert nullable StructArray to RecordBatch, see StructArray documentation",
458 ));
459 }
460 return RecordBatch::try_new_with_options(schema, columns, &options).map_err(to_py_err);
461 }
462
463 validate_class(record_batch_class(value.py())?, value)?;
464 let schema = value.getattr("schema")?;
466 let schema = Arc::new(Schema::from_pyarrow_bound(&schema)?);
467
468 let arrays = value.getattr("columns")?;
469 let arrays = arrays
470 .cast::<PyList>()?
471 .iter()
472 .map(|a| {
473 let data = unsafe { ArrayData::from_pyarrow_bound_unchecked(&a)? };
474 if validate {
475 data.validate_full().map_err(to_py_err)?;
476 }
477 Ok(make_array(data))
478 })
479 .collect::<PyResult<_>>()?;
480
481 let row_count = value
482 .getattr("num_rows")
483 .ok()
484 .and_then(|x| x.extract().ok());
485 let options = RecordBatchOptions::default().with_row_count(row_count);
486
487 RecordBatch::try_new_with_options(schema, arrays, &options).map_err(to_py_err)
488}
489
490impl FromPyArrow for RecordBatch {
491 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatch"));
492
493 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
494 record_batch_from_pyarrow_bound_impl(value, true)
495 }
496}
497
498impl FromPyArrowUnchecked for RecordBatch {
499 unsafe fn from_pyarrow_bound_unchecked(value: &Bound<PyAny>) -> PyResult<Self> {
500 record_batch_from_pyarrow_bound_impl(value, false)
501 }
502}
503
504impl ToPyArrow for RecordBatch {
505 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatch"));
506
507 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
508 let reader = RecordBatchIterator::new(vec![Ok(self.clone())], self.schema());
510 let reader: Box<dyn RecordBatchReader + Send> = Box::new(reader);
511 let py_reader = reader.into_pyarrow(py)?;
512 py_reader.call_method0(intern!(py, "read_next_batch"))
513 }
514}
515
516impl FromPyArrow for RecordBatchStreamReader {
518 type_hint!(INPUT_TYPE = ARRAY_STREAM_INPUT_TYPE);
519
520 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
521 if let Some(capsule) =
525 call_capsule_method_if_exists(value, intern!(value.py(), "__arrow_c_stream__"))?
526 {
527 let stream_ptr =
528 extract_capsule(&capsule, c"arrow_array_stream", "__arrow_c_stream__")?;
529 let stream = unsafe { FFI_ArrowArrayStream::from_raw(stream_ptr.as_ptr()) };
530
531 let stream_reader = RecordBatchStreamReader::try_new(stream)
532 .map_err(|err| PyValueError::new_err(err.to_string()))?;
533
534 return Ok(stream_reader);
535 }
536
537 validate_class(record_batch_reader_class(value.py())?, value)?;
538
539 let mut stream = FFI_ArrowArrayStream::empty();
541
542 value.call_method1(
546 intern!(value.py(), "_export_to_c"),
547 (&raw mut stream as Py_uintptr_t,),
548 )?;
549
550 RecordBatchStreamReader::try_new(stream)
551 .map_err(|err| PyValueError::new_err(err.to_string()))
552 }
553}
554
555impl IntoPyArrow for Box<dyn RecordBatchReader + Send> {
557 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatchReader"));
558
559 fn into_pyarrow(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
562 let stream = FFI_ArrowArrayStream::new(self);
563 record_batch_reader_class(py)?.call_method1(
564 intern!(py, "_import_from_c"),
565 (&raw const stream as Py_uintptr_t,),
566 )
567 }
568}
569
570impl IntoPyArrow for RecordBatchStreamReader {
572 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatchReader"));
573
574 fn into_pyarrow(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
575 let boxed: Box<dyn RecordBatchReader + Send> = Box::new(self);
576 boxed.into_pyarrow(py)
577 }
578}
579
580#[derive(Clone)]
598pub struct Table {
599 record_batches: Vec<RecordBatch>,
600 schema: SchemaRef,
601}
602
603impl Table {
604 pub fn try_new(
605 record_batches: Vec<RecordBatch>,
606 schema: SchemaRef,
607 ) -> Result<Self, ArrowError> {
608 for record_batch in &record_batches {
609 if schema != record_batch.schema() {
610 return Err(ArrowError::SchemaError(format!(
611 "All record batches must have the same schema. \
612 Expected schema: {:?}, got schema: {:?}",
613 schema,
614 record_batch.schema()
615 )));
616 }
617 }
618 Ok(Self {
619 record_batches,
620 schema,
621 })
622 }
623
624 pub fn record_batches(&self) -> &[RecordBatch] {
625 &self.record_batches
626 }
627
628 pub fn schema(&self) -> SchemaRef {
629 self.schema.clone()
630 }
631
632 pub fn into_inner(self) -> (Vec<RecordBatch>, SchemaRef) {
633 (self.record_batches, self.schema)
634 }
635}
636
637impl TryFrom<Box<dyn RecordBatchReader>> for Table {
638 type Error = ArrowError;
639
640 fn try_from(value: Box<dyn RecordBatchReader>) -> Result<Self, ArrowError> {
641 let schema = value.schema();
642 let batches = value.collect::<Result<Vec<_>, _>>()?;
643 Self::try_new(batches, schema)
644 }
645}
646
647impl FromPyArrow for Table {
649 type_hint!(INPUT_TYPE = ARRAY_STREAM_INPUT_TYPE);
650
651 fn from_pyarrow_bound(ob: &Bound<PyAny>) -> PyResult<Self> {
652 let reader: Box<dyn RecordBatchReader> =
653 Box::new(RecordBatchStreamReader::from_pyarrow_bound(ob)?);
654 Self::try_from(reader).map_err(|err| PyValueError::new_err(err.to_string()))
655 }
656}
657
658impl IntoPyArrow for Table {
660 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "Table"));
661
662 fn into_pyarrow(self, py: Python) -> PyResult<Bound<PyAny>> {
663 let py_batches = PyList::new(py, self.record_batches.into_iter().map(PyArrowType))?;
664 let py_schema = PyArrowType(Arc::unwrap_or_clone(self.schema));
665
666 let kwargs = PyDict::new(py);
667 kwargs.set_item("schema", py_schema)?;
668
669 table_class(py)?.call_method("from_batches", (py_batches,), Some(&kwargs))
670 }
671}
672
673fn array_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
674 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
675 TYPE.import(py, "pyarrow", "Array")
676}
677
678fn record_batch_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
679 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
680 TYPE.import(py, "pyarrow", "RecordBatch")
681}
682
683fn record_batch_reader_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
684 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
685 TYPE.import(py, "pyarrow", "RecordBatchReader")
686}
687
688fn data_type_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
689 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
690 TYPE.import(py, "pyarrow", "DataType")
691}
692
693fn field_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
694 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
695 TYPE.import(py, "pyarrow", "Field")
696}
697
698fn schema_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
699 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
700 TYPE.import(py, "pyarrow", "Schema")
701}
702
703fn table_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
704 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
705 TYPE.import(py, "pyarrow", "Table")
706}
707
708#[derive(Debug)]
714pub struct PyArrowType<T>(pub T);
715
716impl<T: FromPyArrow> FromPyObject<'_, '_> for PyArrowType<T> {
717 type Error = PyErr;
718
719 type_hint!(INPUT_TYPE = <T as FromPyArrow>::INPUT_TYPE);
720
721 fn extract(value: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
722 Ok(Self(T::from_pyarrow_bound(&value)?))
723 }
724}
725
726impl<'py, T: IntoPyArrow> IntoPyObject<'py> for PyArrowType<T> {
727 type Target = PyAny;
728
729 type Output = Bound<'py, Self::Target>;
730
731 type Error = PyErr;
732
733 type_hint!(OUTPUT_TYPE = <T as IntoPyArrow>::OUTPUT_TYPE);
734
735 fn into_pyobject(self, py: Python<'py>) -> PyResult<Self::Output> {
736 self.0.into_pyarrow(py)
737 }
738}
739
740impl<T> From<T> for PyArrowType<T> {
741 fn from(s: T) -> Self {
742 Self(s)
743 }
744}
745
746fn call_capsule_method_if_exists<'py>(
747 object: &Bound<'py, PyAny>,
748 method_name: &Bound<'py, PyString>,
749) -> PyResult<Option<Bound<'py, PyCapsule>>> {
750 let Some(method) = object.getattr_opt(method_name)? else {
751 return Ok(None);
752 };
753 Ok(Some(method.call0()?.extract().map_err(
754 |e: CastError| {
755 wrapping_type_error(
756 object.py(),
757 e.into(),
758 format!("Expected {method_name} to return a capsule."),
759 )
760 },
761 )?))
762}
763
764fn call_arrow_c_array_method_if_exists<'py>(
765 object: &Bound<'py, PyAny>,
766) -> PyResult<Option<(Bound<'py, PyCapsule>, Bound<'py, PyCapsule>)>> {
767 let Some(method) = object.getattr_opt(intern!(object.py(), "__arrow_c_array__"))? else {
768 return Ok(None);
769 };
770 Ok(Some(method.call0()?.extract().map_err(|e| {
771 wrapping_type_error(
772 object.py(),
773 e,
774 "Expected __arrow_c_array__ to return a tuple of (schema, array) capsules.".into(),
775 )
776 })?))
777}
778
779fn extract_capsule<T>(
780 capsule: &Bound<PyCapsule>,
781 capsule_name: &CStr,
782 method_name: &'static str,
783) -> PyResult<NonNull<T>> {
784 Ok(capsule
785 .pointer_checked(Some(capsule_name))
786 .map_err(|e| {
787 wrapping_type_error(
788 capsule.py(),
789 e,
790 format!(
791 "Expected {method_name} to return a {} capsule.",
792 capsule_name.to_str().unwrap(),
793 ),
794 )
795 })?
796 .cast::<T>())
797}
798
799fn wrapping_type_error(py: Python<'_>, error: PyErr, message: String) -> PyErr {
800 let e = PyTypeError::new_err(message);
801 e.set_cause(py, Some(error));
802 e
803}
804
805#[cfg(all(test, feature = "experimental-inspect"))]
806mod introspection_tests {
807 use super::*;
808 use pyo3::{FromPyObject, IntoPyObject};
809
810 fn input_type<T: FromPyArrow>() -> String {
812 <PyArrowType<T> as FromPyObject<'_, '_>>::INPUT_TYPE.to_string()
813 }
814
815 fn output_type<T: IntoPyArrow>() -> String
817 where
818 PyArrowType<T>: for<'py> IntoPyObject<'py>,
819 {
820 <PyArrowType<T> as IntoPyObject<'_>>::OUTPUT_TYPE.to_string()
821 }
822
823 #[test]
824 fn scalar_types_map_to_their_pyarrow_class() {
825 assert_eq!(input_type::<DataType>(), "pyarrow.DataType");
826 assert_eq!(output_type::<DataType>(), "pyarrow.DataType");
827 assert_eq!(input_type::<Field>(), "pyarrow.Field");
828 assert_eq!(output_type::<Field>(), "pyarrow.Field");
829 assert_eq!(input_type::<Schema>(), "pyarrow.Schema");
830 assert_eq!(output_type::<Schema>(), "pyarrow.Schema");
831 assert_eq!(input_type::<RecordBatch>(), "pyarrow.RecordBatch");
832 assert_eq!(output_type::<RecordBatch>(), "pyarrow.RecordBatch");
833 }
834
835 #[test]
837 fn array_data_maps_to_pyarrow_array() {
838 assert_eq!(input_type::<ArrayData>(), "pyarrow.Array");
839 assert_eq!(output_type::<ArrayData>(), "pyarrow.Array");
840 }
841
842 #[test]
845 fn vec_is_iterable_in_and_list_out() {
846 assert_eq!(
847 input_type::<Vec<RecordBatch>>(),
848 "collections.abc.Iterable[pyarrow.RecordBatch]"
849 );
850 assert_eq!(
851 output_type::<Vec<RecordBatch>>(),
852 "builtins.list[pyarrow.RecordBatch]"
853 );
854 }
855
856 #[test]
859 fn readers_and_tables_map_to_their_pyarrow_class() {
860 assert_eq!(
861 input_type::<RecordBatchStreamReader>(),
862 "pyarrow.RecordBatchReader | pyarrow.Table"
863 );
864 assert_eq!(
865 output_type::<RecordBatchStreamReader>(),
866 "pyarrow.RecordBatchReader"
867 );
868 assert_eq!(
869 output_type::<Box<dyn RecordBatchReader + Send>>(),
870 "pyarrow.RecordBatchReader"
871 );
872 assert_eq!(
873 input_type::<Table>(),
874 "pyarrow.RecordBatchReader | pyarrow.Table"
875 );
876 assert_eq!(output_type::<Table>(), "pyarrow.Table");
877 }
878}