1use std::convert::Infallible;
2
3use super::*;
4
5use crate::error::PyPolarsErr;
6use crate::ffi::to_py::to_py_array;
7use polars_arrow as arrow;
8use polars_core::datatypes::{CompatLevel, DataType};
9use polars_core::prelude::*;
10use polars_core::utils::materialize_dyn_int;
11#[cfg(feature = "lazy")]
12use polars_lazy::frame::LazyFrame;
13#[cfg(feature = "lazy")]
14use polars_plan::dsl::Expr;
15#[cfg(feature = "lazy")]
16use polars_plan::plans::DslPlan;
17#[cfg(feature = "lazy")]
18use polars_utils::pl_serialize;
19use pyo3::exceptions::{PyTypeError, PyValueError};
20use pyo3::ffi::Py_uintptr_t;
21use pyo3::intern;
22use pyo3::prelude::*;
23use pyo3::pybacked::PyBackedStr;
24#[cfg(feature = "dtype-struct")]
25use pyo3::types::PyList;
26use pyo3::types::{PyBytes, PyDict, PyString};
27
28#[cfg(feature = "dtype-categorical")]
29pub(crate) fn get_series(obj: &Bound<'_, PyAny>) -> PyResult<Series> {
30 let s = obj.getattr(intern!(obj.py(), "_s"))?;
31 Ok(s.extract::<PySeries>()?.0)
32}
33
34#[repr(transparent)]
35#[derive(Debug, Clone)]
36pub struct PySeries(pub Series);
38
39#[repr(transparent)]
40#[derive(Debug, Clone)]
41pub struct PyDataFrame(pub DataFrame);
43
44#[cfg(feature = "lazy")]
45#[repr(transparent)]
46#[derive(Clone)]
47pub struct PyLazyFrame(pub LazyFrame);
55
56#[cfg(feature = "lazy")]
57#[repr(transparent)]
58#[derive(Clone)]
59pub struct PyExpr(pub Expr);
60
61#[repr(transparent)]
62#[derive(Clone)]
63pub struct PySchema(pub SchemaRef);
64
65#[repr(transparent)]
66#[derive(Clone)]
67pub struct PyDataType(pub DataType);
68
69#[repr(transparent)]
70#[derive(Clone, Copy)]
71pub struct PyTimeUnit(TimeUnit);
72
73#[repr(transparent)]
74#[derive(Clone)]
75pub struct PyField(Field);
76
77impl<'py> FromPyObject<'py> for PyField {
78 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
79 let py = ob.py();
80 let name = ob
81 .getattr(intern!(py, "name"))?
82 .str()?
83 .extract::<PyBackedStr>()?;
84 let dtype = ob.getattr(intern!(py, "dtype"))?.extract::<PyDataType>()?;
85 let name: &str = name.as_ref();
86 Ok(PyField(Field::new(name.into(), dtype.0)))
87 }
88}
89
90impl<'py> FromPyObject<'py> for PyTimeUnit {
91 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
92 let parsed = match &*ob.extract::<PyBackedStr>()? {
93 "ns" => TimeUnit::Nanoseconds,
94 "us" => TimeUnit::Microseconds,
95 "ms" => TimeUnit::Milliseconds,
96 v => {
97 return Err(PyValueError::new_err(format!(
98 "`time_unit` must be one of {{'ns', 'us', 'ms'}}, got {v}",
99 )))
100 }
101 };
102 Ok(PyTimeUnit(parsed))
103 }
104}
105
106impl<'py> IntoPyObject<'py> for PyTimeUnit {
107 type Target = PyString;
108 type Output = Bound<'py, Self::Target>;
109 type Error = Infallible;
110
111 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
112 let time_unit = match self.0 {
113 TimeUnit::Nanoseconds => "ns",
114 TimeUnit::Microseconds => "us",
115 TimeUnit::Milliseconds => "ms",
116 };
117 time_unit.into_pyobject(py)
118 }
119}
120
121impl From<PyDataFrame> for DataFrame {
122 fn from(value: PyDataFrame) -> Self {
123 value.0
124 }
125}
126
127impl From<PySeries> for Series {
128 fn from(value: PySeries) -> Self {
129 value.0
130 }
131}
132
133#[cfg(feature = "lazy")]
134impl From<PyLazyFrame> for LazyFrame {
135 fn from(value: PyLazyFrame) -> Self {
136 value.0
137 }
138}
139
140impl From<PySchema> for SchemaRef {
141 fn from(value: PySchema) -> Self {
142 value.0
143 }
144}
145
146impl AsRef<Series> for PySeries {
147 fn as_ref(&self) -> &Series {
148 &self.0
149 }
150}
151
152impl AsRef<DataFrame> for PyDataFrame {
153 fn as_ref(&self) -> &DataFrame {
154 &self.0
155 }
156}
157
158#[cfg(feature = "lazy")]
159impl AsRef<LazyFrame> for PyLazyFrame {
160 fn as_ref(&self) -> &LazyFrame {
161 &self.0
162 }
163}
164
165impl AsRef<Schema> for PySchema {
166 fn as_ref(&self) -> &Schema {
167 self.0.as_ref()
168 }
169}
170
171impl<'a> FromPyObject<'a> for PySeries {
172 fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
173 let ob = ob.call_method0("rechunk")?;
174
175 let name = ob.getattr("name")?;
176 let py_name = name.str()?;
177 let name = py_name.to_cow()?;
178
179 let kwargs = PyDict::new(ob.py());
180 if let Ok(compat_level) = ob.call_method0("_newest_compat_level") {
181 let compat_level = compat_level.extract().unwrap();
182 let compat_level =
183 CompatLevel::with_level(compat_level).unwrap_or(CompatLevel::newest());
184 kwargs.set_item("compat_level", compat_level.get_level())?;
185 }
186 let arr = ob.call_method("to_arrow", (), Some(&kwargs))?;
187 let arr = ffi::to_rust::array_to_rust(&arr)?;
188 let name = name.as_ref();
189 Ok(PySeries(
190 Series::try_from((PlSmallStr::from(name), arr)).map_err(PyPolarsErr::from)?,
191 ))
192 }
193}
194
195impl<'a> FromPyObject<'a> for PyDataFrame {
196 fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
197 let series = ob.call_method0("get_columns")?;
198 let n = ob.getattr("width")?.extract::<usize>()?;
199 let mut columns = Vec::with_capacity(n);
200 for pyseries in series.try_iter()? {
201 let pyseries = pyseries?;
202 let s = pyseries.extract::<PySeries>()?.0;
203 columns.push(s.into_column());
204 }
205 unsafe {
206 Ok(PyDataFrame(DataFrame::new_no_checks_height_from_first(
207 columns,
208 )))
209 }
210 }
211}
212
213#[cfg(feature = "lazy")]
214impl<'a> FromPyObject<'a> for PyLazyFrame {
215 fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
216 let s = ob.call_method0("__getstate__")?;
217 let b = s.extract::<Bound<'_, PyBytes>>()?;
218 let b = b.as_bytes();
219
220 let lp: DslPlan = pl_serialize::SerializeOptions::default()
221 .deserialize_from_reader(&*b)
222 .map_err(
223 |e| PyPolarsErr::Other(
224 format!("Error when deserializing LazyFrame. This may be due to mismatched polars versions. {}", e)
225 )
226 )?;
227
228 Ok(PyLazyFrame(LazyFrame::from(lp)))
229 }
230}
231
232#[cfg(feature = "lazy")]
233impl<'a> FromPyObject<'a> for PyExpr {
234 fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
235 let s = ob.call_method0("__getstate__")?.extract::<Vec<u8>>()?;
236
237 let e: Expr = pl_serialize::SerializeOptions::default()
238 .deserialize_from_reader(&*s)
239 .map_err(
240 |e| PyPolarsErr::Other(
241 format!("Error when deserializing 'Expr'. This may be due to mismatched polars versions. {}", e)
242 )
243 )?;
244
245 Ok(PyExpr(e))
246 }
247}
248
249impl<'py> IntoPyObject<'py> for PySeries {
250 type Target = PyAny;
251 type Output = Bound<'py, Self::Target>;
252 type Error = PyErr;
253
254 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
255 let polars = POLARS.bind(py);
256 let s = SERIES.bind(py);
257 match s
258 .getattr("_import_arrow_from_c")
259 .or_else(|_| s.getattr("_import_from_c"))
260 {
261 Ok(import_arrow_from_c) => {
263 let compat_level = CompatLevel::with_level(
265 s.getattr("_newest_compat_level")
266 .map_or(1, |newest_compat_level| {
267 newest_compat_level.call0().unwrap().extract().unwrap()
268 }),
269 )
270 .unwrap_or(CompatLevel::newest());
271 let mut chunk_ptrs = Vec::with_capacity(self.0.n_chunks());
273 for i in 0..self.0.n_chunks() {
274 let array = self.0.to_arrow(i, compat_level);
275 let schema = Box::new(arrow::ffi::export_field_to_c(&ArrowField::new(
276 "".into(),
277 array.dtype().clone(),
278 true,
279 )));
280 let array = Box::new(arrow::ffi::export_array_to_c(array.clone()));
281
282 let schema_ptr: *const arrow::ffi::ArrowSchema = Box::leak(schema);
283 let array_ptr: *const arrow::ffi::ArrowArray = Box::leak(array);
284
285 chunk_ptrs.push((schema_ptr as Py_uintptr_t, array_ptr as Py_uintptr_t))
286 }
287
288 let pyseries = import_arrow_from_c
290 .call1((self.0.name().as_str(), chunk_ptrs.clone()))
291 .unwrap();
292 for (schema_ptr, array_ptr) in chunk_ptrs {
294 let schema_ptr = schema_ptr as *mut arrow::ffi::ArrowSchema;
295 let array_ptr = array_ptr as *mut arrow::ffi::ArrowArray;
296 unsafe {
297 let _ = Box::from_raw(schema_ptr);
299
300 let array = Box::from_raw(array_ptr);
303 let array = *array;
306 std::mem::forget(array);
307 }
308 }
309
310 Ok(pyseries)
311 }
312 Err(_) => {
314 let s = self.0.rechunk();
315 let name = s.name().as_str();
316 let arr = s.to_arrow(0, CompatLevel::oldest());
317 let pyarrow = py.import("pyarrow").expect("pyarrow not installed");
318
319 let arg = to_py_array(arr, pyarrow).unwrap();
320 let s = polars.call_method1("from_arrow", (arg,)).unwrap();
321 let s = s.call_method1("rename", (name,)).unwrap();
322 Ok(s)
323 }
324 }
325 }
326}
327
328impl<'py> IntoPyObject<'py> for PyDataFrame {
329 type Target = PyAny;
330 type Output = Bound<'py, Self::Target>;
331 type Error = PyErr;
332
333 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
334 let pyseries = self
335 .0
336 .get_columns()
337 .iter()
338 .map(|s| PySeries(s.as_materialized_series().clone()).into_pyobject(py))
339 .collect::<PyResult<Vec<_>>>()?;
340
341 let polars = POLARS.bind(py);
342 polars.call_method1("DataFrame", (pyseries,))
343 }
344}
345
346#[cfg(feature = "lazy")]
347impl<'py> IntoPyObject<'py> for PyLazyFrame {
348 type Target = PyAny;
349 type Output = Bound<'py, Self::Target>;
350 type Error = PyErr;
351
352 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
353 dbg!("into py");
354 let polars = POLARS.bind(py);
355 let cls = polars.getattr("LazyFrame")?;
356 let instance = cls.call_method1(intern!(py, "__new__"), (&cls,)).unwrap();
357
358 let buf = pl_serialize::SerializeOptions::default()
359 .serialize_to_bytes(&self.0.logical_plan)
360 .unwrap();
361 instance.call_method1("__setstate__", (&buf,))?;
362 Ok(instance)
363 }
364}
365
366#[cfg(feature = "lazy")]
367impl<'py> IntoPyObject<'py> for PyExpr {
368 type Target = PyAny;
369 type Output = Bound<'py, Self::Target>;
370 type Error = PyErr;
371
372 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
373 let polars = POLARS.bind(py);
374 let cls = polars.getattr("Expr")?;
375 let instance = cls.call_method1(intern!(py, "__new__"), (&cls,))?;
376
377 let buf = pl_serialize::SerializeOptions::default()
378 .serialize_to_bytes(&self.0)
379 .unwrap();
380
381 instance
382 .call_method1("__setstate__", (&buf,))
383 .map_err(|err| {
384 let msg = format!("deserialization failed: {err}");
385 PyValueError::new_err(msg)
386 })
387 }
388}
389
390#[cfg(feature = "dtype-categorical")]
391pub(crate) fn to_series(py: Python, s: PySeries) -> PyObject {
392 let series = SERIES.bind(py);
393 let constructor = series
394 .getattr(intern!(series.py(), "_from_pyseries"))
395 .unwrap();
396 constructor
397 .call1((s,))
398 .unwrap()
399 .into_pyobject(py)
400 .unwrap()
401 .into()
402}
403
404impl<'py> IntoPyObject<'py> for PyDataType {
405 type Target = PyAny;
406 type Output = Bound<'py, Self::Target>;
407 type Error = PyErr;
408
409 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
410 let pl = POLARS.bind(py);
411
412 match &self.0 {
413 DataType::Int8 => {
414 let class = pl.getattr(intern!(py, "Int8")).unwrap();
415 class.call0()
416 }
417 DataType::Int16 => {
418 let class = pl.getattr(intern!(py, "Int16")).unwrap();
419 class.call0()
420 }
421 DataType::Int32 => {
422 let class = pl.getattr(intern!(py, "Int32")).unwrap();
423 class.call0()
424 }
425 DataType::Int64 => {
426 let class = pl.getattr(intern!(py, "Int64")).unwrap();
427 class.call0()
428 }
429 DataType::UInt8 => {
430 let class = pl.getattr(intern!(py, "UInt8")).unwrap();
431 class.call0()
432 }
433 DataType::UInt16 => {
434 let class = pl.getattr(intern!(py, "UInt16")).unwrap();
435 class.call0()
436 }
437 DataType::UInt32 => {
438 let class = pl.getattr(intern!(py, "UInt32")).unwrap();
439 class.call0()
440 }
441 DataType::UInt64 => {
442 let class = pl.getattr(intern!(py, "UInt64")).unwrap();
443 class.call0()
444 }
445 DataType::Float32 => {
446 let class = pl.getattr(intern!(py, "Float32")).unwrap();
447 class.call0()
448 }
449 DataType::Float64 | DataType::Unknown(UnknownKind::Float) => {
450 let class = pl.getattr(intern!(py, "Float64")).unwrap();
451 class.call0()
452 }
453 #[cfg(feature = "dtype-decimal")]
454 DataType::Decimal(precision, scale) => {
455 let class = pl.getattr(intern!(py, "Decimal")).unwrap();
456 let args = (*precision, *scale);
457 class.call1(args)
458 }
459 DataType::Boolean => {
460 let class = pl.getattr(intern!(py, "Boolean")).unwrap();
461 class.call0()
462 }
463 DataType::String | DataType::Unknown(UnknownKind::Str) => {
464 let class = pl.getattr(intern!(py, "String")).unwrap();
465 class.call0()
466 }
467 DataType::Binary => {
468 let class = pl.getattr(intern!(py, "Binary")).unwrap();
469 class.call0()
470 }
471 #[cfg(feature = "dtype-array")]
472 DataType::Array(inner, size) => {
473 let class = pl.getattr(intern!(py, "Array")).unwrap();
474 let inner = PyDataType(*inner.clone()).into_pyobject(py)?;
475 let args = (inner, *size);
476 class.call1(args)
477 }
478 DataType::List(inner) => {
479 let class = pl.getattr(intern!(py, "List")).unwrap();
480 let inner = PyDataType(*inner.clone()).into_pyobject(py)?;
481 class.call1((inner,))
482 }
483 DataType::Date => {
484 let class = pl.getattr(intern!(py, "Date")).unwrap();
485 class.call0()
486 }
487 DataType::Datetime(tu, tz) => {
488 let datetime_class = pl.getattr(intern!(py, "Datetime")).unwrap();
489 datetime_class.call1((tu.to_ascii(), tz.as_ref().map(|s| s.as_str())))
490 }
491 DataType::Duration(tu) => {
492 let duration_class = pl.getattr(intern!(py, "Duration")).unwrap();
493 duration_class.call1((tu.to_ascii(),))
494 }
495 #[cfg(feature = "object")]
496 DataType::Object(_, _) => {
497 let class = pl.getattr(intern!(py, "Object")).unwrap();
498 class.call0()
499 }
500 #[cfg(feature = "dtype-categorical")]
501 DataType::Categorical(_, ordering) => {
502 let class = pl.getattr(intern!(py, "Categorical")).unwrap();
503 let ordering = match ordering {
504 CategoricalOrdering::Physical => "physical",
505 CategoricalOrdering::Lexical => "lexical",
506 };
507 class.call1((ordering,))
508 }
509 #[cfg(feature = "dtype-categorical")]
510 DataType::Enum(rev_map, _) => {
511 let categories = rev_map.as_ref().unwrap().get_categories();
513 let class = pl.getattr(intern!(py, "Enum")).unwrap();
514 let s = Series::from_arrow("category".into(), categories.clone().boxed()).unwrap();
515 let series = to_series(py, PySeries(s));
516 return class.call1((series,));
517 }
518 DataType::Time => pl.getattr(intern!(py, "Time")),
519 #[cfg(feature = "dtype-struct")]
520 DataType::Struct(fields) => {
521 let field_class = pl.getattr(intern!(py, "Field")).unwrap();
522 let iter = fields
523 .iter()
524 .map(|fld| {
525 let name = fld.name().as_str();
526 let dtype = PyDataType(fld.dtype().clone()).into_pyobject(py)?;
527 field_class.call1((name, dtype))
528 })
529 .collect::<PyResult<Vec<_>>>()?;
530 let fields = PyList::new(py, iter)?;
531 let struct_class = pl.getattr(intern!(py, "Struct")).unwrap();
532 struct_class.call1((fields,))
533 }
534 DataType::Null => {
535 let class = pl.getattr(intern!(py, "Null")).unwrap();
536 class.call0()
537 }
538 DataType::Unknown(UnknownKind::Int(v)) => {
539 PyDataType(materialize_dyn_int(*v).dtype()).into_pyobject(py)
540 }
541 DataType::Unknown(_) => {
542 let class = pl.getattr(intern!(py, "Unknown")).unwrap();
543 class.call0()
544 }
545 DataType::BinaryOffset => {
546 panic!("this type isn't exposed to python")
547 }
548 #[allow(unreachable_patterns)]
549 _ => panic!("activate dtype"),
550 }
551 }
552}
553
554impl<'py> IntoPyObject<'py> for PySchema {
555 type Target = PyDict;
556 type Output = Bound<'py, Self::Target>;
557 type Error = PyErr;
558
559 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
560 let dict = PyDict::new(py);
561 for (k, v) in self.0.iter() {
562 dict.set_item(k.as_str(), PyDataType(v.clone()).into_pyobject(py)?)?;
563 }
564 Ok(dict)
565 }
566}
567
568impl<'py> FromPyObject<'py> for PyDataType {
569 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
570 let py = ob.py();
571 let type_name = ob.get_type().qualname()?.to_string();
572
573 let dtype = match type_name.as_ref() {
574 "DataTypeClass" => {
575 let name = ob
577 .getattr(intern!(py, "__name__"))?
578 .str()?
579 .extract::<PyBackedStr>()?;
580 match &*name {
581 "Int8" => DataType::Int8,
582 "Int16" => DataType::Int16,
583 "Int32" => DataType::Int32,
584 "Int64" => DataType::Int64,
585 "UInt8" => DataType::UInt8,
586 "UInt16" => DataType::UInt16,
587 "UInt32" => DataType::UInt32,
588 "UInt64" => DataType::UInt64,
589 "Float32" => DataType::Float32,
590 "Float64" => DataType::Float64,
591 "Boolean" => DataType::Boolean,
592 "String" => DataType::String,
593 "Binary" => DataType::Binary,
594 #[cfg(feature = "dtype-categorical")]
595 "Categorical" => DataType::Categorical(None, Default::default()),
596 #[cfg(feature = "dtype-categorical")]
597 "Enum" => DataType::Enum(None, Default::default()),
598 "Date" => DataType::Date,
599 "Time" => DataType::Time,
600 "Datetime" => DataType::Datetime(TimeUnit::Microseconds, None),
601 "Duration" => DataType::Duration(TimeUnit::Microseconds),
602 #[cfg(feature = "dtype-decimal")]
603 "Decimal" => DataType::Decimal(None, None), "List" => DataType::List(Box::new(DataType::Null)),
605 #[cfg(feature = "dtype-array")]
606 "Array" => DataType::Array(Box::new(DataType::Null), 0),
607 #[cfg(feature = "dtype-struct")]
608 "Struct" => DataType::Struct(vec![]),
609 "Null" => DataType::Null,
610 #[cfg(feature = "object")]
611 "Object" => todo!(),
612 "Unknown" => DataType::Unknown(Default::default()),
613 dt => {
614 return Err(PyTypeError::new_err(format!(
615 "'{dt}' is not a Polars data type, or the plugin isn't compiled with the right features",
616 )))
617 },
618 }
619 },
620 "Int8" => DataType::Int8,
621 "Int16" => DataType::Int16,
622 "Int32" => DataType::Int32,
623 "Int64" => DataType::Int64,
624 "UInt8" => DataType::UInt8,
625 "UInt16" => DataType::UInt16,
626 "UInt32" => DataType::UInt32,
627 "UInt64" => DataType::UInt64,
628 "Float32" => DataType::Float32,
629 "Float64" => DataType::Float64,
630 "Boolean" => DataType::Boolean,
631 "String" => DataType::String,
632 "Binary" => DataType::Binary,
633 #[cfg(feature = "dtype-categorical")]
634 "Categorical" => {
635 let ordering = ob.getattr(intern!(py, "ordering")).unwrap();
636 let ordering = ordering.extract::<PyBackedStr>()?;
637 let ordering = match ordering.as_bytes() {
638 b"physical" => CategoricalOrdering::Physical,
639 b"lexical" => CategoricalOrdering::Lexical,
640 ordering => {
641 let ordering = std::str::from_utf8(ordering).unwrap();
642 return Err(PyValueError::new_err(format!("invalid ordering argument: {ordering}")))
643 }
644 };
645
646 DataType::Categorical(None, ordering)
647 },
648 #[cfg(feature = "dtype-categorical")]
649 "Enum" => {
650 let categories = ob.getattr(intern!(py, "categories")).unwrap();
651 let s = get_series(&categories.as_borrowed())?;
652 let ca = s.str().map_err(PyPolarsErr::from)?;
653 let categories = ca.downcast_iter().next().unwrap().clone();
654 DataType::Enum(Some(Arc::new(RevMapping::build_local(categories))), Default::default())
655 },
656 "Date" => DataType::Date,
657 "Time" => DataType::Time,
658 "Datetime" => {
659 let time_unit = ob.getattr(intern!(py, "time_unit")).unwrap();
660 let time_unit = time_unit.extract::<PyTimeUnit>()?.0;
661 let time_zone = ob.getattr(intern!(py, "time_zone")).unwrap();
662 let time_zone: Option<String> = time_zone.extract()?;
663 DataType::Datetime(time_unit, time_zone.map(PlSmallStr::from))
664 },
665 "Duration" => {
666 let time_unit = ob.getattr(intern!(py, "time_unit")).unwrap();
667 let time_unit = time_unit.extract::<PyTimeUnit>()?.0;
668 DataType::Duration(time_unit)
669 },
670 #[cfg(feature = "dtype-decimal")]
671 "Decimal" => {
672 let precision = ob.getattr(intern!(py, "precision"))?.extract()?;
673 let scale = ob.getattr(intern!(py, "scale"))?.extract()?;
674 DataType::Decimal(precision, Some(scale))
675 },
676 "List" => {
677 let inner = ob.getattr(intern!(py, "inner")).unwrap();
678 let inner = inner.extract::<PyDataType>()?;
679 DataType::List(Box::new(inner.0))
680 },
681 #[cfg(feature = "dtype-array")]
682 "Array" => {
683 let inner = ob.getattr(intern!(py, "inner")).unwrap();
684 let size = ob.getattr(intern!(py, "size")).unwrap();
685 let inner = inner.extract::<PyDataType>()?;
686 let size = size.extract::<usize>()?;
687 DataType::Array(Box::new(inner.0), size)
688 },
689 #[cfg(feature = "dtype-struct")]
690 "Struct" => {
691 let fields = ob.getattr(intern!(py, "fields"))?;
692 let fields = fields
693 .extract::<Vec<PyField>>()?
694 .into_iter()
695 .map(|f| f.0)
696 .collect::<Vec<Field>>();
697 DataType::Struct(fields)
698 },
699 "Null" => DataType::Null,
700 #[cfg(feature = "object")]
701 "Object" => panic!("object not supported"),
702 "Unknown" => DataType::Unknown(Default::default()),
703 dt => {
704 return Err(PyTypeError::new_err(format!(
705 "'{dt}' is not a Polars data type, or the plugin isn't compiled with the right features",
706 )))
707 },
708 };
709 Ok(PyDataType(dtype))
710 }
711}