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
60pub(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
111pub(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 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 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
1448impl<'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 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 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}