Skip to main content

polars_python/conversion/
mod.rs

1pub(crate) mod any_value;
2mod categorical;
3pub(crate) mod chunked_array;
4mod datetime;
5
6use std::convert::Infallible;
7use std::fmt::{Display, Formatter};
8use std::fs::File;
9use std::hash::{Hash, Hasher};
10
11pub use categorical::PyCategories;
12#[cfg(feature = "object")]
13use polars::chunked_array::object::PolarsObjectSafe;
14#[cfg(feature = "pivot")]
15use polars::frame::PivotColumnNaming;
16use polars::frame::row::Row;
17#[cfg(feature = "avro")]
18use polars::io::avro::AvroCompression;
19use polars::prelude::ColumnMapping;
20use polars::prelude::default_values::DefaultFieldValues;
21use polars::prelude::deletion::{DeletionFilesList, DeltaDeletionVectorProvider};
22use polars::series::ops::NullBehavior;
23use polars_buffer::Buffer;
24use polars_compute::decimal::dec128_verify_prec_scale;
25use polars_core::datatypes::extension::get_extension_type_or_generic;
26use polars_core::schema::iceberg::IcebergSchema;
27use polars_core::utils::arrow::array::Array;
28use polars_core::utils::materialize_dyn_int;
29use polars_lazy::prelude::*;
30#[cfg(feature = "parquet")]
31use polars_parquet::write::StatisticsOptions;
32use polars_plan::dsl::ScanSources;
33use polars_plan::dsl::default_values::IcebergDefaultFieldValues;
34use polars_utils::compression::{BrotliLevel, GzipLevel, ZstdLevel};
35use polars_utils::pl_serialize;
36use polars_utils::pl_str::PlSmallStr;
37use polars_utils::python_function::PythonObject;
38use polars_utils::total_ord::{TotalEq, TotalHash};
39use pyo3::basic::CompareOp;
40use pyo3::exceptions::{PyTypeError, PyValueError};
41use pyo3::intern;
42use pyo3::prelude::*;
43use pyo3::pybacked::{PyBackedBytes, PyBackedStr};
44use pyo3::sync::PyOnceLock;
45use pyo3::types::{IntoPyDict, PyBytes, PyDict, PyList, PySequence, PyString};
46use serde::Serialize;
47use serde::de::DeserializeOwned;
48
49use crate::error::PyPolarsErr;
50use crate::expr::PyExpr;
51use crate::file::{PythonScanSourceInput, get_python_scan_source_input};
52#[cfg(feature = "object")]
53use crate::object::OBJECT_NAME;
54use crate::prelude::*;
55use crate::py_modules::{pl_series, polars};
56use crate::series::{PySeries, import_schema_pycapsule};
57use crate::utils::to_py_err;
58use crate::{PyDataFrame, PyLazyFrame, interned};
59
60/// # Safety
61/// Should only be implemented for transparent types
62pub(crate) unsafe trait Transparent {
63    type Target;
64}
65
66unsafe impl Transparent for PySeries {
67    type Target = Series;
68}
69
70unsafe impl<T> Transparent for Wrap<T> {
71    type Target = T;
72}
73
74unsafe impl<T: Transparent> Transparent for Option<T> {
75    type Target = Option<T::Target>;
76}
77
78pub(crate) fn reinterpret_vec<T: Transparent>(input: Vec<T>) -> Vec<T::Target> {
79    assert_eq!(size_of::<T>(), size_of::<T::Target>());
80    assert_eq!(align_of::<T>(), align_of::<T::Target>());
81    let len = input.len();
82    let cap = input.capacity();
83    let mut manual_drop_vec = std::mem::ManuallyDrop::new(input);
84    let vec_ptr: *mut T = manual_drop_vec.as_mut_ptr();
85    let ptr: *mut T::Target = vec_ptr as *mut T::Target;
86    unsafe { Vec::from_raw_parts(ptr, len, cap) }
87}
88
89pub(crate) fn vec_extract_wrapped<T>(buf: Vec<Wrap<T>>) -> Vec<T> {
90    reinterpret_vec(buf)
91}
92
93#[derive(PartialEq, Eq, Hash)]
94#[repr(transparent)]
95pub struct Wrap<T>(pub T);
96
97impl<T> Clone for Wrap<T>
98where
99    T: Clone,
100{
101    fn clone(&self) -> Self {
102        Wrap(self.0.clone())
103    }
104}
105impl<T> From<T> for Wrap<T> {
106    fn from(t: T) -> Self {
107        Wrap(t)
108    }
109}
110
111// extract a Rust DataFrame from a python DataFrame, that is DataFrame<PyDataFrame<RustDataFrame>>
112pub(crate) fn get_df(obj: &Bound<'_, PyAny>) -> PyResult<DataFrame> {
113    let pydf = obj.getattr(intern!(obj.py(), "_df"))?;
114    Ok(pydf.extract::<PyDataFrame>()?.df.into_inner())
115}
116
117pub(crate) fn get_lf(obj: &Bound<'_, PyAny>) -> PyResult<LazyFrame> {
118    let pydf = obj.getattr(intern!(obj.py(), "_ldf"))?;
119    Ok(pydf.extract::<PyLazyFrame>()?.ldf.into_inner())
120}
121
122pub(crate) fn get_series(obj: &Bound<'_, PyAny>) -> PyResult<Series> {
123    let s = obj.getattr(intern!(obj.py(), "_s"))?;
124    Ok(s.extract::<PySeries>()?.series.into_inner())
125}
126
127pub(crate) fn to_series(py: Python<'_>, s: PySeries) -> PyResult<Bound<'_, PyAny>> {
128    let series = pl_series(py).bind(py);
129    let constructor = series.getattr(intern!(py, "_from_pyseries"))?;
130    constructor.call1((s,))
131}
132
133pub(crate) fn serde_pickle<'py, T: Serialize>(
134    val: &T,
135    py: Python<'py>,
136) -> PyResult<Bound<'py, PyBytes>> {
137    // For pickling we set FC is false, as that is used for caching (compact is faster) and is not
138    // intended to be used across different versions.
139    let mut writer: Vec<u8> = vec![];
140    pl_serialize::SerializeOptions::default()
141        .serialize_into_writer::<_, _, false>(&mut writer, &val)
142        .map_err(|e| PyPolarsErr::Other(format!("{e}")))?;
143    Ok(PyBytes::new(py, &writer))
144}
145
146pub(crate) fn serde_unpickle<T: DeserializeOwned>(
147    val: &mut T,
148    state: &Bound<PyAny>,
149) -> PyResult<()> {
150    let bytes = state.extract::<PyBackedBytes>()?;
151    *val = pl_serialize::SerializeOptions::default()
152        .deserialize_from_reader::<_, _, false>(&*bytes)
153        .map_err(|e| PyPolarsErr::Other(format!("{e}")))?;
154    Ok(())
155}
156
157impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<PlSmallStr> {
158    type Error = PyErr;
159
160    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
161        Ok(Wrap((&*ob.extract::<PyBackedStr>()?).into()))
162    }
163}
164
165#[cfg(feature = "csv")]
166impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<NullValues> {
167    type Error = PyErr;
168
169    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
170        if let Ok(s) = ob.extract::<PyBackedStr>() {
171            Ok(Wrap(NullValues::AllColumnsSingle((&*s).into())))
172        } else if let Ok(s) = ob.extract::<Vec<PyBackedStr>>() {
173            Ok(Wrap(NullValues::AllColumns(
174                s.into_iter().map(|x| (&*x).into()).collect(),
175            )))
176        } else if let Ok(s) = ob.extract::<Vec<(PyBackedStr, PyBackedStr)>>() {
177            Ok(Wrap(NullValues::Named(
178                s.into_iter()
179                    .map(|(a, b)| ((&*a).into(), (&*b).into()))
180                    .collect(),
181            )))
182        } else {
183            Err(
184                PyPolarsErr::Other("could not extract value from null_values argument".into())
185                    .into(),
186            )
187        }
188    }
189}
190
191fn struct_dict<'a, 'py>(
192    py: Python<'py>,
193    vals: impl Iterator<Item = AnyValue<'a>>,
194    flds: &[Field],
195) -> PyResult<Bound<'py, PyDict>> {
196    let dict = PyDict::new(py);
197    flds.iter().zip(vals).try_for_each(|(fld, val)| {
198        dict.set_item(fld.name().as_str(), Wrap(val).into_pyobject(py)?)
199    })?;
200    Ok(dict)
201}
202
203impl<'py> IntoPyObject<'py> for Wrap<Series> {
204    type Target = PyAny;
205    type Output = Bound<'py, Self::Target>;
206    type Error = PyErr;
207
208    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
209        to_series(py, PySeries::new(self.0))
210    }
211}
212
213impl<'py> IntoPyObject<'py> for &Wrap<DataType> {
214    type Target = PyAny;
215    type Output = Bound<'py, Self::Target>;
216    type Error = PyErr;
217
218    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
219        let pl = polars(py).bind(py);
220
221        match &self.0 {
222            DataType::Int8 => {
223                let class = pl.getattr(intern!(py, "Int8"))?;
224                class.call0()
225            },
226            DataType::Int16 => {
227                let class = pl.getattr(intern!(py, "Int16"))?;
228                class.call0()
229            },
230            DataType::Int32 => {
231                let class = pl.getattr(intern!(py, "Int32"))?;
232                class.call0()
233            },
234            DataType::Int64 => {
235                let class = pl.getattr(intern!(py, "Int64"))?;
236                class.call0()
237            },
238            DataType::UInt8 => {
239                let class = pl.getattr(intern!(py, "UInt8"))?;
240                class.call0()
241            },
242            DataType::UInt16 => {
243                let class = pl.getattr(intern!(py, "UInt16"))?;
244                class.call0()
245            },
246            DataType::UInt32 => {
247                let class = pl.getattr(intern!(py, "UInt32"))?;
248                class.call0()
249            },
250            DataType::UInt64 => {
251                let class = pl.getattr(intern!(py, "UInt64"))?;
252                class.call0()
253            },
254            DataType::UInt128 => {
255                let class = pl.getattr(intern!(py, "UInt128"))?;
256                class.call0()
257            },
258            DataType::Int128 => {
259                let class = pl.getattr(intern!(py, "Int128"))?;
260                class.call0()
261            },
262            DataType::Float16 => {
263                let class = pl.getattr(intern!(py, "Float16"))?;
264                class.call0()
265            },
266            DataType::Float32 => {
267                let class = pl.getattr(intern!(py, "Float32"))?;
268                class.call0()
269            },
270            DataType::Float64 | DataType::Unknown(UnknownKind::Float) => {
271                let class = pl.getattr(intern!(py, "Float64"))?;
272                class.call0()
273            },
274            DataType::Decimal(precision, scale) => {
275                let class = pl.getattr(intern!(py, "Decimal"))?;
276                let args = (*precision, *scale);
277                class.call1(args)
278            },
279            DataType::Boolean => {
280                let class = pl.getattr(intern!(py, "Boolean"))?;
281                class.call0()
282            },
283            DataType::String | DataType::Unknown(UnknownKind::Str) => {
284                let class = pl.getattr(intern!(py, "String"))?;
285                class.call0()
286            },
287            DataType::Binary => {
288                let class = pl.getattr(intern!(py, "Binary"))?;
289                class.call0()
290            },
291            DataType::Array(inner, size) => {
292                let class = pl.getattr(intern!(py, "Array"))?;
293                let inner = Wrap(*inner.clone());
294                let args = (&inner, *size);
295                class.call1(args)
296            },
297            DataType::List(inner) => {
298                let class = pl.getattr(intern!(py, "List"))?;
299                let inner = Wrap(*inner.clone());
300                class.call1((&inner,))
301            },
302            DataType::Date => {
303                let class = pl.getattr(intern!(py, "Date"))?;
304                class.call0()
305            },
306            DataType::Datetime(tu, tz) => {
307                let datetime_class = pl.getattr(intern!(py, "Datetime"))?;
308                datetime_class.call1((tu.to_ascii(), tz.as_deref().map(|x| x.as_str())))
309            },
310            DataType::Duration(tu) => {
311                let duration_class = pl.getattr(intern!(py, "Duration"))?;
312                duration_class.call1((tu.to_ascii(),))
313            },
314            #[cfg(feature = "object")]
315            DataType::Object(_) => {
316                let class = pl.getattr(intern!(py, "Object"))?;
317                class.call0()
318            },
319            DataType::Categorical(cats, _) => {
320                let categories_class = pl.getattr(intern!(py, "Categories"))?;
321                let categorical_class = pl.getattr(intern!(py, "Categorical"))?;
322                let categories = categories_class
323                    .call_method1("_from_py_categories", (PyCategories::from(cats.clone()),))?;
324                let kwargs = [("categories", categories)];
325                categorical_class.call((), Some(&kwargs.into_py_dict(py)?))
326            },
327            DataType::Enum(_, mapping) => {
328                let categories = unsafe {
329                    StringChunked::from_chunks(
330                        PlSmallStr::from_static("category"),
331                        vec![mapping.to_arrow(true)],
332                    )
333                };
334                let class = pl.getattr(intern!(py, "Enum"))?;
335                let series = to_series(py, categories.into_series().into())?;
336                class.call1((series,))
337            },
338            DataType::Time => pl.getattr(intern!(py, "Time")).and_then(|x| x.call0()),
339            DataType::Struct(fields) => {
340                let field_class = pl.getattr(intern!(py, "Field"))?;
341                let iter = fields.iter().map(|fld| {
342                    let name = fld.name().as_str();
343                    let dtype = Wrap(fld.dtype().clone());
344                    field_class.call1((name, &dtype)).unwrap()
345                });
346                let fields = PyList::new(py, iter)?;
347                let struct_class = pl.getattr(intern!(py, "Struct"))?;
348                struct_class.call1((fields,))
349            },
350            DataType::Null => {
351                let class = pl.getattr(intern!(py, "Null"))?;
352                class.call0()
353            },
354            DataType::Extension(typ, storage) => {
355                let py_storage = Wrap((**storage).clone()).into_pyobject(py)?;
356                let py_typ = pl
357                    .getattr(intern!(py, "get_extension_type"))?
358                    .call1((typ.name(),))?;
359                let class = if py_typ.is_none()
360                    || py_typ.str().map(|s| s == "storage").ok() == Some(true)
361                {
362                    pl.getattr(intern!(py, "Extension"))?
363                } else {
364                    py_typ
365                };
366                let from_params = class.getattr(intern!(py, "ext_from_params"))?;
367                from_params.call1((typ.name(), py_storage, typ.serialize_metadata()))
368            },
369            DataType::Unknown(UnknownKind::Int(v)) => {
370                Wrap(materialize_dyn_int(*v).dtype()).into_pyobject(py)
371            },
372            DataType::Unknown(_) => {
373                let class = pl.getattr(intern!(py, "Unknown"))?;
374                class.call0()
375            },
376            DataType::BinaryOffset => {
377                unimplemented!()
378            },
379        }
380    }
381}
382
383impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<Field> {
384    type Error = PyErr;
385
386    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
387        let py = ob.py();
388        let name = ob
389            .getattr(interned::NAME.get(py))?
390            .str()?
391            .extract::<PyBackedStr>()?;
392        let dtype = ob
393            .getattr(interned::DTYPE.get(py))?
394            .extract::<Wrap<DataType>>()?;
395        Ok(Wrap(Field::new((&*name).into(), dtype.0)))
396    }
397}
398
399impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<DataType> {
400    type Error = PyErr;
401
402    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
403        let py = ob.py();
404        let type_name = ob.get_type().qualname()?.to_string();
405
406        let dtype = match &*type_name {
407            "DataTypeClass" => {
408                // just the class, not an object
409                let name = ob
410                    .getattr(interned::DUNDER_NAME.get(py))?
411                    .str()?
412                    .extract::<PyBackedStr>()?;
413                match &*name {
414                    "Int8" => DataType::Int8,
415                    "Int16" => DataType::Int16,
416                    "Int32" => DataType::Int32,
417                    "Int64" => DataType::Int64,
418                    "Int128" => DataType::Int128,
419                    "UInt8" => DataType::UInt8,
420                    "UInt16" => DataType::UInt16,
421                    "UInt32" => DataType::UInt32,
422                    "UInt64" => DataType::UInt64,
423                    "UInt128" => DataType::UInt128,
424                    "Float16" => DataType::Float16,
425                    "Float32" => DataType::Float32,
426                    "Float64" => DataType::Float64,
427                    "Boolean" => DataType::Boolean,
428                    "String" => DataType::String,
429                    "Binary" => DataType::Binary,
430                    "Categorical" => DataType::from_categories(Categories::global()),
431                    "Enum" => DataType::from_frozen_categories(FrozenCategories::new([]).unwrap()),
432                    "Date" => DataType::Date,
433                    "Time" => DataType::Time,
434                    "Datetime" => DataType::Datetime(TimeUnit::Microseconds, None),
435                    "Duration" => DataType::Duration(TimeUnit::Microseconds),
436                    "List" => DataType::List(Box::new(DataType::Null)),
437                    "Array" => DataType::Array(Box::new(DataType::Null), 0),
438                    "Struct" => DataType::Struct(vec![]),
439                    "Null" => DataType::Null,
440                    #[cfg(feature = "object")]
441                    "Object" => DataType::Object(OBJECT_NAME),
442                    "Unknown" => DataType::Unknown(Default::default()),
443                    "Decimal" => {
444                        return Err(PyTypeError::new_err(
445                            "Decimal without precision/scale set is not a valid Polars datatype",
446                        ));
447                    },
448                    dt => {
449                        return Err(PyTypeError::new_err(format!(
450                            "'{dt}' is not a Polars data type",
451                        )));
452                    },
453                }
454            },
455            "Int8" => DataType::Int8,
456            "Int16" => DataType::Int16,
457            "Int32" => DataType::Int32,
458            "Int64" => DataType::Int64,
459            "Int128" => DataType::Int128,
460            "UInt8" => DataType::UInt8,
461            "UInt16" => DataType::UInt16,
462            "UInt32" => DataType::UInt32,
463            "UInt64" => DataType::UInt64,
464            "UInt128" => DataType::UInt128,
465            "Float16" => DataType::Float16,
466            "Float32" => DataType::Float32,
467            "Float64" => DataType::Float64,
468            "Boolean" => DataType::Boolean,
469            "String" => DataType::String,
470            "Binary" => DataType::Binary,
471            "Categorical" => {
472                let categories = ob.getattr(intern!(py, "categories")).unwrap();
473                let py_categories = categories.getattr(intern!(py, "_categories")).unwrap();
474                let py_categories = py_categories.extract::<PyCategories>()?;
475                DataType::from_categories(py_categories.categories().clone())
476            },
477            "Enum" => {
478                let categories = ob.getattr(intern!(py, "categories")).unwrap();
479                let s = get_series(&categories.as_borrowed())?;
480                let ca = s.str().map_err(PyPolarsErr::from)?;
481                let categories = ca.downcast_iter().next().unwrap().clone();
482                assert!(!categories.has_nulls());
483                DataType::from_frozen_categories(
484                    FrozenCategories::new(categories.values_iter()).unwrap(),
485                )
486            },
487            "Date" => DataType::Date,
488            "Time" => DataType::Time,
489            "Datetime" => {
490                let time_unit = ob.getattr(intern!(py, "time_unit")).unwrap();
491                let time_unit = time_unit.extract::<Wrap<TimeUnit>>()?.0;
492                let time_zone = ob.getattr(intern!(py, "time_zone")).unwrap();
493                let time_zone = time_zone.extract::<Option<PyBackedStr>>()?;
494                DataType::Datetime(
495                    time_unit,
496                    TimeZone::opt_try_new(time_zone.as_deref()).map_err(to_py_err)?,
497                )
498            },
499            "Duration" => {
500                let time_unit = ob.getattr(intern!(py, "time_unit")).unwrap();
501                let time_unit = time_unit.extract::<Wrap<TimeUnit>>()?.0;
502                DataType::Duration(time_unit)
503            },
504            "Decimal" => {
505                let precision = ob.getattr(intern!(py, "precision"))?.extract()?;
506                let scale = ob.getattr(intern!(py, "scale"))?.extract()?;
507                dec128_verify_prec_scale(precision, scale).map_err(to_py_err)?;
508                DataType::Decimal(precision, scale)
509            },
510            "List" => {
511                let inner = ob.getattr(intern!(py, "inner")).unwrap();
512                let inner = inner.extract::<Wrap<DataType>>()?;
513                DataType::List(Box::new(inner.0))
514            },
515            "Array" => {
516                let inner = ob.getattr(intern!(py, "inner")).unwrap();
517                let size = ob.getattr(intern!(py, "size")).unwrap();
518                let inner = inner.extract::<Wrap<DataType>>()?;
519                let size = size.extract::<usize>()?;
520                DataType::Array(Box::new(inner.0), size)
521            },
522            "Struct" => {
523                let fields = ob.getattr(intern!(py, "fields"))?;
524                let fields = fields
525                    .extract::<Vec<Wrap<Field>>>()?
526                    .into_iter()
527                    .map(|f| f.0)
528                    .collect::<Vec<Field>>();
529                DataType::Struct(fields)
530            },
531            "Null" => DataType::Null,
532            #[cfg(feature = "object")]
533            "Object" => DataType::Object(OBJECT_NAME),
534            "Unknown" => DataType::Unknown(Default::default()),
535            dt => {
536                let base_ext = polars(py)
537                    .getattr(py, intern!(py, "BaseExtension"))
538                    .unwrap();
539                if ob.is_instance(base_ext.bind(py))? {
540                    let ext_name_f = ob.getattr(intern!(py, "ext_name"))?;
541                    let ext_metadata_f = ob.getattr(intern!(py, "ext_metadata"))?;
542                    let ext_storage_f = ob.getattr(intern!(py, "ext_storage"))?;
543                    let name: String = ext_name_f.call0()?.extract()?;
544                    let metadata: Option<String> = ext_metadata_f.call0()?.extract()?;
545                    let storage: Wrap<DataType> = ext_storage_f.call0()?.extract()?;
546                    let ext_typ =
547                        get_extension_type_or_generic(&name, &storage.0, metadata.as_deref());
548                    return Ok(Wrap(DataType::Extension(ext_typ, Box::new(storage.0))));
549                }
550
551                return Err(PyTypeError::new_err(format!(
552                    "'{dt}' is not a Polars data type",
553                )));
554            },
555        };
556        Ok(Wrap(dtype))
557    }
558}
559
560impl<'py> IntoPyObject<'py> for Wrap<TimeUnit> {
561    type Target = PyString;
562    type Output = Bound<'py, Self::Target>;
563    type Error = Infallible;
564
565    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
566        self.0.to_ascii().into_pyobject(py)
567    }
568}
569
570#[cfg(feature = "parquet")]
571impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<StatisticsOptions> {
572    type Error = PyErr;
573
574    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
575        let mut statistics = StatisticsOptions::empty();
576
577        let dict = ob.cast::<PyDict>()?;
578        for (key, val) in dict.iter() {
579            let key = key.extract::<PyBackedStr>()?;
580            let val = val.extract::<bool>()?;
581
582            match key.as_ref() {
583                "min" => statistics.min_value = val,
584                "max" => statistics.max_value = val,
585                "distinct_count" => statistics.distinct_count = val,
586                "null_count" => statistics.null_count = val,
587                _ => {
588                    return Err(PyTypeError::new_err(format!(
589                        "'{key}' is not a valid statistic option",
590                    )));
591                },
592            }
593        }
594
595        Ok(Wrap(statistics))
596    }
597}
598
599impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<Row<'static>> {
600    type Error = PyErr;
601
602    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
603        let vals = ob.extract::<Vec<Wrap<AnyValue<'static>>>>()?;
604        let vals = reinterpret_vec(vals);
605        Ok(Wrap(Row(vals)))
606    }
607}
608
609impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<Schema> {
610    type Error = PyErr;
611
612    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
613        let dict = ob.cast::<PyDict>()?;
614
615        Ok(Wrap(
616            dict.iter()
617                .map(|(key, val)| {
618                    let key = key.extract::<PyBackedStr>()?;
619                    let val = val.extract::<Wrap<DataType>>()?;
620
621                    Ok(Field::new((&*key).into(), val.0))
622                })
623                .collect::<PyResult<Schema>>()?,
624        ))
625    }
626}
627
628impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<ArrowSchema> {
629    type Error = PyErr;
630
631    fn extract(schema_object: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
632        let py = schema_object.py();
633
634        let schema_capsule = schema_object
635            .getattr(intern!(py, "__arrow_c_schema__"))?
636            .call0()?;
637
638        let field = import_schema_pycapsule(&schema_capsule.extract()?)?;
639
640        let ArrowDataType::Struct(fields) = field.dtype else {
641            return Err(PyValueError::new_err(format!(
642                "__arrow_c_schema__ of object did not return struct dtype: \
643                object: {:?}, dtype: {:?}",
644                schema_object, field.dtype
645            )));
646        };
647
648        let mut schema = ArrowSchema::from_iter_check_duplicates(fields).map_err(to_py_err)?;
649
650        if let Some(md) = field.metadata {
651            *schema.metadata_mut() = Arc::unwrap_or_clone(md);
652        }
653
654        Ok(Wrap(schema))
655    }
656}
657
658impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<ScanSources> {
659    type Error = PyErr;
660
661    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
662        let list = ob.cast::<PyList>()?.to_owned();
663
664        if list.is_empty() {
665            return Ok(Wrap(ScanSources::default()));
666        }
667
668        enum MutableSources {
669            Paths(Vec<PlRefPath>),
670            Files(Vec<File>),
671            Buffers(Vec<Buffer<u8>>),
672        }
673
674        let num_items = list.len();
675        let mut iter = list
676            .into_iter()
677            .map(|val| get_python_scan_source_input(val.unbind(), false));
678
679        let Some(first) = iter.next() else {
680            return Ok(Wrap(ScanSources::default()));
681        };
682
683        let mut sources = match first? {
684            PythonScanSourceInput::Path(path) => {
685                let mut sources = Vec::with_capacity(num_items);
686                sources.push(path);
687                MutableSources::Paths(sources)
688            },
689            PythonScanSourceInput::File(file) => {
690                let mut sources = Vec::with_capacity(num_items);
691                sources.push(file.into());
692                MutableSources::Files(sources)
693            },
694            PythonScanSourceInput::Buffer(buffer) => {
695                let mut sources = Vec::with_capacity(num_items);
696                sources.push(buffer);
697                MutableSources::Buffers(sources)
698            },
699        };
700
701        for source in iter {
702            match (&mut sources, source?) {
703                (MutableSources::Paths(v), PythonScanSourceInput::Path(p)) => v.push(p),
704                (MutableSources::Files(v), PythonScanSourceInput::File(f)) => v.push(f.into()),
705                (MutableSources::Buffers(v), PythonScanSourceInput::Buffer(f)) => v.push(f),
706                _ => {
707                    return Err(PyTypeError::new_err(
708                        "Cannot combine in-memory bytes, paths and files for scan sources",
709                    ));
710                },
711            }
712        }
713
714        Ok(Wrap(match sources {
715            MutableSources::Paths(i) => ScanSources::Paths(i.into()),
716            MutableSources::Files(i) => ScanSources::Files(i.into()),
717            MutableSources::Buffers(i) => ScanSources::Buffers(i.into()),
718        }))
719    }
720}
721
722impl<'py> IntoPyObject<'py> for Wrap<Schema> {
723    type Target = PyDict;
724    type Output = Bound<'py, Self::Target>;
725    type Error = PyErr;
726
727    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
728        let dict = PyDict::new(py);
729        self.0
730            .iter()
731            .try_for_each(|(k, v)| dict.set_item(k.as_str(), &Wrap(v.clone())))?;
732        Ok(dict)
733    }
734}
735
736#[derive(Debug)]
737#[repr(transparent)]
738pub struct ObjectValue {
739    pub inner: Py<PyAny>,
740}
741
742impl Clone for ObjectValue {
743    fn clone(&self) -> Self {
744        Python::attach(|py| Self {
745            inner: self.inner.clone_ref(py),
746        })
747    }
748}
749
750impl Hash for ObjectValue {
751    fn hash<H: Hasher>(&self, state: &mut H) {
752        let h = Python::attach(|py| self.inner.bind(py).hash().expect("should be hashable"));
753        state.write_isize(h)
754    }
755}
756
757impl Eq for ObjectValue {}
758
759impl PartialEq for ObjectValue {
760    fn eq(&self, other: &Self) -> bool {
761        Python::attach(|py| {
762            match self
763                .inner
764                .bind(py)
765                .rich_compare(other.inner.bind(py), CompareOp::Eq)
766            {
767                Ok(result) => result.is_truthy().unwrap(),
768                Err(_) => false,
769            }
770        })
771    }
772}
773
774impl TotalEq for ObjectValue {
775    fn tot_eq(&self, other: &Self) -> bool {
776        self == other
777    }
778}
779
780impl TotalHash for ObjectValue {
781    fn tot_hash<H>(&self, state: &mut H)
782    where
783        H: Hasher,
784    {
785        self.hash(state);
786    }
787}
788
789impl Display for ObjectValue {
790    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
791        write!(f, "{}", self.inner)
792    }
793}
794
795#[cfg(feature = "object")]
796impl PolarsObject for ObjectValue {
797    fn type_name() -> &'static str {
798        "object"
799    }
800}
801
802impl From<Py<PyAny>> for ObjectValue {
803    fn from(p: Py<PyAny>) -> Self {
804        Self { inner: p }
805    }
806}
807
808impl<'a, 'py> FromPyObject<'a, 'py> for ObjectValue {
809    type Error = PyErr;
810
811    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
812        Ok(ObjectValue {
813            inner: ob.to_owned().unbind(),
814        })
815    }
816}
817
818#[cfg(feature = "object")]
819impl<'a> From<&'a dyn PolarsObjectSafe> for &'a ObjectValue {
820    fn from(val: &'a dyn PolarsObjectSafe) -> Self {
821        val.as_any().downcast_ref().unwrap()
822    }
823}
824
825impl<'a, 'py> IntoPyObject<'py> for &'a ObjectValue {
826    type Target = PyAny;
827    type Output = Borrowed<'a, 'py, Self::Target>;
828    type Error = std::convert::Infallible;
829
830    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
831        Ok(self.inner.bind_borrowed(py))
832    }
833}
834
835impl Default for ObjectValue {
836    fn default() -> Self {
837        Python::attach(|py| ObjectValue { inner: py.None() })
838    }
839}
840
841impl<'a, 'py, T> FromPyObject<'a, 'py> for Wrap<Vec<T>>
842where
843    T: FromPyObjectOwned<'py>,
844{
845    type Error = PyErr;
846
847    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
848        let seq = ob
849            .cast::<PySequence>()
850            .map_err(<PyErr as From<pyo3::CastError>>::from)?;
851        let mut v = Vec::with_capacity(seq.len().unwrap_or(0));
852        for item in seq.try_iter()? {
853            v.push(item?.extract::<T>().map_err(Into::into)?);
854        }
855        Ok(Wrap(v))
856    }
857}
858
859#[cfg(feature = "asof_join")]
860impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<AsofStrategy> {
861    type Error = PyErr;
862
863    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
864        let parsed = match &*(ob.extract::<PyBackedStr>()?) {
865            "backward" => AsofStrategy::Backward,
866            "forward" => AsofStrategy::Forward,
867            "nearest" => AsofStrategy::Nearest,
868            v => {
869                return Err(PyValueError::new_err(format!(
870                    "asof `strategy` must be one of {{'backward', 'forward', 'nearest'}}, got {v}",
871                )));
872            },
873        };
874        Ok(Wrap(parsed))
875    }
876}
877
878impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<InterpolationMethod> {
879    type Error = PyErr;
880
881    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
882        let parsed = match &*(ob.extract::<PyBackedStr>()?) {
883            "linear" => InterpolationMethod::Linear,
884            "nearest" => InterpolationMethod::Nearest,
885            v => {
886                return Err(PyValueError::new_err(format!(
887                    "interpolation `method` must be one of {{'linear', 'nearest'}}, got {v}",
888                )));
889            },
890        };
891        Ok(Wrap(parsed))
892    }
893}
894
895#[cfg(feature = "avro")]
896impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<Option<AvroCompression>> {
897    type Error = PyErr;
898
899    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
900        let parsed = match &*ob.extract::<PyBackedStr>()? {
901            "uncompressed" => None,
902            "snappy" => Some(AvroCompression::Snappy),
903            "deflate" => Some(AvroCompression::Deflate),
904            v => {
905                return Err(PyValueError::new_err(format!(
906                    "avro `compression` must be one of {{'uncompressed', 'snappy', 'deflate'}}, got {v}",
907                )));
908            },
909        };
910        Ok(Wrap(parsed))
911    }
912}
913
914impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<StartBy> {
915    type Error = PyErr;
916
917    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
918        let parsed = match &*ob.extract::<PyBackedStr>()? {
919            "window" => StartBy::WindowBound,
920            "datapoint" => StartBy::DataPoint,
921            "monday" => StartBy::Monday,
922            "tuesday" => StartBy::Tuesday,
923            "wednesday" => StartBy::Wednesday,
924            "thursday" => StartBy::Thursday,
925            "friday" => StartBy::Friday,
926            "saturday" => StartBy::Saturday,
927            "sunday" => StartBy::Sunday,
928            v => {
929                return Err(PyValueError::new_err(format!(
930                    "`start_by` must be one of {{'window', 'datapoint', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'}}, got {v}",
931                )));
932            },
933        };
934        Ok(Wrap(parsed))
935    }
936}
937
938impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<ClosedWindow> {
939    type Error = PyErr;
940
941    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
942        let parsed = match &*ob.extract::<PyBackedStr>()? {
943            "left" => ClosedWindow::Left,
944            "right" => ClosedWindow::Right,
945            "both" => ClosedWindow::Both,
946            "none" => ClosedWindow::None,
947            v => {
948                return Err(PyValueError::new_err(format!(
949                    "`closed` must be one of {{'left', 'right', 'both', 'none'}}, got {v}",
950                )));
951            },
952        };
953        Ok(Wrap(parsed))
954    }
955}
956
957impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<RoundMode> {
958    type Error = PyErr;
959
960    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
961        let parsed = match &*ob.extract::<PyBackedStr>()? {
962            "half_to_even" => RoundMode::HalfToEven,
963            "half_away_from_zero" => RoundMode::HalfAwayFromZero,
964            v => {
965                return Err(PyValueError::new_err(format!(
966                    "`mode` must be one of {{'half_to_even', 'half_away_from_zero'}}, got {v}",
967                )));
968            },
969        };
970        Ok(Wrap(parsed))
971    }
972}
973
974#[cfg(feature = "csv")]
975impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<CsvEncoding> {
976    type Error = PyErr;
977
978    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
979        let parsed = match &*ob.extract::<PyBackedStr>()? {
980            "utf8" => CsvEncoding::Utf8,
981            "utf8-lossy" => CsvEncoding::LossyUtf8,
982            v => {
983                return Err(PyValueError::new_err(format!(
984                    "csv `encoding` must be one of {{'utf8', 'utf8-lossy'}}, got {v}",
985                )));
986            },
987        };
988        Ok(Wrap(parsed))
989    }
990}
991
992#[cfg(feature = "ipc")]
993impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<Option<IpcCompression>> {
994    type Error = PyErr;
995
996    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
997        let parsed = match &*ob.extract::<PyBackedStr>()? {
998            "uncompressed" => None,
999            "lz4" => Some(IpcCompression::LZ4),
1000            "zstd" => Some(IpcCompression::ZSTD(Default::default())),
1001            v => {
1002                return Err(PyValueError::new_err(format!(
1003                    "ipc `compression` must be one of {{'uncompressed', 'lz4', 'zstd'}}, got {v}",
1004                )));
1005            },
1006        };
1007        Ok(Wrap(parsed))
1008    }
1009}
1010
1011impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<JoinType> {
1012    type Error = PyErr;
1013
1014    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1015        let parsed = match &*ob.extract::<PyBackedStr>()? {
1016            "inner" => JoinType::Inner,
1017            "left" => JoinType::Left,
1018            "right" => JoinType::Right,
1019            "full" => JoinType::Full,
1020            "semi" => JoinType::Semi,
1021            "anti" => JoinType::Anti,
1022            #[cfg(feature = "cross_join")]
1023            "cross" => JoinType::Cross,
1024            v => {
1025                return Err(PyValueError::new_err(format!(
1026                    "`how` must be one of {{'inner', 'left', 'full', 'semi', 'anti', 'cross'}}, got {v}",
1027                )));
1028            },
1029        };
1030        Ok(Wrap(parsed))
1031    }
1032}
1033
1034impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<Label> {
1035    type Error = PyErr;
1036
1037    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1038        let parsed = match &*ob.extract::<PyBackedStr>()? {
1039            "left" => Label::Left,
1040            "right" => Label::Right,
1041            "datapoint" => Label::DataPoint,
1042            v => {
1043                return Err(PyValueError::new_err(format!(
1044                    "`label` must be one of {{'left', 'right', 'datapoint'}}, got {v}",
1045                )));
1046            },
1047        };
1048        Ok(Wrap(parsed))
1049    }
1050}
1051
1052impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<ListToStructWidthStrategy> {
1053    type Error = PyErr;
1054
1055    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1056        let parsed = match &*ob.extract::<PyBackedStr>()? {
1057            "first_non_null" => ListToStructWidthStrategy::FirstNonNull,
1058            "max_width" => ListToStructWidthStrategy::MaxWidth,
1059            v => {
1060                return Err(PyValueError::new_err(format!(
1061                    "`n_field_strategy` must be one of {{'first_non_null', 'max_width'}}, got {v}",
1062                )));
1063            },
1064        };
1065        Ok(Wrap(parsed))
1066    }
1067}
1068
1069impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<NonExistent> {
1070    type Error = PyErr;
1071
1072    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1073        let parsed = match &*ob.extract::<PyBackedStr>()? {
1074            "null" => NonExistent::Null,
1075            "raise" => NonExistent::Raise,
1076            v => {
1077                return Err(PyValueError::new_err(format!(
1078                    "`non_existent` must be one of {{'null', 'raise'}}, got {v}",
1079                )));
1080            },
1081        };
1082        Ok(Wrap(parsed))
1083    }
1084}
1085
1086impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<NullBehavior> {
1087    type Error = PyErr;
1088
1089    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1090        let parsed = match &*ob.extract::<PyBackedStr>()? {
1091            "drop" => NullBehavior::Drop,
1092            "ignore" => NullBehavior::Ignore,
1093            v => {
1094                return Err(PyValueError::new_err(format!(
1095                    "`null_behavior` must be one of {{'drop', 'ignore'}}, got {v}",
1096                )));
1097            },
1098        };
1099        Ok(Wrap(parsed))
1100    }
1101}
1102
1103impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<NullStrategy> {
1104    type Error = PyErr;
1105
1106    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1107        let parsed = match &*ob.extract::<PyBackedStr>()? {
1108            "ignore" => NullStrategy::Ignore,
1109            "propagate" => NullStrategy::Propagate,
1110            v => {
1111                return Err(PyValueError::new_err(format!(
1112                    "`null_strategy` must be one of {{'ignore', 'propagate'}}, got {v}",
1113                )));
1114            },
1115        };
1116        Ok(Wrap(parsed))
1117    }
1118}
1119
1120#[cfg(feature = "parquet")]
1121impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<ParallelStrategy> {
1122    type Error = PyErr;
1123
1124    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1125        let parsed = match &*ob.extract::<PyBackedStr>()? {
1126            "auto" => ParallelStrategy::Auto,
1127            "columns" => ParallelStrategy::Columns,
1128            "row_groups" => ParallelStrategy::RowGroups,
1129            "prefiltered" => ParallelStrategy::Prefiltered,
1130            "none" => ParallelStrategy::None,
1131            v => {
1132                return Err(PyValueError::new_err(format!(
1133                    "`parallel` must be one of {{'auto', 'columns', 'row_groups', 'prefiltered', 'none'}}, got {v}",
1134                )));
1135            },
1136        };
1137        Ok(Wrap(parsed))
1138    }
1139}
1140
1141impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<IndexOrder> {
1142    type Error = PyErr;
1143
1144    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1145        let parsed = match &*ob.extract::<PyBackedStr>()? {
1146            "fortran" => IndexOrder::Fortran,
1147            "c" => IndexOrder::C,
1148            v => {
1149                return Err(PyValueError::new_err(format!(
1150                    "`order` must be one of {{'fortran', 'c'}}, got {v}",
1151                )));
1152            },
1153        };
1154        Ok(Wrap(parsed))
1155    }
1156}
1157
1158impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<QuantileMethod> {
1159    type Error = PyErr;
1160
1161    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1162        let parsed = match &*ob.extract::<PyBackedStr>()? {
1163            "lower" => QuantileMethod::Lower,
1164            "higher" => QuantileMethod::Higher,
1165            "nearest" => QuantileMethod::Nearest,
1166            "linear" => QuantileMethod::Linear,
1167            "midpoint" => QuantileMethod::Midpoint,
1168            "equiprobable" => QuantileMethod::Equiprobable,
1169            v => {
1170                return Err(PyValueError::new_err(format!(
1171                    "`interpolation` must be one of {{'lower', 'higher', 'nearest', 'linear', 'midpoint', 'equiprobable'}}, got {v}",
1172                )));
1173            },
1174        };
1175        Ok(Wrap(parsed))
1176    }
1177}
1178
1179impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<RankMethod> {
1180    type Error = PyErr;
1181
1182    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1183        let parsed = match &*ob.extract::<PyBackedStr>()? {
1184            "min" => RankMethod::Min,
1185            "max" => RankMethod::Max,
1186            "average" => RankMethod::Average,
1187            "dense" => RankMethod::Dense,
1188            "ordinal" => RankMethod::Ordinal,
1189            "random" => RankMethod::Random,
1190            v => {
1191                return Err(PyValueError::new_err(format!(
1192                    "rank `method` must be one of {{'min', 'max', 'average', 'dense', 'ordinal', 'random'}}, got {v}",
1193                )));
1194            },
1195        };
1196        Ok(Wrap(parsed))
1197    }
1198}
1199
1200impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<RollingRankMethod> {
1201    type Error = PyErr;
1202
1203    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1204        let parsed = match &*ob.extract::<PyBackedStr>()? {
1205            "min" => RollingRankMethod::Min,
1206            "max" => RollingRankMethod::Max,
1207            "average" => RollingRankMethod::Average,
1208            "dense" => RollingRankMethod::Dense,
1209            "random" => RollingRankMethod::Random,
1210            v => {
1211                return Err(PyValueError::new_err(format!(
1212                    "rank `method` must be one of {{'min', 'max', 'average', 'dense', 'random'}}, got {v}",
1213                )));
1214            },
1215        };
1216        Ok(Wrap(parsed))
1217    }
1218}
1219
1220impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<Roll> {
1221    type Error = PyErr;
1222
1223    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1224        let parsed = match &*ob.extract::<PyBackedStr>()? {
1225            "raise" => Roll::Raise,
1226            "forward" => Roll::Forward,
1227            "backward" => Roll::Backward,
1228            v => {
1229                return Err(PyValueError::new_err(format!(
1230                    "`roll` must be one of {{'raise', 'forward', 'backward'}}, got {v}",
1231                )));
1232            },
1233        };
1234        Ok(Wrap(parsed))
1235    }
1236}
1237
1238impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<TimeUnit> {
1239    type Error = PyErr;
1240
1241    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1242        let parsed = match &*ob.extract::<PyBackedStr>()? {
1243            "ns" => TimeUnit::Nanoseconds,
1244            "us" => TimeUnit::Microseconds,
1245            "ms" => TimeUnit::Milliseconds,
1246            v => {
1247                return Err(PyValueError::new_err(format!(
1248                    "`time_unit` must be one of {{'ns', 'us', 'ms'}}, got {v}",
1249                )));
1250            },
1251        };
1252        Ok(Wrap(parsed))
1253    }
1254}
1255
1256impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<UniqueKeepStrategy> {
1257    type Error = PyErr;
1258
1259    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1260        let parsed = match &*ob.extract::<PyBackedStr>()? {
1261            "first" => UniqueKeepStrategy::First,
1262            "last" => UniqueKeepStrategy::Last,
1263            "none" => UniqueKeepStrategy::None,
1264            "any" => UniqueKeepStrategy::Any,
1265            v => {
1266                return Err(PyValueError::new_err(format!(
1267                    "`keep` must be one of {{'first', 'last', 'any', 'none'}}, got {v}",
1268                )));
1269            },
1270        };
1271        Ok(Wrap(parsed))
1272    }
1273}
1274
1275#[cfg(feature = "search_sorted")]
1276impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<SearchSortedSide> {
1277    type Error = PyErr;
1278
1279    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1280        let parsed = match &*ob.extract::<PyBackedStr>()? {
1281            "any" => SearchSortedSide::Any,
1282            "left" => SearchSortedSide::Left,
1283            "right" => SearchSortedSide::Right,
1284            v => {
1285                return Err(PyValueError::new_err(format!(
1286                    "sorted `side` must be one of {{'any', 'left', 'right'}}, got {v}",
1287                )));
1288            },
1289        };
1290        Ok(Wrap(parsed))
1291    }
1292}
1293
1294#[cfg(feature = "pivot")]
1295impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<PivotColumnNaming> {
1296    type Error = PyErr;
1297
1298    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1299        let parsed = match &*ob.extract::<PyBackedStr>()? {
1300            "auto" => PivotColumnNaming::Auto,
1301            "combine" => PivotColumnNaming::Combine,
1302            v => {
1303                return Err(PyValueError::new_err(format!(
1304                    "`column_naming` must be one of {{'auto', 'combine'}}, got {v}",
1305                )));
1306            },
1307        };
1308        Ok(Wrap(parsed))
1309    }
1310}
1311
1312impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<ClosedInterval> {
1313    type Error = PyErr;
1314
1315    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1316        let parsed = match &*ob.extract::<PyBackedStr>()? {
1317            "both" => ClosedInterval::Both,
1318            "left" => ClosedInterval::Left,
1319            "right" => ClosedInterval::Right,
1320            "none" => ClosedInterval::None,
1321            v => {
1322                return Err(PyValueError::new_err(format!(
1323                    "`closed` must be one of {{'both', 'left', 'right', 'none'}}, got {v}",
1324                )));
1325            },
1326        };
1327        Ok(Wrap(parsed))
1328    }
1329}
1330
1331impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<WindowMapping> {
1332    type Error = PyErr;
1333
1334    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1335        let parsed = match &*ob.extract::<PyBackedStr>()? {
1336            "group_to_rows" => WindowMapping::GroupsToRows,
1337            "join" => WindowMapping::Join,
1338            "explode" => WindowMapping::Explode,
1339            v => {
1340                return Err(PyValueError::new_err(format!(
1341                    "`mapping_strategy` must be one of {{'group_to_rows', 'join', 'explode'}}, got {v}",
1342                )));
1343            },
1344        };
1345        Ok(Wrap(parsed))
1346    }
1347}
1348
1349impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<JoinValidation> {
1350    type Error = PyErr;
1351
1352    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1353        let parsed = match &*ob.extract::<PyBackedStr>()? {
1354            "1:1" => JoinValidation::OneToOne,
1355            "1:m" => JoinValidation::OneToMany,
1356            "m:m" => JoinValidation::ManyToMany,
1357            "m:1" => JoinValidation::ManyToOne,
1358            v => {
1359                return Err(PyValueError::new_err(format!(
1360                    "`validate` must be one of {{'m:m', 'm:1', '1:m', '1:1'}}, got {v}",
1361                )));
1362            },
1363        };
1364        Ok(Wrap(parsed))
1365    }
1366}
1367
1368impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<MaintainOrderJoin> {
1369    type Error = PyErr;
1370
1371    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1372        let parsed = match &*ob.extract::<PyBackedStr>()? {
1373            "none" => MaintainOrderJoin::None,
1374            "left" => MaintainOrderJoin::Left,
1375            "right" => MaintainOrderJoin::Right,
1376            "left_right" => MaintainOrderJoin::LeftRight,
1377            "right_left" => MaintainOrderJoin::RightLeft,
1378            v => {
1379                return Err(PyValueError::new_err(format!(
1380                    "`maintain_order` must be one of {{'none', 'left', 'right', 'left_right', 'right_left'}}, got {v}",
1381                )));
1382            },
1383        };
1384        Ok(Wrap(parsed))
1385    }
1386}
1387
1388impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<Option<JoinBuildSide>> {
1389    type Error = PyErr;
1390
1391    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1392        let parsed = match &*ob.extract::<PyBackedStr>()? {
1393            "auto" => None,
1394            "prefer_left" => Some(JoinBuildSide::PreferLeft),
1395            "prefer_right" => Some(JoinBuildSide::PreferRight),
1396            "force_left" => Some(JoinBuildSide::ForceLeft),
1397            "force_right" => Some(JoinBuildSide::ForceRight),
1398            v => {
1399                return Err(PyValueError::new_err(format!(
1400                    "`build_side` must be one of {{'auto', 'prefer_left', 'prefer_right', 'force_left', 'force_right'}}, got {v}",
1401                )));
1402            },
1403        };
1404        Ok(Wrap(parsed))
1405    }
1406}
1407
1408#[cfg(feature = "csv")]
1409impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<QuoteStyle> {
1410    type Error = PyErr;
1411
1412    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1413        let parsed = match &*ob.extract::<PyBackedStr>()? {
1414            "always" => QuoteStyle::Always,
1415            "necessary" => QuoteStyle::Necessary,
1416            "non_numeric" => QuoteStyle::NonNumeric,
1417            "never" => QuoteStyle::Never,
1418            v => {
1419                return Err(PyValueError::new_err(format!(
1420                    "`quote_style` must be one of {{'always', 'necessary', 'non_numeric', 'never'}}, got {v}",
1421                )));
1422            },
1423        };
1424        Ok(Wrap(parsed))
1425    }
1426}
1427
1428#[cfg(feature = "list_sets")]
1429impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<SetOperation> {
1430    type Error = PyErr;
1431
1432    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1433        let parsed = match &*ob.extract::<PyBackedStr>()? {
1434            "union" => SetOperation::Union,
1435            "difference" => SetOperation::Difference,
1436            "intersection" => SetOperation::Intersection,
1437            "symmetric_difference" => SetOperation::SymmetricDifference,
1438            v => {
1439                return Err(PyValueError::new_err(format!(
1440                    "set operation must be one of {{'union', 'difference', 'intersection', 'symmetric_difference'}}, got {v}",
1441                )));
1442            },
1443        };
1444        Ok(Wrap(parsed))
1445    }
1446}
1447
1448// Conversion from ScanCastOptions class from the Python side.
1449impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<CastColumnsPolicy> {
1450    type Error = PyErr;
1451
1452    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1453        if ob.is_none() {
1454            // Initialize the default ScanCastOptions from Python.
1455            static DEFAULT: PyOnceLock<Wrap<CastColumnsPolicy>> = PyOnceLock::new();
1456
1457            let out = DEFAULT.get_or_try_init(ob.py(), || {
1458                let ob = PyModule::import(ob.py(), "polars.io.scan_options.cast_options")
1459                    .unwrap()
1460                    .getattr("ScanCastOptions")
1461                    .unwrap()
1462                    .call_method0("_default")
1463                    .unwrap();
1464
1465                let out = Self::extract(ob.as_borrowed())?;
1466
1467                // The default policy should match ERROR_ON_MISMATCH (but this can change).
1468                debug_assert_eq!(&out.0, &CastColumnsPolicy::ERROR_ON_MISMATCH);
1469
1470                PyResult::Ok(out)
1471            })?;
1472
1473            return Ok(out.clone());
1474        }
1475
1476        let py = ob.py();
1477
1478        let mut integer_upcast = false;
1479        let mut integer_to_float_cast = false;
1480
1481        let integer_cast_object = ob.getattr(intern!(py, "integer_cast"))?;
1482
1483        parse_multiple_options("integer_cast", integer_cast_object, |v| {
1484            match v {
1485                "upcast" => integer_upcast = true,
1486                "allow-float" => integer_to_float_cast = true,
1487                "forbid" => {},
1488                v => {
1489                    return Err(PyValueError::new_err(format!(
1490                        "unknown option for integer_cast: {v}"
1491                    )));
1492                },
1493            }
1494
1495            Ok(())
1496        })?;
1497
1498        let mut float_upcast = false;
1499        let mut float_downcast = false;
1500
1501        let float_cast_object = ob.getattr(intern!(py, "float_cast"))?;
1502
1503        parse_multiple_options("float_cast", float_cast_object, |v| {
1504            match v {
1505                "upcast" => float_upcast = true,
1506                "downcast" => float_downcast = true,
1507                "forbid" => {},
1508                v => {
1509                    return Err(PyValueError::new_err(format!(
1510                        "unknown option for float_cast: {v}"
1511                    )));
1512                },
1513            }
1514
1515            Ok(())
1516        })?;
1517
1518        let mut datetime_nanoseconds_downcast = false;
1519        let mut datetime_microseconds_downcast = false;
1520        let mut datetime_milliseconds_upcast = false;
1521        let mut datetime_microseconds_upcast = false;
1522        let mut datetime_convert_timezone = false;
1523
1524        let datetime_cast_object = ob.getattr(intern!(py, "datetime_cast"))?;
1525
1526        parse_multiple_options("datetime_cast", datetime_cast_object, |v| {
1527            match v {
1528                "forbid" => {},
1529                "nanosecond-downcast" => datetime_nanoseconds_downcast = true,
1530                "microsecond-downcast" => datetime_microseconds_downcast = true,
1531                "millisecond-upcast" => datetime_milliseconds_upcast = true,
1532                "microsecond-upcast" => datetime_microseconds_upcast = true,
1533                "downcast" => {
1534                    datetime_nanoseconds_downcast = true;
1535                    datetime_microseconds_downcast = true;
1536                },
1537                "upcast" => {
1538                    datetime_milliseconds_upcast = true;
1539                    datetime_microseconds_upcast = true;
1540                },
1541                "convert-timezone" => datetime_convert_timezone = true,
1542                v => {
1543                    return Err(PyValueError::new_err(format!(
1544                        "unknown option for datetime_cast: {v}"
1545                    )));
1546                },
1547            };
1548
1549            Ok(())
1550        })?;
1551
1552        let missing_struct_fields = match &*ob
1553            .getattr(intern!(py, "missing_struct_fields"))?
1554            .extract::<PyBackedStr>()?
1555        {
1556            "insert" => MissingColumnsPolicy::Insert,
1557            "raise" => MissingColumnsPolicy::Raise,
1558            v => {
1559                return Err(PyValueError::new_err(format!(
1560                    "unknown option for missing_struct_fields: {v}"
1561                )));
1562            },
1563        };
1564
1565        let extra_struct_fields = match &*ob
1566            .getattr(intern!(py, "extra_struct_fields"))?
1567            .extract::<PyBackedStr>()?
1568        {
1569            "ignore" => ExtraColumnsPolicy::Ignore,
1570            "raise" => ExtraColumnsPolicy::Raise,
1571            v => {
1572                return Err(PyValueError::new_err(format!(
1573                    "unknown option for extra_struct_fields: {v}"
1574                )));
1575            },
1576        };
1577
1578        let categorical_to_string = match &*ob
1579            .getattr(intern!(py, "categorical_to_string"))?
1580            .extract::<PyBackedStr>()?
1581        {
1582            "allow" => true,
1583            "forbid" => false,
1584            v => {
1585                return Err(PyValueError::new_err(format!(
1586                    "unknown option for categorical_to_string: {v}"
1587                )));
1588            },
1589        };
1590
1591        return Ok(Wrap(CastColumnsPolicy {
1592            integer_upcast,
1593            integer_to_float_cast,
1594            float_upcast,
1595            float_downcast,
1596            datetime_nanoseconds_downcast,
1597            datetime_microseconds_downcast,
1598            datetime_milliseconds_upcast,
1599            datetime_microseconds_upcast,
1600            datetime_convert_timezone,
1601            null_upcast: true,
1602            categorical_to_string,
1603            missing_struct_fields,
1604            extra_struct_fields,
1605        }));
1606
1607        fn parse_multiple_options(
1608            parameter_name: &'static str,
1609            py_object: Bound<'_, PyAny>,
1610            mut parser_func: impl FnMut(&str) -> PyResult<()>,
1611        ) -> PyResult<()> {
1612            if let Ok(v) = py_object.extract::<PyBackedStr>() {
1613                parser_func(&v)?;
1614            } else if let Ok(v) = py_object.try_iter() {
1615                for v in v {
1616                    parser_func(&v?.extract::<PyBackedStr>()?)?;
1617                }
1618            } else {
1619                return Err(PyValueError::new_err(format!(
1620                    "unknown type for {parameter_name}: {py_object}"
1621                )));
1622            }
1623
1624            Ok(())
1625        }
1626    }
1627}
1628
1629pub(crate) fn parse_fill_null_strategy(
1630    strategy: &str,
1631    limit: FillNullLimit,
1632) -> PyResult<FillNullStrategy> {
1633    let parsed = match strategy {
1634        "forward" => FillNullStrategy::Forward(limit),
1635        "backward" => FillNullStrategy::Backward(limit),
1636        "min" => FillNullStrategy::Min,
1637        "max" => FillNullStrategy::Max,
1638        "mean" => FillNullStrategy::Mean,
1639        "zero" => FillNullStrategy::Zero,
1640        "one" => FillNullStrategy::One,
1641        e => {
1642            return Err(PyValueError::new_err(format!(
1643                "`strategy` must be one of {{'forward', 'backward', 'min', 'max', 'mean', 'zero', 'one'}}, got {e}",
1644            )));
1645        },
1646    };
1647    Ok(parsed)
1648}
1649
1650#[cfg(feature = "parquet")]
1651pub(crate) fn parse_parquet_compression(
1652    compression: &str,
1653    compression_level: Option<i32>,
1654) -> PyResult<ParquetCompression> {
1655    let parsed = match compression {
1656        "uncompressed" => ParquetCompression::Uncompressed,
1657        "snappy" => ParquetCompression::Snappy,
1658        "gzip" => ParquetCompression::Gzip(
1659            compression_level
1660                .map(|lvl| {
1661                    GzipLevel::try_new(lvl as u8)
1662                        .map_err(|e| PyValueError::new_err(format!("{e:?}")))
1663                })
1664                .transpose()?,
1665        ),
1666        "brotli" => ParquetCompression::Brotli(
1667            compression_level
1668                .map(|lvl| {
1669                    BrotliLevel::try_new(lvl as u32)
1670                        .map_err(|e| PyValueError::new_err(format!("{e:?}")))
1671                })
1672                .transpose()?,
1673        ),
1674        "lz4" => ParquetCompression::Lz4Raw,
1675        "zstd" => ParquetCompression::Zstd(
1676            compression_level
1677                .map(|lvl| {
1678                    ZstdLevel::try_new(lvl).map_err(|e| PyValueError::new_err(format!("{e:?}")))
1679                })
1680                .transpose()?,
1681        ),
1682        e => {
1683            return Err(PyValueError::new_err(format!(
1684                "parquet `compression` must be one of {{'uncompressed', 'snappy', 'gzip', 'brotli', 'lz4', 'zstd'}}, got {e}",
1685            )));
1686        },
1687    };
1688    Ok(parsed)
1689}
1690
1691pub(crate) fn strings_to_pl_smallstr<I, S>(container: I) -> Vec<PlSmallStr>
1692where
1693    I: IntoIterator<Item = S>,
1694    S: AsRef<str>,
1695{
1696    container
1697        .into_iter()
1698        .map(|s| PlSmallStr::from_str(s.as_ref()))
1699        .collect()
1700}
1701
1702#[derive(Debug, Copy, Clone)]
1703pub struct PyCompatLevel(pub CompatLevel);
1704
1705impl<'a, 'py> FromPyObject<'a, 'py> for PyCompatLevel {
1706    type Error = PyErr;
1707
1708    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1709        Ok(PyCompatLevel(if let Ok(level) = ob.extract::<u16>() {
1710            if let Ok(compat_level) = CompatLevel::with_level(level) {
1711                compat_level
1712            } else {
1713                return Err(PyValueError::new_err("invalid compat level"));
1714            }
1715        } else if let Ok(future) = ob.extract::<bool>() {
1716            if future {
1717                CompatLevel::newest()
1718            } else {
1719                CompatLevel::oldest()
1720            }
1721        } else {
1722            return Err(PyTypeError::new_err(
1723                "'compat_level' argument accepts int or bool",
1724            ));
1725        }))
1726    }
1727}
1728
1729#[cfg(feature = "string_normalize")]
1730impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<UnicodeForm> {
1731    type Error = PyErr;
1732
1733    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1734        let parsed = match &*ob.extract::<PyBackedStr>()? {
1735            "NFC" => UnicodeForm::NFC,
1736            "NFKC" => UnicodeForm::NFKC,
1737            "NFD" => UnicodeForm::NFD,
1738            "NFKD" => UnicodeForm::NFKD,
1739            v => {
1740                return Err(PyValueError::new_err(format!(
1741                    "`form` must be one of {{'NFC', 'NFKC', 'NFD', 'NFKD'}}, got {v}",
1742                )));
1743            },
1744        };
1745        Ok(Wrap(parsed))
1746    }
1747}
1748
1749#[cfg(feature = "parquet")]
1750impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<Option<KeyValueMetadata>> {
1751    type Error = PyErr;
1752
1753    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1754        #[derive(FromPyObject)]
1755        enum Metadata {
1756            Static(Vec<(String, String)>),
1757            Dynamic(Py<PyAny>),
1758        }
1759
1760        let metadata = Option::<Metadata>::extract(ob)?;
1761        let key_value_metadata = metadata.map(|x| match x {
1762            Metadata::Static(kv) => KeyValueMetadata::from_static(kv),
1763            Metadata::Dynamic(func) => KeyValueMetadata::from_py_function(func),
1764        });
1765        Ok(Wrap(key_value_metadata))
1766    }
1767}
1768
1769impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<Option<TimeZone>> {
1770    type Error = PyErr;
1771
1772    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1773        let tz = Option::<Wrap<PlSmallStr>>::extract(ob)?;
1774
1775        let tz = tz.map(|x| x.0);
1776
1777        Ok(Wrap(TimeZone::opt_try_new(tz).map_err(to_py_err)?))
1778    }
1779}
1780
1781impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<UpcastOrForbid> {
1782    type Error = PyErr;
1783
1784    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1785        let parsed = match &*ob.extract::<PyBackedStr>()? {
1786            "upcast" => UpcastOrForbid::Upcast,
1787            "forbid" => UpcastOrForbid::Forbid,
1788            v => {
1789                return Err(PyValueError::new_err(format!(
1790                    "cast parameter must be one of {{'upcast', 'forbid'}}, got {v}",
1791                )));
1792            },
1793        };
1794        Ok(Wrap(parsed))
1795    }
1796}
1797
1798impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<ExtraColumnsPolicy> {
1799    type Error = PyErr;
1800
1801    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1802        let parsed = match &*ob.extract::<PyBackedStr>()? {
1803            "ignore" => ExtraColumnsPolicy::Ignore,
1804            "raise" => ExtraColumnsPolicy::Raise,
1805            v => {
1806                return Err(PyValueError::new_err(format!(
1807                    "extra column/field parameter must be one of {{'ignore', 'raise'}}, got {v}",
1808                )));
1809            },
1810        };
1811        Ok(Wrap(parsed))
1812    }
1813}
1814
1815impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<MissingColumnsPolicy> {
1816    type Error = PyErr;
1817
1818    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1819        let parsed = match &*ob.extract::<PyBackedStr>()? {
1820            "insert" => MissingColumnsPolicy::Insert,
1821            "raise" => MissingColumnsPolicy::Raise,
1822            v => {
1823                return Err(PyValueError::new_err(format!(
1824                    "missing column/field parameter must be one of {{'insert', 'raise'}}, got {v}",
1825                )));
1826            },
1827        };
1828        Ok(Wrap(parsed))
1829    }
1830}
1831
1832impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<MissingColumnsPolicyOrExpr> {
1833    type Error = PyErr;
1834
1835    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1836        if let Ok(pyexpr) = ob.extract::<PyExpr>() {
1837            return Ok(Wrap(MissingColumnsPolicyOrExpr::InsertWith(pyexpr.inner)));
1838        }
1839
1840        let parsed = match &*ob.extract::<PyBackedStr>()? {
1841            "insert" => MissingColumnsPolicyOrExpr::Insert,
1842            "raise" => MissingColumnsPolicyOrExpr::Raise,
1843            v => {
1844                return Err(PyValueError::new_err(format!(
1845                    "missing column/field parameter must be one of {{'insert', 'raise', expression}}, got {v}",
1846                )));
1847            },
1848        };
1849        Ok(Wrap(parsed))
1850    }
1851}
1852
1853impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<ColumnMapping> {
1854    type Error = PyErr;
1855
1856    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1857        let (column_mapping_type, ob): (PyBackedStr, Bound<'_, PyAny>) = ob.extract()?;
1858
1859        Ok(Wrap(match &*column_mapping_type {
1860            "iceberg-column-mapping" => {
1861                let arrow_schema: Wrap<ArrowSchema> = ob.extract()?;
1862                ColumnMapping::Iceberg(Arc::new(
1863                    IcebergSchema::from_arrow_schema(&arrow_schema.0).map_err(to_py_err)?,
1864                ))
1865            },
1866
1867            v => {
1868                return Err(PyValueError::new_err(format!(
1869                    "unknown column mapping type: {v}"
1870                )));
1871            },
1872        }))
1873    }
1874}
1875
1876impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<DeletionFilesList> {
1877    type Error = PyErr;
1878
1879    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1880        let (deletion_file_type, ob): (PyBackedStr, Bound<'_, PyAny>) = ob.extract()?;
1881
1882        Ok(Wrap(match &*deletion_file_type {
1883            "iceberg-position-delete" => {
1884                let dict: Bound<'_, PyDict> = ob.extract()?;
1885
1886                let mut out = PlIndexMap::new();
1887
1888                for (k, v) in dict
1889                    .try_iter()?
1890                    .zip(dict.call_method0("values")?.try_iter()?)
1891                {
1892                    let k: usize = k?.extract()?;
1893                    let v: Bound<'_, PyAny> = v?.extract()?;
1894
1895                    let files = v
1896                        .try_iter()?
1897                        .map(|x| {
1898                            x.and_then(|x| {
1899                                let x: String = x.extract()?;
1900                                Ok(x)
1901                            })
1902                        })
1903                        .collect::<PyResult<Arc<[String]>>>()?;
1904
1905                    if !files.is_empty() {
1906                        out.insert(k, files);
1907                    }
1908                }
1909
1910                DeletionFilesList::IcebergPositionDelete(Arc::new(out))
1911            },
1912
1913            "delta-deletion-vector" => {
1914                let callback: Py<PyAny> = ob.extract()?;
1915                DeletionFilesList::Delta(DeltaDeletionVectorProvider::new(PythonObject(callback)))
1916            },
1917
1918            v => {
1919                return Err(PyValueError::new_err(format!(
1920                    "unknown deletion file type: {v}"
1921                )));
1922            },
1923        }))
1924    }
1925}
1926
1927impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<DefaultFieldValues> {
1928    type Error = PyErr;
1929
1930    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1931        let (default_values_type, ob): (PyBackedStr, Bound<'_, PyAny>) = ob.extract()?;
1932
1933        Ok(Wrap(match &*default_values_type {
1934            "iceberg" => {
1935                let (identity_transformed_partition_values, initial_defaults): (
1936                    Bound<'_, PyDict>,
1937                    Bound<'_, PyDict>,
1938                ) = ob.extract()?;
1939
1940                let mut converted_identity_transformed_partition_values = PlIndexMap::new();
1941                let mut converted_initial_defaults = PlIndexMap::new();
1942
1943                for (k, v) in identity_transformed_partition_values.try_iter()?.zip(
1944                    identity_transformed_partition_values
1945                        .call_method0("values")?
1946                        .try_iter()?,
1947                ) {
1948                    let k: u32 = k?.extract()?;
1949                    let v = v?;
1950
1951                    let v: Result<Column, String> = if let Ok(s) = get_series(&v) {
1952                        Ok(s.into_column())
1953                    } else {
1954                        let err_msg: String = v.extract()?;
1955                        Err(err_msg)
1956                    };
1957
1958                    converted_identity_transformed_partition_values.insert(k, v);
1959                }
1960
1961                for (k, v) in initial_defaults
1962                    .try_iter()?
1963                    .zip(initial_defaults.call_method0("values")?.try_iter()?)
1964                {
1965                    let k: u32 = k?.extract()?;
1966                    let v = get_series(&v?)?;
1967                    let v = Scalar::new(
1968                        v.dtype().clone(),
1969                        v.get(0).map_err(to_py_err)?.into_static(),
1970                    );
1971                    converted_initial_defaults.insert(k, v);
1972                }
1973
1974                DefaultFieldValues::Iceberg(Arc::new(IcebergDefaultFieldValues {
1975                    identity_transformed_partition_fields: PlIndexMapHashable(
1976                        converted_identity_transformed_partition_values,
1977                    ),
1978                    initial_defaults: PlIndexMapHashable(converted_initial_defaults),
1979                }))
1980            },
1981
1982            v => {
1983                return Err(PyValueError::new_err(format!(
1984                    "unknown deletion file type: {v}"
1985                )));
1986            },
1987        }))
1988    }
1989}
1990
1991impl<'a, 'py> FromPyObject<'a, 'py> for Wrap<PlRefPath> {
1992    type Error = PyErr;
1993
1994    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
1995        if let Ok(path) = ob.extract::<PyBackedStr>() {
1996            Ok(Wrap(PlRefPath::new(&*path)))
1997        } else if let Ok(path) = ob.extract::<std::path::PathBuf>() {
1998            Ok(Wrap(PlRefPath::try_from_path(&path).map_err(to_py_err)?))
1999        } else {
2000            Err(PyTypeError::new_err(format!(
2001                "PlRefPath cannot be formed from '{}'",
2002                ob.get_type()
2003            ))
2004            .into())
2005        }
2006    }
2007}
2008
2009impl<'py> IntoPyObject<'py> for Wrap<PlRefPath> {
2010    type Target = PyString;
2011    type Output = Bound<'py, Self::Target>;
2012    type Error = Infallible;
2013
2014    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
2015        self.0.as_str().into_pyobject(py)
2016    }
2017}
2018
2019impl<'a, 'py, T> FromPyObject<'a, 'py> for Wrap<Buffer<T>>
2020where
2021    Vec<T>: FromPyObject<'a, 'py>,
2022{
2023    type Error = <Vec<T> as FromPyObject<'a, 'py>>::Error;
2024
2025    fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
2026        Vec::<T>::extract(obj).map(Buffer::from_vec).map(Wrap)
2027    }
2028}
2029
2030impl<'py, T> IntoPyObject<'py> for Wrap<Buffer<T>>
2031where
2032    T: IntoPyObject<'py> + Clone,
2033{
2034    type Target = PyList;
2035    type Output = Bound<'py, Self::Target>;
2036    type Error = PyErr;
2037
2038    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
2039        PyList::new(py, self.0.iter().cloned())
2040    }
2041}