Skip to main content

pyo3_arrow/
datatypes.rs

1use std::fmt::Display;
2use std::hash::{DefaultHasher, Hash, Hasher};
3use std::sync::Arc;
4
5use arrow_schema::{DataType, Field, IntervalUnit, TimeUnit};
6use pyo3::exceptions::{PyTypeError, PyValueError};
7use pyo3::intern;
8use pyo3::prelude::*;
9use pyo3::types::{PyCapsule, PyTuple, PyType};
10
11use crate::error::PyArrowResult;
12use crate::export::{Arro3DataType, Arro3Field};
13use crate::ffi::from_python::utils::import_schema_pycapsule;
14use crate::ffi::to_python::nanoarrow::to_nanoarrow_schema;
15use crate::ffi::to_schema_pycapsule;
16use crate::PyField;
17
18struct PyTimeUnit(arrow_schema::TimeUnit);
19
20impl<'a> FromPyObject<'_, 'a> for PyTimeUnit {
21    type Error = PyErr;
22
23    fn extract(obj: Borrowed<'_, 'a, PyAny>) -> Result<Self, Self::Error> {
24        let s: String = obj.extract()?;
25        match s.to_lowercase().as_str() {
26            "s" => Ok(Self(TimeUnit::Second)),
27            "ms" => Ok(Self(TimeUnit::Millisecond)),
28            "us" => Ok(Self(TimeUnit::Microsecond)),
29            "ns" => Ok(Self(TimeUnit::Nanosecond)),
30            _ => Err(PyValueError::new_err("Unexpected time unit")),
31        }
32    }
33}
34
35/// A Python-facing wrapper around [DataType].
36#[derive(PartialEq, Eq, Debug)]
37#[pyclass(module = "arro3.core._core", name = "DataType", subclass, frozen)]
38pub struct PyDataType(DataType);
39
40impl PyDataType {
41    /// Construct a new PyDataType around a [DataType].
42    pub fn new(data_type: DataType) -> Self {
43        Self(data_type)
44    }
45
46    /// Create from a raw Arrow C Schema capsule
47    pub fn from_arrow_pycapsule(capsule: &Bound<PyCapsule>) -> PyResult<Self> {
48        let schema_ptr = import_schema_pycapsule(capsule)?;
49        let data_type =
50            DataType::try_from(schema_ptr).map_err(|err| PyTypeError::new_err(err.to_string()))?;
51        Ok(Self::new(data_type))
52    }
53
54    /// Consume this and return its inner part.
55    pub fn into_inner(self) -> DataType {
56        self.0
57    }
58
59    /// Export this to a Python `arro3.core.DataType`.
60    pub fn to_arro3<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
61        let arro3_mod = py.import(intern!(py, "arro3.core"))?;
62        arro3_mod.getattr(intern!(py, "DataType"))?.call_method1(
63            intern!(py, "from_arrow_pycapsule"),
64            PyTuple::new(py, vec![self.__arrow_c_schema__(py)?])?,
65        )
66    }
67
68    /// Export this to a Python `arro3.core.DataType`.
69    pub fn into_arro3(self, py: Python) -> PyResult<Bound<PyAny>> {
70        let arro3_mod = py.import(intern!(py, "arro3.core"))?;
71        let capsule = to_schema_pycapsule(py, &self.0)?;
72        arro3_mod.getattr(intern!(py, "DataType"))?.call_method1(
73            intern!(py, "from_arrow_pycapsule"),
74            PyTuple::new(py, vec![capsule])?,
75        )
76    }
77
78    /// Export this to a Python `nanoarrow.Schema`.
79    pub fn to_nanoarrow<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
80        to_nanoarrow_schema(py, &self.__arrow_c_schema__(py)?)
81    }
82
83    /// Export to a pyarrow.DataType
84    ///
85    /// Requires pyarrow >=14
86    pub fn into_pyarrow(self, py: Python) -> PyResult<Bound<PyAny>> {
87        let pyarrow_mod = py.import(intern!(py, "pyarrow"))?;
88        let pyarrow_field = pyarrow_mod
89            .getattr(intern!(py, "field"))?
90            .call1(PyTuple::new(py, vec![self.into_pyobject(py)?])?)?;
91        pyarrow_field.getattr(intern!(py, "type"))
92    }
93}
94
95impl From<PyDataType> for DataType {
96    fn from(value: PyDataType) -> Self {
97        value.0
98    }
99}
100
101impl From<DataType> for PyDataType {
102    fn from(value: DataType) -> Self {
103        Self(value)
104    }
105}
106
107impl From<&DataType> for PyDataType {
108    fn from(value: &DataType) -> Self {
109        Self(value.clone())
110    }
111}
112
113impl AsRef<DataType> for PyDataType {
114    fn as_ref(&self) -> &DataType {
115        &self.0
116    }
117}
118
119impl Display for PyDataType {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        write!(f, "arro3.core.DataType<")?;
122        self.0.fmt(f)?;
123        writeln!(f, ">")?;
124        Ok(())
125    }
126}
127
128#[allow(non_snake_case)]
129#[pymethods]
130impl PyDataType {
131    pub(crate) fn __arrow_c_schema__<'py>(
132        &'py self,
133        py: Python<'py>,
134    ) -> PyArrowResult<Bound<'py, PyCapsule>> {
135        // We manually construct a field to set `nullable: true` on any exported DataType.
136        // By default, arrow-rs sets `nullable: False` in the `TryInto<FFI_ArrowSchema>` impl.
137        let field = Field::new("", self.0.clone(), true);
138        to_schema_pycapsule(py, &field)
139    }
140
141    fn __eq__(&self, other: PyDataType) -> bool {
142        self.equals(other, false)
143    }
144
145    fn __hash__(&self) -> u64 {
146        let mut hasher = DefaultHasher::new();
147        self.0.hash(&mut hasher);
148        hasher.finish()
149    }
150
151    fn __repr__(&self) -> String {
152        self.to_string()
153    }
154
155    #[classmethod]
156    fn from_arrow(_cls: &Bound<PyType>, input: Self) -> Self {
157        input
158    }
159
160    #[classmethod]
161    #[pyo3(name = "from_arrow_pycapsule")]
162    fn from_arrow_pycapsule_py(_cls: &Bound<PyType>, capsule: &Bound<PyCapsule>) -> PyResult<Self> {
163        Self::from_arrow_pycapsule(capsule)
164    }
165
166    #[getter]
167    fn bit_width(&self) -> Option<usize> {
168        self.0.primitive_width().map(|width| width * 8)
169    }
170
171    #[pyo3(signature=(other, *, check_metadata=false))]
172    fn equals(&self, other: PyDataType, check_metadata: bool) -> bool {
173        let other = other.into_inner();
174        if check_metadata {
175            self.0 == other
176        } else {
177            self.0.equals_datatype(&other)
178        }
179    }
180
181    #[getter]
182    fn list_size(&self) -> Option<i32> {
183        match &self.0 {
184            DataType::FixedSizeList(_, list_size) => Some(*list_size),
185            _ => None,
186        }
187    }
188
189    #[getter]
190    fn num_fields(&self) -> usize {
191        match &self.0 {
192            DataType::Null
193            | DataType::Boolean
194            | DataType::Int8
195            | DataType::Int16
196            | DataType::Int32
197            | DataType::Int64
198            | DataType::UInt8
199            | DataType::UInt16
200            | DataType::UInt32
201            | DataType::UInt64
202            | DataType::Float16
203            | DataType::Float32
204            | DataType::Float64
205            | DataType::Timestamp(_, _)
206            | DataType::Date32
207            | DataType::Date64
208            | DataType::Time32(_)
209            | DataType::Time64(_)
210            | DataType::Duration(_)
211            | DataType::Interval(_)
212            | DataType::Binary
213            | DataType::FixedSizeBinary(_)
214            | DataType::LargeBinary
215            | DataType::BinaryView
216            | DataType::Utf8
217            | DataType::LargeUtf8
218            | DataType::Utf8View
219            | DataType::Decimal32(_, _)
220            | DataType::Decimal64(_, _)
221            | DataType::Decimal128(_, _)
222            | DataType::Decimal256(_, _) => 0,
223            DataType::List(_)
224            | DataType::ListView(_)
225            | DataType::FixedSizeList(_, _)
226            | DataType::LargeList(_)
227            | DataType::LargeListView(_) => 1,
228            DataType::Struct(fields) => fields.len(),
229            DataType::Union(fields, _) => fields.len(),
230            // Is this accurate?
231            DataType::Dictionary(_, _) | DataType::Map(_, _) | DataType::RunEndEncoded(_, _) => 2,
232        }
233    }
234
235    #[getter]
236    fn time_unit(&self) -> Option<&str> {
237        match &self.0 {
238            DataType::Time32(unit)
239            | DataType::Time64(unit)
240            | DataType::Timestamp(unit, _)
241            | DataType::Duration(unit) => match unit {
242                TimeUnit::Second => Some("s"),
243                TimeUnit::Millisecond => Some("ms"),
244                TimeUnit::Microsecond => Some("us"),
245                TimeUnit::Nanosecond => Some("ns"),
246            },
247            _ => None,
248        }
249    }
250
251    #[getter]
252    fn tz(&self) -> Option<&str> {
253        match &self.0 {
254            DataType::Timestamp(_, tz) => tz.as_deref(),
255            _ => None,
256        }
257    }
258
259    #[getter]
260    fn value_type(&self) -> Option<Arro3DataType> {
261        match &self.0 {
262            DataType::FixedSizeList(value_field, _)
263            | DataType::List(value_field)
264            | DataType::LargeList(value_field)
265            | DataType::ListView(value_field)
266            | DataType::LargeListView(value_field)
267            | DataType::RunEndEncoded(_, value_field) => {
268                Some(PyDataType::new(value_field.data_type().clone()).into())
269            }
270            DataType::Dictionary(_key_type, value_type) => {
271                Some(PyDataType::new(*value_type.clone()).into())
272            }
273            _ => None,
274        }
275    }
276
277    #[getter]
278    fn value_field(&self) -> Option<Arro3Field> {
279        match &self.0 {
280            DataType::FixedSizeList(value_field, _)
281            | DataType::List(value_field)
282            | DataType::LargeList(value_field)
283            | DataType::ListView(value_field)
284            | DataType::LargeListView(value_field) => {
285                Some(PyField::new(value_field.clone()).into())
286            }
287            _ => None,
288        }
289    }
290
291    #[getter]
292    fn fields(&self) -> Option<Vec<Arro3Field>> {
293        match &self.0 {
294            DataType::Struct(fields) => Some(
295                fields
296                    .into_iter()
297                    .map(|f| PyField::new(f.clone()).into())
298                    .collect::<Vec<_>>(),
299            ),
300            _ => None,
301        }
302    }
303
304    ///////////////////// Constructors
305
306    #[classmethod]
307    fn null(_: &Bound<PyType>) -> Self {
308        Self(DataType::Null)
309    }
310
311    #[classmethod]
312    fn bool(_: &Bound<PyType>) -> Self {
313        Self(DataType::Boolean)
314    }
315
316    #[classmethod]
317    fn int8(_: &Bound<PyType>) -> Self {
318        Self(DataType::Int8)
319    }
320
321    #[classmethod]
322    fn int16(_: &Bound<PyType>) -> Self {
323        Self(DataType::Int16)
324    }
325
326    #[classmethod]
327    fn int32(_: &Bound<PyType>) -> Self {
328        Self(DataType::Int32)
329    }
330
331    #[classmethod]
332    fn int64(_: &Bound<PyType>) -> Self {
333        Self(DataType::Int64)
334    }
335
336    #[classmethod]
337    fn uint8(_: &Bound<PyType>) -> Self {
338        Self(DataType::UInt8)
339    }
340
341    #[classmethod]
342    fn uint16(_: &Bound<PyType>) -> Self {
343        Self(DataType::UInt16)
344    }
345
346    #[classmethod]
347    fn uint32(_: &Bound<PyType>) -> Self {
348        Self(DataType::UInt32)
349    }
350
351    #[classmethod]
352    fn uint64(_: &Bound<PyType>) -> Self {
353        Self(DataType::UInt64)
354    }
355
356    #[classmethod]
357    fn float16(_: &Bound<PyType>) -> Self {
358        Self(DataType::Float16)
359    }
360
361    #[classmethod]
362    fn float32(_: &Bound<PyType>) -> Self {
363        Self(DataType::Float32)
364    }
365
366    #[classmethod]
367    fn float64(_: &Bound<PyType>) -> Self {
368        Self(DataType::Float64)
369    }
370
371    #[classmethod]
372    fn time32(_: &Bound<PyType>, unit: PyTimeUnit) -> PyArrowResult<Self> {
373        if unit.0 == TimeUnit::Microsecond || unit.0 == TimeUnit::Nanosecond {
374            return Err(PyValueError::new_err("Unexpected timeunit for time32").into());
375        }
376
377        Ok(Self(DataType::Time32(unit.0)))
378    }
379
380    #[classmethod]
381    fn time64(_: &Bound<PyType>, unit: PyTimeUnit) -> PyArrowResult<Self> {
382        if unit.0 == TimeUnit::Second || unit.0 == TimeUnit::Millisecond {
383            return Err(PyValueError::new_err("Unexpected timeunit for time64").into());
384        }
385
386        Ok(Self(DataType::Time64(unit.0)))
387    }
388
389    #[classmethod]
390    #[pyo3(signature = (unit, *, tz=None))]
391    fn timestamp(_: &Bound<PyType>, unit: PyTimeUnit, tz: Option<String>) -> Self {
392        Self(DataType::Timestamp(unit.0, tz.map(|s| s.into())))
393    }
394
395    #[classmethod]
396    fn date32(_: &Bound<PyType>) -> Self {
397        Self(DataType::Date32)
398    }
399
400    #[classmethod]
401    fn date64(_: &Bound<PyType>) -> Self {
402        Self(DataType::Date64)
403    }
404
405    #[classmethod]
406    fn duration(_: &Bound<PyType>, unit: PyTimeUnit) -> Self {
407        Self(DataType::Duration(unit.0))
408    }
409
410    #[classmethod]
411    fn month_day_nano_interval(_: &Bound<PyType>) -> Self {
412        Self(DataType::Interval(IntervalUnit::MonthDayNano))
413    }
414
415    #[classmethod]
416    #[pyo3(signature = (length=None))]
417    fn binary(_: &Bound<PyType>, length: Option<i32>) -> Self {
418        if let Some(length) = length {
419            Self(DataType::FixedSizeBinary(length))
420        } else {
421            Self(DataType::Binary)
422        }
423    }
424
425    #[classmethod]
426    fn string(_: &Bound<PyType>) -> Self {
427        Self(DataType::Utf8)
428    }
429
430    #[classmethod]
431    fn utf8(_: &Bound<PyType>) -> Self {
432        Self(DataType::Utf8)
433    }
434
435    #[classmethod]
436    fn large_binary(_: &Bound<PyType>) -> Self {
437        Self(DataType::LargeBinary)
438    }
439
440    #[classmethod]
441    fn large_string(_: &Bound<PyType>) -> Self {
442        Self(DataType::LargeUtf8)
443    }
444
445    #[classmethod]
446    fn large_utf8(_: &Bound<PyType>) -> Self {
447        Self(DataType::LargeUtf8)
448    }
449
450    #[classmethod]
451    fn binary_view(_: &Bound<PyType>) -> Self {
452        Self(DataType::BinaryView)
453    }
454
455    #[classmethod]
456    fn string_view(_: &Bound<PyType>) -> Self {
457        Self(DataType::Utf8View)
458    }
459
460    #[classmethod]
461    fn decimal128(_: &Bound<PyType>, precision: u8, scale: i8) -> Self {
462        Self(DataType::Decimal128(precision, scale))
463    }
464
465    #[classmethod]
466    fn decimal256(_: &Bound<PyType>, precision: u8, scale: i8) -> Self {
467        Self(DataType::Decimal256(precision, scale))
468    }
469
470    #[classmethod]
471    #[pyo3(signature = (value_type, list_size=None))]
472    fn list(_: &Bound<PyType>, value_type: PyField, list_size: Option<i32>) -> Self {
473        if let Some(list_size) = list_size {
474            Self(DataType::FixedSizeList(value_type.into(), list_size))
475        } else {
476            Self(DataType::List(value_type.into()))
477        }
478    }
479
480    #[classmethod]
481    fn large_list(_: &Bound<PyType>, value_type: PyField) -> Self {
482        Self(DataType::LargeList(value_type.into()))
483    }
484
485    #[classmethod]
486    fn list_view(_: &Bound<PyType>, value_type: PyField) -> Self {
487        Self(DataType::ListView(value_type.into()))
488    }
489
490    #[classmethod]
491    fn large_list_view(_: &Bound<PyType>, value_type: PyField) -> Self {
492        Self(DataType::LargeListView(value_type.into()))
493    }
494
495    #[classmethod]
496    fn map(_: &Bound<PyType>, key_type: PyField, item_type: PyField, keys_sorted: bool) -> Self {
497        // Note: copied from source of `Field::new_map`
498        // https://github.com/apache/arrow-rs/blob/bf9ce475df82d362631099d491d3454d64d50217/arrow-schema/src/field.rs#L251-L258
499        let data_type = DataType::Map(
500            Arc::new(Field::new(
501                "entries",
502                DataType::Struct(vec![key_type.into_inner(), item_type.into_inner()].into()),
503                false, // The inner map field is always non-nullable (arrow-rs #1697),
504            )),
505            keys_sorted,
506        );
507        Self(data_type)
508    }
509
510    #[classmethod]
511    fn r#struct(_: &Bound<PyType>, fields: Vec<PyField>) -> Self {
512        Self(DataType::Struct(
513            fields.into_iter().map(|field| field.into_inner()).collect(),
514        ))
515    }
516
517    #[classmethod]
518    fn dictionary(_: &Bound<PyType>, index_type: PyDataType, value_type: PyDataType) -> Self {
519        Self(DataType::Dictionary(
520            Box::new(index_type.into_inner()),
521            Box::new(value_type.into_inner()),
522        ))
523    }
524
525    #[classmethod]
526    fn run_end_encoded(_: &Bound<PyType>, run_end_type: PyField, value_type: PyField) -> Self {
527        Self(DataType::RunEndEncoded(
528            run_end_type.into_inner(),
529            value_type.into_inner(),
530        ))
531    }
532
533    ///////////////////// Type checking
534
535    #[staticmethod]
536    fn is_boolean(t: PyDataType) -> bool {
537        t.0 == DataType::Boolean
538    }
539
540    #[staticmethod]
541    fn is_integer(t: PyDataType) -> bool {
542        t.0.is_integer()
543    }
544
545    #[staticmethod]
546    fn is_signed_integer(t: PyDataType) -> bool {
547        t.0.is_signed_integer()
548    }
549
550    #[staticmethod]
551    fn is_unsigned_integer(t: PyDataType) -> bool {
552        t.0.is_unsigned_integer()
553    }
554
555    #[staticmethod]
556    fn is_int8(t: PyDataType) -> bool {
557        t.0 == DataType::Int8
558    }
559    #[staticmethod]
560    fn is_int16(t: PyDataType) -> bool {
561        t.0 == DataType::Int16
562    }
563    #[staticmethod]
564    fn is_int32(t: PyDataType) -> bool {
565        t.0 == DataType::Int32
566    }
567    #[staticmethod]
568    fn is_int64(t: PyDataType) -> bool {
569        t.0 == DataType::Int64
570    }
571    #[staticmethod]
572    fn is_uint8(t: PyDataType) -> bool {
573        t.0 == DataType::UInt8
574    }
575    #[staticmethod]
576    fn is_uint16(t: PyDataType) -> bool {
577        t.0 == DataType::UInt16
578    }
579    #[staticmethod]
580    fn is_uint32(t: PyDataType) -> bool {
581        t.0 == DataType::UInt32
582    }
583    #[staticmethod]
584    fn is_uint64(t: PyDataType) -> bool {
585        t.0 == DataType::UInt64
586    }
587    #[staticmethod]
588    fn is_floating(t: PyDataType) -> bool {
589        t.0.is_floating()
590    }
591    #[staticmethod]
592    fn is_float16(t: PyDataType) -> bool {
593        t.0 == DataType::Float16
594    }
595    #[staticmethod]
596    fn is_float32(t: PyDataType) -> bool {
597        t.0 == DataType::Float32
598    }
599    #[staticmethod]
600    fn is_float64(t: PyDataType) -> bool {
601        t.0 == DataType::Float64
602    }
603    #[staticmethod]
604    fn is_decimal(t: PyDataType) -> bool {
605        matches!(t.0, DataType::Decimal128(_, _) | DataType::Decimal256(_, _))
606    }
607    #[staticmethod]
608    fn is_decimal128(t: PyDataType) -> bool {
609        matches!(t.0, DataType::Decimal128(_, _))
610    }
611    #[staticmethod]
612    fn is_decimal256(t: PyDataType) -> bool {
613        matches!(t.0, DataType::Decimal256(_, _))
614    }
615
616    #[staticmethod]
617    fn is_list(t: PyDataType) -> bool {
618        matches!(t.0, DataType::List(_))
619    }
620    #[staticmethod]
621    fn is_large_list(t: PyDataType) -> bool {
622        matches!(t.0, DataType::LargeList(_))
623    }
624    #[staticmethod]
625    fn is_fixed_size_list(t: PyDataType) -> bool {
626        matches!(t.0, DataType::FixedSizeList(_, _))
627    }
628    #[staticmethod]
629    fn is_list_view(t: PyDataType) -> bool {
630        matches!(t.0, DataType::ListView(_))
631    }
632    #[staticmethod]
633    fn is_large_list_view(t: PyDataType) -> bool {
634        matches!(t.0, DataType::LargeListView(_))
635    }
636    #[staticmethod]
637    fn is_struct(t: PyDataType) -> bool {
638        matches!(t.0, DataType::Struct(_))
639    }
640    #[staticmethod]
641    fn is_union(t: PyDataType) -> bool {
642        matches!(t.0, DataType::Union(_, _))
643    }
644    #[staticmethod]
645    fn is_nested(t: PyDataType) -> bool {
646        t.0.is_nested()
647    }
648    #[staticmethod]
649    fn is_run_end_encoded(t: PyDataType) -> bool {
650        t.0.is_run_ends_type()
651    }
652    #[staticmethod]
653    fn is_temporal(t: PyDataType) -> bool {
654        t.0.is_temporal()
655    }
656    #[staticmethod]
657    fn is_timestamp(t: PyDataType) -> bool {
658        matches!(t.0, DataType::Timestamp(_, _))
659    }
660    #[staticmethod]
661    fn is_date(t: PyDataType) -> bool {
662        matches!(t.0, DataType::Date32 | DataType::Date64)
663    }
664    #[staticmethod]
665    fn is_date32(t: PyDataType) -> bool {
666        t.0 == DataType::Date32
667    }
668    #[staticmethod]
669    fn is_date64(t: PyDataType) -> bool {
670        t.0 == DataType::Date64
671    }
672    #[staticmethod]
673    fn is_time(t: PyDataType) -> bool {
674        matches!(t.0, DataType::Time32(_) | DataType::Time64(_))
675    }
676    #[staticmethod]
677    fn is_time32(t: PyDataType) -> bool {
678        matches!(t.0, DataType::Time32(_))
679    }
680    #[staticmethod]
681    fn is_time64(t: PyDataType) -> bool {
682        matches!(t.0, DataType::Time64(_))
683    }
684    #[staticmethod]
685    fn is_duration(t: PyDataType) -> bool {
686        matches!(t.0, DataType::Duration(_))
687    }
688    #[staticmethod]
689    fn is_interval(t: PyDataType) -> bool {
690        matches!(t.0, DataType::Interval(_))
691    }
692    #[staticmethod]
693    fn is_null(t: PyDataType) -> bool {
694        t.0 == DataType::Null
695    }
696    #[staticmethod]
697    fn is_binary(t: PyDataType) -> bool {
698        t.0 == DataType::Binary
699    }
700    #[staticmethod]
701    fn is_unicode(t: PyDataType) -> bool {
702        t.0 == DataType::Utf8
703    }
704    #[staticmethod]
705    fn is_string(t: PyDataType) -> bool {
706        t.0 == DataType::Utf8
707    }
708    #[staticmethod]
709    fn is_large_binary(t: PyDataType) -> bool {
710        t.0 == DataType::LargeBinary
711    }
712    #[staticmethod]
713    fn is_large_unicode(t: PyDataType) -> bool {
714        t.0 == DataType::LargeUtf8
715    }
716    #[staticmethod]
717    fn is_large_string(t: PyDataType) -> bool {
718        t.0 == DataType::LargeUtf8
719    }
720    #[staticmethod]
721    fn is_binary_view(t: PyDataType) -> bool {
722        t.0 == DataType::BinaryView
723    }
724    #[staticmethod]
725    fn is_string_view(t: PyDataType) -> bool {
726        t.0 == DataType::Utf8View
727    }
728    #[staticmethod]
729    fn is_fixed_size_binary(t: PyDataType) -> bool {
730        matches!(t.0, DataType::FixedSizeBinary(_))
731    }
732    #[staticmethod]
733    fn is_map(t: PyDataType) -> bool {
734        matches!(t.0, DataType::Map(_, _))
735    }
736    #[staticmethod]
737    fn is_dictionary(t: PyDataType) -> bool {
738        matches!(t.0, DataType::Dictionary(_, _))
739    }
740    #[staticmethod]
741    fn is_primitive(t: PyDataType) -> bool {
742        t.0.is_primitive()
743    }
744    #[staticmethod]
745    fn is_numeric(t: PyDataType) -> bool {
746        t.0.is_numeric()
747    }
748    #[staticmethod]
749    fn is_dictionary_key_type(t: PyDataType) -> bool {
750        t.0.is_dictionary_key_type()
751    }
752}