1use std::convert::{From, TryFrom};
67use std::ffi::CStr;
68use std::ptr::NonNull;
69use std::sync::Arc;
70
71use arrow_array::ffi;
72use arrow_array::ffi::{FFI_ArrowArray, FFI_ArrowSchema};
73use arrow_array::ffi_stream::{FFI_ArrowArrayStream, RecordBatchStreamReader};
74use arrow_array::{
75 RecordBatch, RecordBatchIterator, RecordBatchOptions, RecordBatchReader, StructArray,
76 make_array,
77};
78use arrow_data::ArrayData;
79use arrow_schema::{ArrowError, DataType, Field, Schema, SchemaRef};
80use pyo3::exceptions::{PyTypeError, PyValueError};
81use pyo3::ffi::Py_uintptr_t;
82use pyo3::prelude::*;
83use pyo3::sync::PyOnceLock;
84use pyo3::types::{PyCapsule, PyDict, PyList, PyString, PyType};
85use pyo3::{CastError, import_exception, intern};
86#[cfg(feature = "experimental-inspect")]
87use pyo3::{
88 inspect::PyStaticExpr, type_hint_identifier, type_hint_subscript, type_hint_union,
89 type_object::PyTypeInfo,
90};
91
92macro_rules! type_hint {
95 ($name:ident = $hint:expr) => {
96 #[cfg(feature = "experimental-inspect")]
97 const $name: PyStaticExpr = $hint;
98 };
99}
100
101import_exception!(pyarrow, ArrowException);
102pub type PyArrowException = ArrowException;
104
105fn to_py_err(err: ArrowError) -> PyErr {
106 PyArrowException::new_err(err.to_string())
107}
108
109#[cfg(feature = "experimental-inspect")]
117const ARRAY_STREAM_INPUT_TYPE: PyStaticExpr = type_hint_union!(
118 type_hint_identifier!("pyarrow", "RecordBatchReader"),
119 type_hint_identifier!("pyarrow", "Table")
120);
121
122pub trait FromPyArrowUnchecked: Sized {
126 unsafe fn from_pyarrow_bound_unchecked(value: &Bound<PyAny>) -> PyResult<Self>;
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 FromPyArrowUnchecked for ArrayData {
325 unsafe fn from_pyarrow_bound_unchecked(value: &Bound<PyAny>) -> PyResult<Self> {
326 if let Some((schema_capsule, array_capsule)) = call_arrow_c_array_method_if_exists(value)? {
330 let schema_ptr =
331 extract_capsule(&schema_capsule, c"arrow_schema", "__arrow_c_array__")?;
332 let array_ptr = extract_capsule(&array_capsule, c"arrow_array", "__arrow_c_array__")?;
333 let array = unsafe { FFI_ArrowArray::from_raw(array_ptr.as_ptr()) };
334 return unsafe { ffi::from_ffi(array, schema_ptr.as_ref()) }.map_err(to_py_err);
335 }
336
337 validate_class(array_class(value.py())?, value)?;
338
339 let mut array = FFI_ArrowArray::empty();
341 let mut schema = FFI_ArrowSchema::empty();
342
343 value.call_method1(
347 intern!(value.py(), "_export_to_c"),
348 (
349 &raw mut array as Py_uintptr_t,
350 &raw mut schema as Py_uintptr_t,
351 ),
352 )?;
353
354 unsafe { ffi::from_ffi(array, &schema) }.map_err(to_py_err)
355 }
356}
357
358impl FromPyArrow for ArrayData {
359 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "Array"));
360
361 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
362 let data = unsafe { ArrayData::from_pyarrow_bound_unchecked(value)? };
363 data.validate_full().map_err(to_py_err)?;
364 Ok(data)
365 }
366}
367
368impl ToPyArrow for ArrayData {
369 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "Array"));
370
371 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
372 let array = FFI_ArrowArray::new(self);
373 let schema = FFI_ArrowSchema::try_from(self.data_type()).map_err(to_py_err)?;
374 array_class(py)?.call_method1(
375 intern!(py, "_import_from_c"),
376 (
377 &raw const array as Py_uintptr_t,
378 &raw const schema as Py_uintptr_t,
379 ),
380 )
381 }
382}
383
384impl<T: FromPyArrow> FromPyArrow for Vec<T> {
385 type_hint!(
386 INPUT_TYPE = type_hint_subscript!(
387 type_hint_identifier!("collections.abc", "Iterable"),
388 <T as FromPyArrow>::INPUT_TYPE
389 )
390 );
391
392 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
393 let mut v = Vec::with_capacity(value.len().unwrap_or(0));
394 for item in value.try_iter()? {
395 v.push(T::from_pyarrow_bound(&item?)?);
396 }
397 Ok(v)
398 }
399}
400
401impl<T: ToPyArrow> ToPyArrow for Vec<T> {
402 type_hint!(
403 OUTPUT_TYPE = type_hint_subscript!(PyList::TYPE_HINT, <T as ToPyArrow>::OUTPUT_TYPE)
404 );
405
406 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
407 self.iter()
408 .map(|v| v.to_pyarrow(py))
409 .collect::<PyResult<Vec<_>>>()?
410 .into_pyobject(py)
411 }
412}
413
414fn record_batch_from_pyarrow_bound_impl(
415 value: &Bound<PyAny>,
416 validate: bool,
417) -> PyResult<RecordBatch> {
418 if let Some((schema_capsule, array_capsule)) = call_arrow_c_array_method_if_exists(value)? {
422 let schema_ptr = extract_capsule(&schema_capsule, c"arrow_schema", "__arrow_c_array__")?;
423 let array_ptr = extract_capsule(&array_capsule, c"arrow_array", "__arrow_c_array__")?;
424 let ffi_array = unsafe { FFI_ArrowArray::from_raw(array_ptr.as_ptr()) };
425 let array_data =
426 unsafe { ffi::from_ffi(ffi_array, schema_ptr.as_ref()) }.map_err(to_py_err)?;
427 if validate {
428 array_data.validate_full().map_err(to_py_err)?;
429 }
430 if !matches!(array_data.data_type(), DataType::Struct(_)) {
431 return Err(PyTypeError::new_err(format!(
432 "Expected Struct type from __arrow_c_array__, found {}.",
433 array_data.data_type()
434 )));
435 }
436 let options = RecordBatchOptions::default().with_row_count(Some(array_data.len()));
437 let array = StructArray::from(array_data);
438 let schema = unsafe { Arc::new(Schema::try_from(schema_ptr.as_ref()).map_err(to_py_err)?) };
441 let (_fields, columns, nulls) = array.into_parts();
442 if nulls.map(|n| n.null_count()).unwrap_or_default() != 0 {
443 return Err(PyValueError::new_err(
444 "Cannot convert nullable StructArray to RecordBatch, see StructArray documentation",
445 ));
446 }
447 return RecordBatch::try_new_with_options(schema, columns, &options).map_err(to_py_err);
448 }
449
450 validate_class(record_batch_class(value.py())?, value)?;
451 let schema = value.getattr("schema")?;
453 let schema = Arc::new(Schema::from_pyarrow_bound(&schema)?);
454
455 let arrays = value.getattr("columns")?;
456 let arrays = arrays
457 .cast::<PyList>()?
458 .iter()
459 .map(|a| {
460 let data = unsafe { ArrayData::from_pyarrow_bound_unchecked(&a)? };
461 if validate {
462 data.validate_full().map_err(to_py_err)?;
463 }
464 Ok(make_array(data))
465 })
466 .collect::<PyResult<_>>()?;
467
468 let row_count = value
469 .getattr("num_rows")
470 .ok()
471 .and_then(|x| x.extract().ok());
472 let options = RecordBatchOptions::default().with_row_count(row_count);
473
474 RecordBatch::try_new_with_options(schema, arrays, &options).map_err(to_py_err)
475}
476
477impl FromPyArrow for RecordBatch {
478 type_hint!(INPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatch"));
479
480 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
481 record_batch_from_pyarrow_bound_impl(value, true)
482 }
483}
484
485impl FromPyArrowUnchecked for RecordBatch {
486 unsafe fn from_pyarrow_bound_unchecked(value: &Bound<PyAny>) -> PyResult<Self> {
487 record_batch_from_pyarrow_bound_impl(value, false)
488 }
489}
490
491impl ToPyArrow for RecordBatch {
492 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatch"));
493
494 fn to_pyarrow<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
495 let reader = RecordBatchIterator::new(vec![Ok(self.clone())], self.schema());
497 let reader: Box<dyn RecordBatchReader + Send> = Box::new(reader);
498 let py_reader = reader.into_pyarrow(py)?;
499 py_reader.call_method0(intern!(py, "read_next_batch"))
500 }
501}
502
503impl FromPyArrow for RecordBatchStreamReader {
505 type_hint!(INPUT_TYPE = ARRAY_STREAM_INPUT_TYPE);
506
507 fn from_pyarrow_bound(value: &Bound<PyAny>) -> PyResult<Self> {
508 if let Some(capsule) =
512 call_capsule_method_if_exists(value, intern!(value.py(), "__arrow_c_stream__"))?
513 {
514 let stream_ptr =
515 extract_capsule(&capsule, c"arrow_array_stream", "__arrow_c_stream__")?;
516 let stream = unsafe { FFI_ArrowArrayStream::from_raw(stream_ptr.as_ptr()) };
517
518 let stream_reader = RecordBatchStreamReader::try_new(stream)
519 .map_err(|err| PyValueError::new_err(err.to_string()))?;
520
521 return Ok(stream_reader);
522 }
523
524 validate_class(record_batch_reader_class(value.py())?, value)?;
525
526 let mut stream = FFI_ArrowArrayStream::empty();
528
529 value.call_method1(
533 intern!(value.py(), "_export_to_c"),
534 (&raw mut stream as Py_uintptr_t,),
535 )?;
536
537 RecordBatchStreamReader::try_new(stream)
538 .map_err(|err| PyValueError::new_err(err.to_string()))
539 }
540}
541
542impl IntoPyArrow for Box<dyn RecordBatchReader + Send> {
544 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatchReader"));
545
546 fn into_pyarrow(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
549 let stream = FFI_ArrowArrayStream::new(self);
550 record_batch_reader_class(py)?.call_method1(
551 intern!(py, "_import_from_c"),
552 (&raw const stream as Py_uintptr_t,),
553 )
554 }
555}
556
557impl IntoPyArrow for RecordBatchStreamReader {
559 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "RecordBatchReader"));
560
561 fn into_pyarrow(self, py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
562 let boxed: Box<dyn RecordBatchReader + Send> = Box::new(self);
563 boxed.into_pyarrow(py)
564 }
565}
566
567#[derive(Clone)]
585pub struct Table {
586 record_batches: Vec<RecordBatch>,
587 schema: SchemaRef,
588}
589
590impl Table {
591 pub fn try_new(
592 record_batches: Vec<RecordBatch>,
593 schema: SchemaRef,
594 ) -> Result<Self, ArrowError> {
595 for record_batch in &record_batches {
596 if schema != record_batch.schema() {
597 return Err(ArrowError::SchemaError(format!(
598 "All record batches must have the same schema. \
599 Expected schema: {:?}, got schema: {:?}",
600 schema,
601 record_batch.schema()
602 )));
603 }
604 }
605 Ok(Self {
606 record_batches,
607 schema,
608 })
609 }
610
611 pub fn record_batches(&self) -> &[RecordBatch] {
612 &self.record_batches
613 }
614
615 pub fn schema(&self) -> SchemaRef {
616 self.schema.clone()
617 }
618
619 pub fn into_inner(self) -> (Vec<RecordBatch>, SchemaRef) {
620 (self.record_batches, self.schema)
621 }
622}
623
624impl TryFrom<Box<dyn RecordBatchReader>> for Table {
625 type Error = ArrowError;
626
627 fn try_from(value: Box<dyn RecordBatchReader>) -> Result<Self, ArrowError> {
628 let schema = value.schema();
629 let batches = value.collect::<Result<Vec<_>, _>>()?;
630 Self::try_new(batches, schema)
631 }
632}
633
634impl FromPyArrow for Table {
636 type_hint!(INPUT_TYPE = ARRAY_STREAM_INPUT_TYPE);
637
638 fn from_pyarrow_bound(ob: &Bound<PyAny>) -> PyResult<Self> {
639 let reader: Box<dyn RecordBatchReader> =
640 Box::new(RecordBatchStreamReader::from_pyarrow_bound(ob)?);
641 Self::try_from(reader).map_err(|err| PyValueError::new_err(err.to_string()))
642 }
643}
644
645impl IntoPyArrow for Table {
647 type_hint!(OUTPUT_TYPE = type_hint_identifier!("pyarrow", "Table"));
648
649 fn into_pyarrow(self, py: Python) -> PyResult<Bound<PyAny>> {
650 let py_batches = PyList::new(py, self.record_batches.into_iter().map(PyArrowType))?;
651 let py_schema = PyArrowType(Arc::unwrap_or_clone(self.schema));
652
653 let kwargs = PyDict::new(py);
654 kwargs.set_item("schema", py_schema)?;
655
656 table_class(py)?.call_method("from_batches", (py_batches,), Some(&kwargs))
657 }
658}
659
660fn array_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
661 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
662 TYPE.import(py, "pyarrow", "Array")
663}
664
665fn record_batch_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
666 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
667 TYPE.import(py, "pyarrow", "RecordBatch")
668}
669
670fn record_batch_reader_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
671 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
672 TYPE.import(py, "pyarrow", "RecordBatchReader")
673}
674
675fn data_type_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
676 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
677 TYPE.import(py, "pyarrow", "DataType")
678}
679
680fn field_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
681 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
682 TYPE.import(py, "pyarrow", "Field")
683}
684
685fn schema_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
686 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
687 TYPE.import(py, "pyarrow", "Schema")
688}
689
690fn table_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
691 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
692 TYPE.import(py, "pyarrow", "Table")
693}
694
695#[derive(Debug)]
701pub struct PyArrowType<T>(pub T);
702
703impl<T: FromPyArrow> FromPyObject<'_, '_> for PyArrowType<T> {
704 type Error = PyErr;
705
706 type_hint!(INPUT_TYPE = <T as FromPyArrow>::INPUT_TYPE);
707
708 fn extract(value: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
709 Ok(Self(T::from_pyarrow_bound(&value)?))
710 }
711}
712
713impl<'py, T: IntoPyArrow> IntoPyObject<'py> for PyArrowType<T> {
714 type Target = PyAny;
715
716 type Output = Bound<'py, Self::Target>;
717
718 type Error = PyErr;
719
720 type_hint!(OUTPUT_TYPE = <T as IntoPyArrow>::OUTPUT_TYPE);
721
722 fn into_pyobject(self, py: Python<'py>) -> PyResult<Self::Output> {
723 self.0.into_pyarrow(py)
724 }
725}
726
727impl<T> From<T> for PyArrowType<T> {
728 fn from(s: T) -> Self {
729 Self(s)
730 }
731}
732
733fn call_capsule_method_if_exists<'py>(
734 object: &Bound<'py, PyAny>,
735 method_name: &Bound<'py, PyString>,
736) -> PyResult<Option<Bound<'py, PyCapsule>>> {
737 let Some(method) = object.getattr_opt(method_name)? else {
738 return Ok(None);
739 };
740 Ok(Some(method.call0()?.extract().map_err(
741 |e: CastError| {
742 wrapping_type_error(
743 object.py(),
744 e.into(),
745 format!("Expected {method_name} to return a capsule."),
746 )
747 },
748 )?))
749}
750
751fn call_arrow_c_array_method_if_exists<'py>(
752 object: &Bound<'py, PyAny>,
753) -> PyResult<Option<(Bound<'py, PyCapsule>, Bound<'py, PyCapsule>)>> {
754 let Some(method) = object.getattr_opt(intern!(object.py(), "__arrow_c_array__"))? else {
755 return Ok(None);
756 };
757 Ok(Some(method.call0()?.extract().map_err(|e| {
758 wrapping_type_error(
759 object.py(),
760 e,
761 "Expected __arrow_c_array__ to return a tuple of (schema, array) capsules.".into(),
762 )
763 })?))
764}
765
766fn extract_capsule<T>(
767 capsule: &Bound<PyCapsule>,
768 capsule_name: &CStr,
769 method_name: &'static str,
770) -> PyResult<NonNull<T>> {
771 Ok(capsule
772 .pointer_checked(Some(capsule_name))
773 .map_err(|e| {
774 wrapping_type_error(
775 capsule.py(),
776 e,
777 format!(
778 "Expected {method_name} to return a {} capsule.",
779 capsule_name.to_str().unwrap(),
780 ),
781 )
782 })?
783 .cast::<T>())
784}
785
786fn wrapping_type_error(py: Python<'_>, error: PyErr, message: String) -> PyErr {
787 let e = PyTypeError::new_err(message);
788 e.set_cause(py, Some(error));
789 e
790}
791
792#[cfg(all(test, feature = "experimental-inspect"))]
793mod introspection_tests {
794 use super::*;
795 use pyo3::{FromPyObject, IntoPyObject};
796
797 fn input_type<T: FromPyArrow>() -> String {
799 <PyArrowType<T> as FromPyObject<'_, '_>>::INPUT_TYPE.to_string()
800 }
801
802 fn output_type<T: IntoPyArrow>() -> String
804 where
805 PyArrowType<T>: for<'py> IntoPyObject<'py>,
806 {
807 <PyArrowType<T> as IntoPyObject<'_>>::OUTPUT_TYPE.to_string()
808 }
809
810 #[test]
811 fn scalar_types_map_to_their_pyarrow_class() {
812 assert_eq!(input_type::<DataType>(), "pyarrow.DataType");
813 assert_eq!(output_type::<DataType>(), "pyarrow.DataType");
814 assert_eq!(input_type::<Field>(), "pyarrow.Field");
815 assert_eq!(output_type::<Field>(), "pyarrow.Field");
816 assert_eq!(input_type::<Schema>(), "pyarrow.Schema");
817 assert_eq!(output_type::<Schema>(), "pyarrow.Schema");
818 assert_eq!(input_type::<RecordBatch>(), "pyarrow.RecordBatch");
819 assert_eq!(output_type::<RecordBatch>(), "pyarrow.RecordBatch");
820 }
821
822 #[test]
824 fn array_data_maps_to_pyarrow_array() {
825 assert_eq!(input_type::<ArrayData>(), "pyarrow.Array");
826 assert_eq!(output_type::<ArrayData>(), "pyarrow.Array");
827 }
828
829 #[test]
832 fn vec_is_iterable_in_and_list_out() {
833 assert_eq!(
834 input_type::<Vec<RecordBatch>>(),
835 "collections.abc.Iterable[pyarrow.RecordBatch]"
836 );
837 assert_eq!(
838 output_type::<Vec<RecordBatch>>(),
839 "builtins.list[pyarrow.RecordBatch]"
840 );
841 }
842
843 #[test]
846 fn readers_and_tables_map_to_their_pyarrow_class() {
847 assert_eq!(
848 input_type::<RecordBatchStreamReader>(),
849 "pyarrow.RecordBatchReader | pyarrow.Table"
850 );
851 assert_eq!(
852 output_type::<RecordBatchStreamReader>(),
853 "pyarrow.RecordBatchReader"
854 );
855 assert_eq!(
856 output_type::<Box<dyn RecordBatchReader + Send>>(),
857 "pyarrow.RecordBatchReader"
858 );
859 assert_eq!(
860 input_type::<Table>(),
861 "pyarrow.RecordBatchReader | pyarrow.Table"
862 );
863 assert_eq!(output_type::<Table>(), "pyarrow.Table");
864 }
865}