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