1pub(crate) mod any_value;
2pub(crate) mod chunked_array;
3mod datetime;
4
5use std::convert::Infallible;
6use std::fmt::{Display, Formatter};
7use std::fs::File;
8use std::hash::{Hash, Hasher};
9use std::path::PathBuf;
10
11#[cfg(feature = "object")]
12use polars::chunked_array::object::PolarsObjectSafe;
13use polars::frame::row::Row;
14#[cfg(feature = "avro")]
15use polars::io::avro::AvroCompression;
16#[cfg(feature = "cloud")]
17use polars::io::cloud::CloudOptions;
18use polars::series::ops::NullBehavior;
19use polars_core::utils::arrow::array::Array;
20use polars_core::utils::arrow::types::NativeType;
21use polars_core::utils::materialize_dyn_int;
22use polars_lazy::prelude::*;
23#[cfg(feature = "parquet")]
24use polars_parquet::write::StatisticsOptions;
25use polars_plan::dsl::ScanSources;
26use polars_utils::mmap::MemSlice;
27use polars_utils::pl_str::PlSmallStr;
28use polars_utils::total_ord::{TotalEq, TotalHash};
29use pyo3::basic::CompareOp;
30use pyo3::exceptions::{PyTypeError, PyValueError};
31use pyo3::intern;
32use pyo3::prelude::*;
33use pyo3::pybacked::PyBackedStr;
34use pyo3::sync::GILOnceCell;
35use pyo3::types::{PyDict, PyList, PySequence, PyString};
36
37use crate::error::PyPolarsErr;
38use crate::expr::PyExpr;
39use crate::file::{PythonScanSourceInput, get_python_scan_source_input};
40#[cfg(feature = "object")]
41use crate::object::OBJECT_NAME;
42use crate::prelude::*;
43use crate::py_modules::{pl_series, polars};
44use crate::series::PySeries;
45use crate::utils::to_py_err;
46use crate::{PyDataFrame, PyLazyFrame};
47
48pub(crate) unsafe trait Transparent {
51 type Target;
52}
53
54unsafe impl Transparent for PySeries {
55 type Target = Series;
56}
57
58unsafe impl<T> Transparent for Wrap<T> {
59 type Target = T;
60}
61
62unsafe impl<T: Transparent> Transparent for Option<T> {
63 type Target = Option<T::Target>;
64}
65
66pub(crate) fn reinterpret_vec<T: Transparent>(input: Vec<T>) -> Vec<T::Target> {
67 assert_eq!(size_of::<T>(), size_of::<T::Target>());
68 assert_eq!(align_of::<T>(), align_of::<T::Target>());
69 let len = input.len();
70 let cap = input.capacity();
71 let mut manual_drop_vec = std::mem::ManuallyDrop::new(input);
72 let vec_ptr: *mut T = manual_drop_vec.as_mut_ptr();
73 let ptr: *mut T::Target = vec_ptr as *mut T::Target;
74 unsafe { Vec::from_raw_parts(ptr, len, cap) }
75}
76
77pub(crate) fn vec_extract_wrapped<T>(buf: Vec<Wrap<T>>) -> Vec<T> {
78 reinterpret_vec(buf)
79}
80
81#[derive(PartialEq, Eq, Hash)]
82#[repr(transparent)]
83pub struct Wrap<T>(pub T);
84
85impl<T> Clone for Wrap<T>
86where
87 T: Clone,
88{
89 fn clone(&self) -> Self {
90 Wrap(self.0.clone())
91 }
92}
93impl<T> From<T> for Wrap<T> {
94 fn from(t: T) -> Self {
95 Wrap(t)
96 }
97}
98
99pub(crate) fn get_df(obj: &Bound<'_, PyAny>) -> PyResult<DataFrame> {
101 let pydf = obj.getattr(intern!(obj.py(), "_df"))?;
102 Ok(pydf.extract::<PyDataFrame>()?.df)
103}
104
105pub(crate) fn get_lf(obj: &Bound<'_, PyAny>) -> PyResult<LazyFrame> {
106 let pydf = obj.getattr(intern!(obj.py(), "_ldf"))?;
107 Ok(pydf.extract::<PyLazyFrame>()?.ldf)
108}
109
110pub(crate) fn get_series(obj: &Bound<'_, PyAny>) -> PyResult<Series> {
111 let s = obj.getattr(intern!(obj.py(), "_s"))?;
112 Ok(s.extract::<PySeries>()?.series)
113}
114
115pub(crate) fn to_series(py: Python<'_>, s: PySeries) -> PyResult<Bound<PyAny>> {
116 let series = pl_series(py).bind(py);
117 let constructor = series.getattr(intern!(py, "_from_pyseries"))?;
118 constructor.call1((s,))
119}
120
121impl<'py> FromPyObject<'py> for Wrap<PlSmallStr> {
122 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
123 Ok(Wrap((&*ob.extract::<PyBackedStr>()?).into()))
124 }
125}
126
127#[cfg(feature = "csv")]
128impl<'py> FromPyObject<'py> for Wrap<NullValues> {
129 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
130 if let Ok(s) = ob.extract::<PyBackedStr>() {
131 Ok(Wrap(NullValues::AllColumnsSingle((&*s).into())))
132 } else if let Ok(s) = ob.extract::<Vec<PyBackedStr>>() {
133 Ok(Wrap(NullValues::AllColumns(
134 s.into_iter().map(|x| (&*x).into()).collect(),
135 )))
136 } else if let Ok(s) = ob.extract::<Vec<(PyBackedStr, PyBackedStr)>>() {
137 Ok(Wrap(NullValues::Named(
138 s.into_iter()
139 .map(|(a, b)| ((&*a).into(), (&*b).into()))
140 .collect(),
141 )))
142 } else {
143 Err(
144 PyPolarsErr::Other("could not extract value from null_values argument".into())
145 .into(),
146 )
147 }
148 }
149}
150
151fn struct_dict<'a, 'py>(
152 py: Python<'py>,
153 vals: impl Iterator<Item = AnyValue<'a>>,
154 flds: &[Field],
155) -> PyResult<Bound<'py, PyDict>> {
156 let dict = PyDict::new(py);
157 flds.iter().zip(vals).try_for_each(|(fld, val)| {
158 dict.set_item(fld.name().as_str(), Wrap(val).into_pyobject(py)?)
159 })?;
160 Ok(dict)
161}
162
163fn decimal_to_digits(v: i128, buf: &mut [u128; 3]) -> usize {
165 const ZEROS: i128 = 0x3030_3030_3030_3030_3030_3030_3030_3030;
166 let buf = unsafe { std::mem::transmute::<&mut [u128; 3], &mut [u8; 48]>(buf) };
169 let mut buffer = itoa::Buffer::new();
170 let value = buffer.format(v);
171 let len = value.len();
172 for (dst, src) in buf.iter_mut().zip(value.as_bytes().iter()) {
173 *dst = *src
174 }
175
176 let ptr = buf.as_mut_ptr() as *mut i128;
177 unsafe {
178 *ptr -= ZEROS;
180 *ptr.add(1) -= ZEROS;
181 *ptr.add(2) -= ZEROS;
182 }
183 len
184}
185
186impl<'py> IntoPyObject<'py> for &Wrap<DataType> {
187 type Target = PyAny;
188 type Output = Bound<'py, Self::Target>;
189 type Error = PyErr;
190
191 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
192 let pl = polars(py).bind(py);
193
194 match &self.0 {
195 DataType::Int8 => {
196 let class = pl.getattr(intern!(py, "Int8"))?;
197 class.call0()
198 },
199 DataType::Int16 => {
200 let class = pl.getattr(intern!(py, "Int16"))?;
201 class.call0()
202 },
203 DataType::Int32 => {
204 let class = pl.getattr(intern!(py, "Int32"))?;
205 class.call0()
206 },
207 DataType::Int64 => {
208 let class = pl.getattr(intern!(py, "Int64"))?;
209 class.call0()
210 },
211 DataType::UInt8 => {
212 let class = pl.getattr(intern!(py, "UInt8"))?;
213 class.call0()
214 },
215 DataType::UInt16 => {
216 let class = pl.getattr(intern!(py, "UInt16"))?;
217 class.call0()
218 },
219 DataType::UInt32 => {
220 let class = pl.getattr(intern!(py, "UInt32"))?;
221 class.call0()
222 },
223 DataType::UInt64 => {
224 let class = pl.getattr(intern!(py, "UInt64"))?;
225 class.call0()
226 },
227 DataType::Int128 => {
228 let class = pl.getattr(intern!(py, "Int128"))?;
229 class.call0()
230 },
231 DataType::Float32 => {
232 let class = pl.getattr(intern!(py, "Float32"))?;
233 class.call0()
234 },
235 DataType::Float64 | DataType::Unknown(UnknownKind::Float) => {
236 let class = pl.getattr(intern!(py, "Float64"))?;
237 class.call0()
238 },
239 DataType::Decimal(precision, scale) => {
240 let class = pl.getattr(intern!(py, "Decimal"))?;
241 let args = (*precision, *scale);
242 class.call1(args)
243 },
244 DataType::Boolean => {
245 let class = pl.getattr(intern!(py, "Boolean"))?;
246 class.call0()
247 },
248 DataType::String | DataType::Unknown(UnknownKind::Str) => {
249 let class = pl.getattr(intern!(py, "String"))?;
250 class.call0()
251 },
252 DataType::Binary => {
253 let class = pl.getattr(intern!(py, "Binary"))?;
254 class.call0()
255 },
256 DataType::Array(inner, size) => {
257 let class = pl.getattr(intern!(py, "Array"))?;
258 let inner = Wrap(*inner.clone());
259 let args = (&inner, *size);
260 class.call1(args)
261 },
262 DataType::List(inner) => {
263 let class = pl.getattr(intern!(py, "List"))?;
264 let inner = Wrap(*inner.clone());
265 class.call1((&inner,))
266 },
267 DataType::Date => {
268 let class = pl.getattr(intern!(py, "Date"))?;
269 class.call0()
270 },
271 DataType::Datetime(tu, tz) => {
272 let datetime_class = pl.getattr(intern!(py, "Datetime"))?;
273 datetime_class.call1((tu.to_ascii(), tz.as_deref().map(|x| x.as_str())))
274 },
275 DataType::Duration(tu) => {
276 let duration_class = pl.getattr(intern!(py, "Duration"))?;
277 duration_class.call1((tu.to_ascii(),))
278 },
279 #[cfg(feature = "object")]
280 DataType::Object(_) => {
281 let class = pl.getattr(intern!(py, "Object"))?;
282 class.call0()
283 },
284 DataType::Categorical(_, ordering) => {
285 let class = pl.getattr(intern!(py, "Categorical"))?;
286 class.call1((Wrap(*ordering),))
287 },
288 DataType::Enum(rev_map, _) => {
289 let categories = rev_map.as_ref().unwrap().get_categories();
291 let class = pl.getattr(intern!(py, "Enum"))?;
292 let s =
293 Series::from_arrow(PlSmallStr::from_static("category"), categories.to_boxed())
294 .map_err(PyPolarsErr::from)?;
295 let series = to_series(py, s.into())?;
296 class.call1((series,))
297 },
298 DataType::Time => pl.getattr(intern!(py, "Time")),
299 DataType::Struct(fields) => {
300 let field_class = pl.getattr(intern!(py, "Field"))?;
301 let iter = fields.iter().map(|fld| {
302 let name = fld.name().as_str();
303 let dtype = Wrap(fld.dtype().clone());
304 field_class.call1((name, &dtype)).unwrap()
305 });
306 let fields = PyList::new(py, iter)?;
307 let struct_class = pl.getattr(intern!(py, "Struct"))?;
308 struct_class.call1((fields,))
309 },
310 DataType::Null => {
311 let class = pl.getattr(intern!(py, "Null"))?;
312 class.call0()
313 },
314 DataType::Unknown(UnknownKind::Int(v)) => {
315 Wrap(materialize_dyn_int(*v).dtype()).into_pyobject(py)
316 },
317 DataType::Unknown(_) => {
318 let class = pl.getattr(intern!(py, "Unknown"))?;
319 class.call0()
320 },
321 DataType::BinaryOffset => {
322 unimplemented!()
323 },
324 }
325 }
326}
327
328impl<'py> FromPyObject<'py> for Wrap<Field> {
329 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
330 let py = ob.py();
331 let name = ob
332 .getattr(intern!(py, "name"))?
333 .str()?
334 .extract::<PyBackedStr>()?;
335 let dtype = ob
336 .getattr(intern!(py, "dtype"))?
337 .extract::<Wrap<DataType>>()?;
338 Ok(Wrap(Field::new((&*name).into(), dtype.0)))
339 }
340}
341
342impl<'py> FromPyObject<'py> for Wrap<DataType> {
343 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
344 let py = ob.py();
345 let type_name = ob.get_type().qualname()?.to_string();
346
347 let dtype = match &*type_name {
348 "DataTypeClass" => {
349 let name = ob
351 .getattr(intern!(py, "__name__"))?
352 .str()?
353 .extract::<PyBackedStr>()?;
354 match &*name {
355 "Int8" => DataType::Int8,
356 "Int16" => DataType::Int16,
357 "Int32" => DataType::Int32,
358 "Int64" => DataType::Int64,
359 "Int128" => DataType::Int128,
360 "UInt8" => DataType::UInt8,
361 "UInt16" => DataType::UInt16,
362 "UInt32" => DataType::UInt32,
363 "UInt64" => DataType::UInt64,
364 "Float32" => DataType::Float32,
365 "Float64" => DataType::Float64,
366 "Boolean" => DataType::Boolean,
367 "String" => DataType::String,
368 "Binary" => DataType::Binary,
369 "Categorical" => DataType::Categorical(None, Default::default()),
370 "Enum" => DataType::Enum(None, Default::default()),
371 "Date" => DataType::Date,
372 "Time" => DataType::Time,
373 "Datetime" => DataType::Datetime(TimeUnit::Microseconds, None),
374 "Duration" => DataType::Duration(TimeUnit::Microseconds),
375 "Decimal" => DataType::Decimal(None, None), "List" => DataType::List(Box::new(DataType::Null)),
377 "Array" => DataType::Array(Box::new(DataType::Null), 0),
378 "Struct" => DataType::Struct(vec![]),
379 "Null" => DataType::Null,
380 #[cfg(feature = "object")]
381 "Object" => DataType::Object(OBJECT_NAME),
382 "Unknown" => DataType::Unknown(Default::default()),
383 dt => {
384 return Err(PyTypeError::new_err(format!(
385 "'{dt}' is not a Polars data type",
386 )));
387 },
388 }
389 },
390 "Int8" => DataType::Int8,
391 "Int16" => DataType::Int16,
392 "Int32" => DataType::Int32,
393 "Int64" => DataType::Int64,
394 "Int128" => DataType::Int128,
395 "UInt8" => DataType::UInt8,
396 "UInt16" => DataType::UInt16,
397 "UInt32" => DataType::UInt32,
398 "UInt64" => DataType::UInt64,
399 "Float32" => DataType::Float32,
400 "Float64" => DataType::Float64,
401 "Boolean" => DataType::Boolean,
402 "String" => DataType::String,
403 "Binary" => DataType::Binary,
404 "Categorical" => {
405 let ordering = ob.getattr(intern!(py, "ordering")).unwrap();
406 let ordering = ordering.extract::<Wrap<CategoricalOrdering>>()?.0;
407 DataType::Categorical(None, ordering)
408 },
409 "Enum" => {
410 let categories = ob.getattr(intern!(py, "categories")).unwrap();
411 let s = get_series(&categories.as_borrowed())?;
412 let ca = s.str().map_err(PyPolarsErr::from)?;
413 let categories = ca.downcast_iter().next().unwrap().clone();
414 create_enum_dtype(categories)
415 },
416 "Date" => DataType::Date,
417 "Time" => DataType::Time,
418 "Datetime" => {
419 let time_unit = ob.getattr(intern!(py, "time_unit")).unwrap();
420 let time_unit = time_unit.extract::<Wrap<TimeUnit>>()?.0;
421 let time_zone = ob.getattr(intern!(py, "time_zone")).unwrap();
422 let time_zone = time_zone.extract::<Option<PyBackedStr>>()?;
423 DataType::Datetime(
424 time_unit,
425 TimeZone::opt_try_new(time_zone.as_deref()).map_err(to_py_err)?,
426 )
427 },
428 "Duration" => {
429 let time_unit = ob.getattr(intern!(py, "time_unit")).unwrap();
430 let time_unit = time_unit.extract::<Wrap<TimeUnit>>()?.0;
431 DataType::Duration(time_unit)
432 },
433 "Decimal" => {
434 let precision = ob.getattr(intern!(py, "precision"))?.extract()?;
435 let scale = ob.getattr(intern!(py, "scale"))?.extract()?;
436 DataType::Decimal(precision, Some(scale))
437 },
438 "List" => {
439 let inner = ob.getattr(intern!(py, "inner")).unwrap();
440 let inner = inner.extract::<Wrap<DataType>>()?;
441 DataType::List(Box::new(inner.0))
442 },
443 "Array" => {
444 let inner = ob.getattr(intern!(py, "inner")).unwrap();
445 let size = ob.getattr(intern!(py, "size")).unwrap();
446 let inner = inner.extract::<Wrap<DataType>>()?;
447 let size = size.extract::<usize>()?;
448 DataType::Array(Box::new(inner.0), size)
449 },
450 "Struct" => {
451 let fields = ob.getattr(intern!(py, "fields"))?;
452 let fields = fields
453 .extract::<Vec<Wrap<Field>>>()?
454 .into_iter()
455 .map(|f| f.0)
456 .collect::<Vec<Field>>();
457 DataType::Struct(fields)
458 },
459 "Null" => DataType::Null,
460 #[cfg(feature = "object")]
461 "Object" => DataType::Object(OBJECT_NAME),
462 "Unknown" => DataType::Unknown(Default::default()),
463 dt => {
464 return Err(PyTypeError::new_err(format!(
465 "'{dt}' is not a Polars data type",
466 )));
467 },
468 };
469 Ok(Wrap(dtype))
470 }
471}
472
473impl<'py> IntoPyObject<'py> for Wrap<CategoricalOrdering> {
474 type Target = PyString;
475 type Output = Bound<'py, Self::Target>;
476 type Error = Infallible;
477
478 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
479 match self.0 {
480 CategoricalOrdering::Physical => "physical",
481 CategoricalOrdering::Lexical => "lexical",
482 }
483 .into_pyobject(py)
484 }
485}
486
487impl<'py> IntoPyObject<'py> for Wrap<TimeUnit> {
488 type Target = PyString;
489 type Output = Bound<'py, Self::Target>;
490 type Error = Infallible;
491
492 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
493 self.0.to_ascii().into_pyobject(py)
494 }
495}
496
497#[cfg(feature = "parquet")]
498impl<'py> FromPyObject<'py> for Wrap<StatisticsOptions> {
499 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
500 let mut statistics = StatisticsOptions::empty();
501
502 let dict = ob.downcast::<PyDict>()?;
503 for (key, val) in dict {
504 let key = key.extract::<PyBackedStr>()?;
505 let val = val.extract::<bool>()?;
506
507 match key.as_ref() {
508 "min" => statistics.min_value = val,
509 "max" => statistics.max_value = val,
510 "distinct_count" => statistics.distinct_count = val,
511 "null_count" => statistics.null_count = val,
512 _ => {
513 return Err(PyTypeError::new_err(format!(
514 "'{key}' is not a valid statistic option",
515 )));
516 },
517 }
518 }
519
520 Ok(Wrap(statistics))
521 }
522}
523
524impl<'py> FromPyObject<'py> for Wrap<Row<'static>> {
525 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
526 let vals = ob.extract::<Vec<Wrap<AnyValue<'static>>>>()?;
527 let vals = reinterpret_vec(vals);
528 Ok(Wrap(Row(vals)))
529 }
530}
531
532impl<'py> FromPyObject<'py> for Wrap<Schema> {
533 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
534 let dict = ob.downcast::<PyDict>()?;
535
536 Ok(Wrap(
537 dict.iter()
538 .map(|(key, val)| {
539 let key = key.extract::<PyBackedStr>()?;
540 let val = val.extract::<Wrap<DataType>>()?;
541
542 Ok(Field::new((&*key).into(), val.0))
543 })
544 .collect::<PyResult<Schema>>()?,
545 ))
546 }
547}
548
549impl<'py> FromPyObject<'py> for Wrap<ScanSources> {
550 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
551 let list = ob.downcast::<PyList>()?.to_owned();
552
553 if list.is_empty() {
554 return Ok(Wrap(ScanSources::default()));
555 }
556
557 enum MutableSources {
558 Paths(Vec<PathBuf>),
559 Files(Vec<File>),
560 Buffers(Vec<MemSlice>),
561 }
562
563 let num_items = list.len();
564 let mut iter = list
565 .into_iter()
566 .map(|val| get_python_scan_source_input(val.unbind(), false));
567
568 let Some(first) = iter.next() else {
569 return Ok(Wrap(ScanSources::default()));
570 };
571
572 let mut sources = match first? {
573 PythonScanSourceInput::Path(path) => {
574 let mut sources = Vec::with_capacity(num_items);
575 sources.push(path);
576 MutableSources::Paths(sources)
577 },
578 PythonScanSourceInput::File(file) => {
579 let mut sources = Vec::with_capacity(num_items);
580 sources.push(file.into());
581 MutableSources::Files(sources)
582 },
583 PythonScanSourceInput::Buffer(buffer) => {
584 let mut sources = Vec::with_capacity(num_items);
585 sources.push(buffer);
586 MutableSources::Buffers(sources)
587 },
588 };
589
590 for source in iter {
591 match (&mut sources, source?) {
592 (MutableSources::Paths(v), PythonScanSourceInput::Path(p)) => v.push(p),
593 (MutableSources::Files(v), PythonScanSourceInput::File(f)) => v.push(f.into()),
594 (MutableSources::Buffers(v), PythonScanSourceInput::Buffer(f)) => v.push(f),
595 _ => {
596 return Err(PyTypeError::new_err(
597 "Cannot combine in-memory bytes, paths and files for scan sources",
598 ));
599 },
600 }
601 }
602
603 Ok(Wrap(match sources {
604 MutableSources::Paths(i) => ScanSources::Paths(i.into()),
605 MutableSources::Files(i) => ScanSources::Files(i.into()),
606 MutableSources::Buffers(i) => ScanSources::Buffers(i.into()),
607 }))
608 }
609}
610
611impl<'py> IntoPyObject<'py> for Wrap<&Schema> {
612 type Target = PyDict;
613 type Output = Bound<'py, Self::Target>;
614 type Error = PyErr;
615
616 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
617 let dict = PyDict::new(py);
618 self.0
619 .iter()
620 .try_for_each(|(k, v)| dict.set_item(k.as_str(), &Wrap(v.clone())))?;
621 Ok(dict)
622 }
623}
624
625#[derive(Debug)]
626#[repr(transparent)]
627pub struct ObjectValue {
628 pub inner: PyObject,
629}
630
631impl Clone for ObjectValue {
632 fn clone(&self) -> Self {
633 Python::with_gil(|py| Self {
634 inner: self.inner.clone_ref(py),
635 })
636 }
637}
638
639impl Hash for ObjectValue {
640 fn hash<H: Hasher>(&self, state: &mut H) {
641 let h = Python::with_gil(|py| self.inner.bind(py).hash().expect("should be hashable"));
642 state.write_isize(h)
643 }
644}
645
646impl Eq for ObjectValue {}
647
648impl PartialEq for ObjectValue {
649 fn eq(&self, other: &Self) -> bool {
650 Python::with_gil(|py| {
651 match self
652 .inner
653 .bind(py)
654 .rich_compare(other.inner.bind(py), CompareOp::Eq)
655 {
656 Ok(result) => result.is_truthy().unwrap(),
657 Err(_) => false,
658 }
659 })
660 }
661}
662
663impl TotalEq for ObjectValue {
664 fn tot_eq(&self, other: &Self) -> bool {
665 self == other
666 }
667}
668
669impl TotalHash for ObjectValue {
670 fn tot_hash<H>(&self, state: &mut H)
671 where
672 H: Hasher,
673 {
674 self.hash(state);
675 }
676}
677
678impl Display for ObjectValue {
679 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
680 write!(f, "{}", self.inner)
681 }
682}
683
684#[cfg(feature = "object")]
685impl PolarsObject for ObjectValue {
686 fn type_name() -> &'static str {
687 "object"
688 }
689}
690
691impl From<PyObject> for ObjectValue {
692 fn from(p: PyObject) -> Self {
693 Self { inner: p }
694 }
695}
696
697impl<'py> FromPyObject<'py> for ObjectValue {
698 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
699 Ok(ObjectValue {
700 inner: ob.to_owned().unbind(),
701 })
702 }
703}
704
705#[cfg(feature = "object")]
709impl From<&dyn PolarsObjectSafe> for &ObjectValue {
710 fn from(val: &dyn PolarsObjectSafe) -> Self {
711 unsafe { &*(val as *const dyn PolarsObjectSafe as *const ObjectValue) }
712 }
713}
714
715impl<'a, 'py> IntoPyObject<'py> for &'a ObjectValue {
716 type Target = PyAny;
717 type Output = Borrowed<'a, 'py, Self::Target>;
718 type Error = std::convert::Infallible;
719
720 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
721 Ok(self.inner.bind_borrowed(py))
722 }
723}
724
725impl Default for ObjectValue {
726 fn default() -> Self {
727 Python::with_gil(|py| ObjectValue { inner: py.None() })
728 }
729}
730
731impl<'py, T: NativeType + FromPyObject<'py>> FromPyObject<'py> for Wrap<Vec<T>> {
732 fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
733 let seq = obj.downcast::<PySequence>()?;
734 let mut v = Vec::with_capacity(seq.len().unwrap_or(0));
735 for item in seq.try_iter()? {
736 v.push(item?.extract::<T>()?);
737 }
738 Ok(Wrap(v))
739 }
740}
741
742#[cfg(feature = "asof_join")]
743impl<'py> FromPyObject<'py> for Wrap<AsofStrategy> {
744 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
745 let parsed = match &*(ob.extract::<PyBackedStr>()?) {
746 "backward" => AsofStrategy::Backward,
747 "forward" => AsofStrategy::Forward,
748 "nearest" => AsofStrategy::Nearest,
749 v => {
750 return Err(PyValueError::new_err(format!(
751 "asof `strategy` must be one of {{'backward', 'forward', 'nearest'}}, got {v}",
752 )));
753 },
754 };
755 Ok(Wrap(parsed))
756 }
757}
758
759impl<'py> FromPyObject<'py> for Wrap<InterpolationMethod> {
760 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
761 let parsed = match &*(ob.extract::<PyBackedStr>()?) {
762 "linear" => InterpolationMethod::Linear,
763 "nearest" => InterpolationMethod::Nearest,
764 v => {
765 return Err(PyValueError::new_err(format!(
766 "interpolation `method` must be one of {{'linear', 'nearest'}}, got {v}",
767 )));
768 },
769 };
770 Ok(Wrap(parsed))
771 }
772}
773
774#[cfg(feature = "avro")]
775impl<'py> FromPyObject<'py> for Wrap<Option<AvroCompression>> {
776 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
777 let parsed = match &*ob.extract::<PyBackedStr>()? {
778 "uncompressed" => None,
779 "snappy" => Some(AvroCompression::Snappy),
780 "deflate" => Some(AvroCompression::Deflate),
781 v => {
782 return Err(PyValueError::new_err(format!(
783 "avro `compression` must be one of {{'uncompressed', 'snappy', 'deflate'}}, got {v}",
784 )));
785 },
786 };
787 Ok(Wrap(parsed))
788 }
789}
790
791impl<'py> FromPyObject<'py> for Wrap<CategoricalOrdering> {
792 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
793 let parsed = match &*ob.extract::<PyBackedStr>()? {
794 "physical" => CategoricalOrdering::Physical,
795 "lexical" => CategoricalOrdering::Lexical,
796 v => {
797 return Err(PyValueError::new_err(format!(
798 "categorical `ordering` must be one of {{'physical', 'lexical'}}, got {v}",
799 )));
800 },
801 };
802 Ok(Wrap(parsed))
803 }
804}
805
806impl<'py> FromPyObject<'py> for Wrap<StartBy> {
807 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
808 let parsed = match &*ob.extract::<PyBackedStr>()? {
809 "window" => StartBy::WindowBound,
810 "datapoint" => StartBy::DataPoint,
811 "monday" => StartBy::Monday,
812 "tuesday" => StartBy::Tuesday,
813 "wednesday" => StartBy::Wednesday,
814 "thursday" => StartBy::Thursday,
815 "friday" => StartBy::Friday,
816 "saturday" => StartBy::Saturday,
817 "sunday" => StartBy::Sunday,
818 v => {
819 return Err(PyValueError::new_err(format!(
820 "`start_by` must be one of {{'window', 'datapoint', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'}}, got {v}",
821 )));
822 },
823 };
824 Ok(Wrap(parsed))
825 }
826}
827
828impl<'py> FromPyObject<'py> for Wrap<ClosedWindow> {
829 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
830 let parsed = match &*ob.extract::<PyBackedStr>()? {
831 "left" => ClosedWindow::Left,
832 "right" => ClosedWindow::Right,
833 "both" => ClosedWindow::Both,
834 "none" => ClosedWindow::None,
835 v => {
836 return Err(PyValueError::new_err(format!(
837 "`closed` must be one of {{'left', 'right', 'both', 'none'}}, got {v}",
838 )));
839 },
840 };
841 Ok(Wrap(parsed))
842 }
843}
844
845impl<'py> FromPyObject<'py> for Wrap<RoundMode> {
846 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
847 let parsed = match &*ob.extract::<PyBackedStr>()? {
848 "half_to_even" => RoundMode::HalfToEven,
849 "half_away_from_zero" => RoundMode::HalfAwayFromZero,
850 v => {
851 return Err(PyValueError::new_err(format!(
852 "`mode` must be one of {{'half_to_even', 'half_away_from_zero'}}, got {v}",
853 )));
854 },
855 };
856 Ok(Wrap(parsed))
857 }
858}
859
860#[cfg(feature = "csv")]
861impl<'py> FromPyObject<'py> for Wrap<CsvEncoding> {
862 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
863 let parsed = match &*ob.extract::<PyBackedStr>()? {
864 "utf8" => CsvEncoding::Utf8,
865 "utf8-lossy" => CsvEncoding::LossyUtf8,
866 v => {
867 return Err(PyValueError::new_err(format!(
868 "csv `encoding` must be one of {{'utf8', 'utf8-lossy'}}, got {v}",
869 )));
870 },
871 };
872 Ok(Wrap(parsed))
873 }
874}
875
876#[cfg(feature = "ipc")]
877impl<'py> FromPyObject<'py> for Wrap<Option<IpcCompression>> {
878 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
879 let parsed = match &*ob.extract::<PyBackedStr>()? {
880 "uncompressed" => None,
881 "lz4" => Some(IpcCompression::LZ4),
882 "zstd" => Some(IpcCompression::ZSTD),
883 v => {
884 return Err(PyValueError::new_err(format!(
885 "ipc `compression` must be one of {{'uncompressed', 'lz4', 'zstd'}}, got {v}",
886 )));
887 },
888 };
889 Ok(Wrap(parsed))
890 }
891}
892
893impl<'py> FromPyObject<'py> for Wrap<JoinType> {
894 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
895 let parsed = match &*ob.extract::<PyBackedStr>()? {
896 "inner" => JoinType::Inner,
897 "left" => JoinType::Left,
898 "right" => JoinType::Right,
899 "full" => JoinType::Full,
900 "semi" => JoinType::Semi,
901 "anti" => JoinType::Anti,
902 #[cfg(feature = "cross_join")]
903 "cross" => JoinType::Cross,
904 v => {
905 return Err(PyValueError::new_err(format!(
906 "`how` must be one of {{'inner', 'left', 'full', 'semi', 'anti', 'cross'}}, got {v}",
907 )));
908 },
909 };
910 Ok(Wrap(parsed))
911 }
912}
913
914impl<'py> FromPyObject<'py> for Wrap<Label> {
915 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
916 let parsed = match &*ob.extract::<PyBackedStr>()? {
917 "left" => Label::Left,
918 "right" => Label::Right,
919 "datapoint" => Label::DataPoint,
920 v => {
921 return Err(PyValueError::new_err(format!(
922 "`label` must be one of {{'left', 'right', 'datapoint'}}, got {v}",
923 )));
924 },
925 };
926 Ok(Wrap(parsed))
927 }
928}
929
930impl<'py> FromPyObject<'py> for Wrap<ListToStructWidthStrategy> {
931 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
932 let parsed = match &*ob.extract::<PyBackedStr>()? {
933 "first_non_null" => ListToStructWidthStrategy::FirstNonNull,
934 "max_width" => ListToStructWidthStrategy::MaxWidth,
935 v => {
936 return Err(PyValueError::new_err(format!(
937 "`n_field_strategy` must be one of {{'first_non_null', 'max_width'}}, got {v}",
938 )));
939 },
940 };
941 Ok(Wrap(parsed))
942 }
943}
944
945impl<'py> FromPyObject<'py> for Wrap<NonExistent> {
946 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
947 let parsed = match &*ob.extract::<PyBackedStr>()? {
948 "null" => NonExistent::Null,
949 "raise" => NonExistent::Raise,
950 v => {
951 return Err(PyValueError::new_err(format!(
952 "`non_existent` must be one of {{'null', 'raise'}}, got {v}",
953 )));
954 },
955 };
956 Ok(Wrap(parsed))
957 }
958}
959
960impl<'py> FromPyObject<'py> for Wrap<NullBehavior> {
961 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
962 let parsed = match &*ob.extract::<PyBackedStr>()? {
963 "drop" => NullBehavior::Drop,
964 "ignore" => NullBehavior::Ignore,
965 v => {
966 return Err(PyValueError::new_err(format!(
967 "`null_behavior` must be one of {{'drop', 'ignore'}}, got {v}",
968 )));
969 },
970 };
971 Ok(Wrap(parsed))
972 }
973}
974
975impl<'py> FromPyObject<'py> for Wrap<NullStrategy> {
976 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
977 let parsed = match &*ob.extract::<PyBackedStr>()? {
978 "ignore" => NullStrategy::Ignore,
979 "propagate" => NullStrategy::Propagate,
980 v => {
981 return Err(PyValueError::new_err(format!(
982 "`null_strategy` must be one of {{'ignore', 'propagate'}}, got {v}",
983 )));
984 },
985 };
986 Ok(Wrap(parsed))
987 }
988}
989
990#[cfg(feature = "parquet")]
991impl<'py> FromPyObject<'py> for Wrap<ParallelStrategy> {
992 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
993 let parsed = match &*ob.extract::<PyBackedStr>()? {
994 "auto" => ParallelStrategy::Auto,
995 "columns" => ParallelStrategy::Columns,
996 "row_groups" => ParallelStrategy::RowGroups,
997 "prefiltered" => ParallelStrategy::Prefiltered,
998 "none" => ParallelStrategy::None,
999 v => {
1000 return Err(PyValueError::new_err(format!(
1001 "`parallel` must be one of {{'auto', 'columns', 'row_groups', 'prefiltered', 'none'}}, got {v}",
1002 )));
1003 },
1004 };
1005 Ok(Wrap(parsed))
1006 }
1007}
1008
1009impl<'py> FromPyObject<'py> for Wrap<IndexOrder> {
1010 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1011 let parsed = match &*ob.extract::<PyBackedStr>()? {
1012 "fortran" => IndexOrder::Fortran,
1013 "c" => IndexOrder::C,
1014 v => {
1015 return Err(PyValueError::new_err(format!(
1016 "`order` must be one of {{'fortran', 'c'}}, got {v}",
1017 )));
1018 },
1019 };
1020 Ok(Wrap(parsed))
1021 }
1022}
1023
1024impl<'py> FromPyObject<'py> for Wrap<QuantileMethod> {
1025 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1026 let parsed = match &*ob.extract::<PyBackedStr>()? {
1027 "lower" => QuantileMethod::Lower,
1028 "higher" => QuantileMethod::Higher,
1029 "nearest" => QuantileMethod::Nearest,
1030 "linear" => QuantileMethod::Linear,
1031 "midpoint" => QuantileMethod::Midpoint,
1032 "equiprobable" => QuantileMethod::Equiprobable,
1033 v => {
1034 return Err(PyValueError::new_err(format!(
1035 "`interpolation` must be one of {{'lower', 'higher', 'nearest', 'linear', 'midpoint', 'equiprobable'}}, got {v}",
1036 )));
1037 },
1038 };
1039 Ok(Wrap(parsed))
1040 }
1041}
1042
1043impl<'py> FromPyObject<'py> for Wrap<RankMethod> {
1044 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1045 let parsed = match &*ob.extract::<PyBackedStr>()? {
1046 "min" => RankMethod::Min,
1047 "max" => RankMethod::Max,
1048 "average" => RankMethod::Average,
1049 "dense" => RankMethod::Dense,
1050 "ordinal" => RankMethod::Ordinal,
1051 "random" => RankMethod::Random,
1052 v => {
1053 return Err(PyValueError::new_err(format!(
1054 "rank `method` must be one of {{'min', 'max', 'average', 'dense', 'ordinal', 'random'}}, got {v}",
1055 )));
1056 },
1057 };
1058 Ok(Wrap(parsed))
1059 }
1060}
1061
1062impl<'py> FromPyObject<'py> for Wrap<Roll> {
1063 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1064 let parsed = match &*ob.extract::<PyBackedStr>()? {
1065 "raise" => Roll::Raise,
1066 "forward" => Roll::Forward,
1067 "backward" => Roll::Backward,
1068 v => {
1069 return Err(PyValueError::new_err(format!(
1070 "`roll` must be one of {{'raise', 'forward', 'backward'}}, got {v}",
1071 )));
1072 },
1073 };
1074 Ok(Wrap(parsed))
1075 }
1076}
1077
1078impl<'py> FromPyObject<'py> for Wrap<TimeUnit> {
1079 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1080 let parsed = match &*ob.extract::<PyBackedStr>()? {
1081 "ns" => TimeUnit::Nanoseconds,
1082 "us" => TimeUnit::Microseconds,
1083 "ms" => TimeUnit::Milliseconds,
1084 v => {
1085 return Err(PyValueError::new_err(format!(
1086 "`time_unit` must be one of {{'ns', 'us', 'ms'}}, got {v}",
1087 )));
1088 },
1089 };
1090 Ok(Wrap(parsed))
1091 }
1092}
1093
1094impl<'py> FromPyObject<'py> for Wrap<UniqueKeepStrategy> {
1095 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1096 let parsed = match &*ob.extract::<PyBackedStr>()? {
1097 "first" => UniqueKeepStrategy::First,
1098 "last" => UniqueKeepStrategy::Last,
1099 "none" => UniqueKeepStrategy::None,
1100 "any" => UniqueKeepStrategy::Any,
1101 v => {
1102 return Err(PyValueError::new_err(format!(
1103 "`keep` must be one of {{'first', 'last', 'any', 'none'}}, got {v}",
1104 )));
1105 },
1106 };
1107 Ok(Wrap(parsed))
1108 }
1109}
1110
1111#[cfg(feature = "search_sorted")]
1112impl<'py> FromPyObject<'py> for Wrap<SearchSortedSide> {
1113 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1114 let parsed = match &*ob.extract::<PyBackedStr>()? {
1115 "any" => SearchSortedSide::Any,
1116 "left" => SearchSortedSide::Left,
1117 "right" => SearchSortedSide::Right,
1118 v => {
1119 return Err(PyValueError::new_err(format!(
1120 "sorted `side` must be one of {{'any', 'left', 'right'}}, got {v}",
1121 )));
1122 },
1123 };
1124 Ok(Wrap(parsed))
1125 }
1126}
1127
1128impl<'py> FromPyObject<'py> for Wrap<ClosedInterval> {
1129 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1130 let parsed = match &*ob.extract::<PyBackedStr>()? {
1131 "both" => ClosedInterval::Both,
1132 "left" => ClosedInterval::Left,
1133 "right" => ClosedInterval::Right,
1134 "none" => ClosedInterval::None,
1135 v => {
1136 return Err(PyValueError::new_err(format!(
1137 "`closed` must be one of {{'both', 'left', 'right', 'none'}}, got {v}",
1138 )));
1139 },
1140 };
1141 Ok(Wrap(parsed))
1142 }
1143}
1144
1145impl<'py> FromPyObject<'py> for Wrap<WindowMapping> {
1146 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1147 let parsed = match &*ob.extract::<PyBackedStr>()? {
1148 "group_to_rows" => WindowMapping::GroupsToRows,
1149 "join" => WindowMapping::Join,
1150 "explode" => WindowMapping::Explode,
1151 v => {
1152 return Err(PyValueError::new_err(format!(
1153 "`mapping_strategy` must be one of {{'group_to_rows', 'join', 'explode'}}, got {v}",
1154 )));
1155 },
1156 };
1157 Ok(Wrap(parsed))
1158 }
1159}
1160
1161impl<'py> FromPyObject<'py> for Wrap<JoinValidation> {
1162 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1163 let parsed = match &*ob.extract::<PyBackedStr>()? {
1164 "1:1" => JoinValidation::OneToOne,
1165 "1:m" => JoinValidation::OneToMany,
1166 "m:m" => JoinValidation::ManyToMany,
1167 "m:1" => JoinValidation::ManyToOne,
1168 v => {
1169 return Err(PyValueError::new_err(format!(
1170 "`validate` must be one of {{'m:m', 'm:1', '1:m', '1:1'}}, got {v}",
1171 )));
1172 },
1173 };
1174 Ok(Wrap(parsed))
1175 }
1176}
1177
1178impl<'py> FromPyObject<'py> for Wrap<MaintainOrderJoin> {
1179 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1180 let parsed = match &*ob.extract::<PyBackedStr>()? {
1181 "none" => MaintainOrderJoin::None,
1182 "left" => MaintainOrderJoin::Left,
1183 "right" => MaintainOrderJoin::Right,
1184 "left_right" => MaintainOrderJoin::LeftRight,
1185 "right_left" => MaintainOrderJoin::RightLeft,
1186 v => {
1187 return Err(PyValueError::new_err(format!(
1188 "`maintain_order` must be one of {{'none', 'left', 'right', 'left_right', 'right_left'}}, got {v}",
1189 )));
1190 },
1191 };
1192 Ok(Wrap(parsed))
1193 }
1194}
1195
1196#[cfg(feature = "csv")]
1197impl<'py> FromPyObject<'py> for Wrap<QuoteStyle> {
1198 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1199 let parsed = match &*ob.extract::<PyBackedStr>()? {
1200 "always" => QuoteStyle::Always,
1201 "necessary" => QuoteStyle::Necessary,
1202 "non_numeric" => QuoteStyle::NonNumeric,
1203 "never" => QuoteStyle::Never,
1204 v => {
1205 return Err(PyValueError::new_err(format!(
1206 "`quote_style` must be one of {{'always', 'necessary', 'non_numeric', 'never'}}, got {v}",
1207 )));
1208 },
1209 };
1210 Ok(Wrap(parsed))
1211 }
1212}
1213
1214#[cfg(feature = "cloud")]
1215pub(crate) fn parse_cloud_options(uri: &str, kv: Vec<(String, String)>) -> PyResult<CloudOptions> {
1216 let out = CloudOptions::from_untyped_config(uri, kv).map_err(PyPolarsErr::from)?;
1217 Ok(out)
1218}
1219
1220#[cfg(feature = "list_sets")]
1221impl<'py> FromPyObject<'py> for Wrap<SetOperation> {
1222 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1223 let parsed = match &*ob.extract::<PyBackedStr>()? {
1224 "union" => SetOperation::Union,
1225 "difference" => SetOperation::Difference,
1226 "intersection" => SetOperation::Intersection,
1227 "symmetric_difference" => SetOperation::SymmetricDifference,
1228 v => {
1229 return Err(PyValueError::new_err(format!(
1230 "set operation must be one of {{'union', 'difference', 'intersection', 'symmetric_difference'}}, got {v}",
1231 )));
1232 },
1233 };
1234 Ok(Wrap(parsed))
1235 }
1236}
1237
1238impl<'py> FromPyObject<'py> for Wrap<CastColumnsPolicy> {
1240 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1241 if ob.is_none() {
1242 static DEFAULT: GILOnceCell<Wrap<CastColumnsPolicy>> = GILOnceCell::new();
1245
1246 let out = DEFAULT.get_or_try_init(ob.py(), || {
1247 let ob = PyModule::import(ob.py(), "polars.io.cast_options")
1248 .unwrap()
1249 .getattr("ScanCastOptions")
1250 .unwrap()
1251 .call_method0("_default")
1252 .unwrap();
1253
1254 let out = Self::extract_bound(&ob)?;
1255
1256 debug_assert_eq!(&out.0, &CastColumnsPolicy::ERROR_ON_MISMATCH);
1258
1259 PyResult::Ok(out)
1260 })?;
1261
1262 return Ok(out.clone());
1263 }
1264
1265 let integer_upcast = match &*ob.getattr("integer_cast")?.extract::<PyBackedStr>()? {
1266 "upcast" => true,
1267 "forbid" => false,
1268 v => {
1269 return Err(PyValueError::new_err(format!(
1270 "unknown option for integer_cast: {}",
1271 v
1272 )));
1273 },
1274 };
1275
1276 let mut float_upcast = false;
1277 let mut float_downcast = false;
1278
1279 let mut parse_float_cast_option = |v: &str| -> PyResult<()> {
1280 match v {
1281 "forbid" => {},
1282 "upcast" => float_upcast = true,
1283 "downcast" => float_downcast = true,
1284 v => {
1285 return Err(PyValueError::new_err(format!(
1286 "unknown option for float_cast: {}",
1287 v
1288 )));
1289 },
1290 }
1291
1292 Ok(())
1293 };
1294
1295 let float_cast_object = ob.getattr("float_cast")?;
1296
1297 parse_multiple_options(
1298 "float_cast",
1299 float_cast_object,
1300 &mut parse_float_cast_option,
1301 )?;
1302
1303 let mut datetime_nanoseconds_downcast = false;
1304 let mut datetime_convert_timezone = false;
1305
1306 let mut parse_datetime_cast_option = |v: &str| -> PyResult<()> {
1307 match v {
1308 "forbid" => {},
1309 "nanosecond-downcast" => datetime_nanoseconds_downcast = true,
1310 "convert-timezone" => datetime_convert_timezone = true,
1311 v => {
1312 return Err(PyValueError::new_err(format!(
1313 "unknown option for datetime_cast: {}",
1314 v
1315 )));
1316 },
1317 };
1318
1319 Ok(())
1320 };
1321
1322 let datetime_cast_object = ob.getattr("datetime_cast")?;
1323
1324 parse_multiple_options(
1325 "datetime_cast",
1326 datetime_cast_object,
1327 &mut parse_datetime_cast_option,
1328 )?;
1329
1330 let missing_struct_fields = match &*ob
1331 .getattr("missing_struct_fields")?
1332 .extract::<PyBackedStr>()?
1333 {
1334 "insert" => MissingColumnsPolicy::Insert,
1335 "raise" => MissingColumnsPolicy::Raise,
1336 v => {
1337 return Err(PyValueError::new_err(format!(
1338 "unknown option for missing_struct_fields: {}",
1339 v
1340 )));
1341 },
1342 };
1343
1344 let extra_struct_fields = match &*ob
1345 .getattr("extra_struct_fields")?
1346 .extract::<PyBackedStr>()?
1347 {
1348 "ignore" => ExtraColumnsPolicy::Ignore,
1349 "raise" => ExtraColumnsPolicy::Raise,
1350 v => {
1351 return Err(PyValueError::new_err(format!(
1352 "unknown option for extra_struct_fields: {}",
1353 v
1354 )));
1355 },
1356 };
1357
1358 return Ok(Wrap(CastColumnsPolicy {
1359 integer_upcast,
1360 float_upcast,
1361 float_downcast,
1362 datetime_nanoseconds_downcast,
1363 datetime_microseconds_downcast: false,
1364 datetime_convert_timezone,
1365 missing_struct_fields,
1366 extra_struct_fields,
1367 }));
1368
1369 fn parse_multiple_options<'a>(
1370 parameter_name: &'static str,
1371 py_object: Bound<'a, PyAny>,
1372 parser_func: &mut dyn FnMut(&str) -> PyResult<()>,
1373 ) -> PyResult<()> {
1374 if let Ok(v) = py_object.extract::<PyBackedStr>() {
1375 parser_func(&v)?;
1376 } else if let Ok(v) = py_object.try_iter() {
1377 for v in v {
1378 parser_func(&v?.extract::<PyBackedStr>()?)?;
1379 }
1380 } else {
1381 return Err(PyValueError::new_err(format!(
1382 "unknown type for {}: {}",
1383 parameter_name, py_object
1384 )));
1385 }
1386
1387 Ok(())
1388 }
1389 }
1390}
1391
1392pub(crate) fn parse_fill_null_strategy(
1393 strategy: &str,
1394 limit: FillNullLimit,
1395) -> PyResult<FillNullStrategy> {
1396 let parsed = match strategy {
1397 "forward" => FillNullStrategy::Forward(limit),
1398 "backward" => FillNullStrategy::Backward(limit),
1399 "min" => FillNullStrategy::Min,
1400 "max" => FillNullStrategy::Max,
1401 "mean" => FillNullStrategy::Mean,
1402 "zero" => FillNullStrategy::Zero,
1403 "one" => FillNullStrategy::One,
1404 e => {
1405 return Err(PyValueError::new_err(format!(
1406 "`strategy` must be one of {{'forward', 'backward', 'min', 'max', 'mean', 'zero', 'one'}}, got {e}",
1407 )));
1408 },
1409 };
1410 Ok(parsed)
1411}
1412
1413#[cfg(feature = "parquet")]
1414pub(crate) fn parse_parquet_compression(
1415 compression: &str,
1416 compression_level: Option<i32>,
1417) -> PyResult<ParquetCompression> {
1418 let parsed = match compression {
1419 "uncompressed" => ParquetCompression::Uncompressed,
1420 "snappy" => ParquetCompression::Snappy,
1421 "gzip" => ParquetCompression::Gzip(
1422 compression_level
1423 .map(|lvl| {
1424 GzipLevel::try_new(lvl as u8)
1425 .map_err(|e| PyValueError::new_err(format!("{e:?}")))
1426 })
1427 .transpose()?,
1428 ),
1429 "lzo" => ParquetCompression::Lzo,
1430 "brotli" => ParquetCompression::Brotli(
1431 compression_level
1432 .map(|lvl| {
1433 BrotliLevel::try_new(lvl as u32)
1434 .map_err(|e| PyValueError::new_err(format!("{e:?}")))
1435 })
1436 .transpose()?,
1437 ),
1438 "lz4" => ParquetCompression::Lz4Raw,
1439 "zstd" => ParquetCompression::Zstd(
1440 compression_level
1441 .map(|lvl| {
1442 ZstdLevel::try_new(lvl).map_err(|e| PyValueError::new_err(format!("{e:?}")))
1443 })
1444 .transpose()?,
1445 ),
1446 e => {
1447 return Err(PyValueError::new_err(format!(
1448 "parquet `compression` must be one of {{'uncompressed', 'snappy', 'gzip', 'lzo', 'brotli', 'lz4', 'zstd'}}, got {e}",
1449 )));
1450 },
1451 };
1452 Ok(parsed)
1453}
1454
1455pub(crate) fn strings_to_pl_smallstr<I, S>(container: I) -> Vec<PlSmallStr>
1456where
1457 I: IntoIterator<Item = S>,
1458 S: AsRef<str>,
1459{
1460 container
1461 .into_iter()
1462 .map(|s| PlSmallStr::from_str(s.as_ref()))
1463 .collect()
1464}
1465
1466#[derive(Debug, Copy, Clone)]
1467pub struct PyCompatLevel(pub CompatLevel);
1468
1469impl<'py> FromPyObject<'py> for PyCompatLevel {
1470 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1471 Ok(PyCompatLevel(if let Ok(level) = ob.extract::<u16>() {
1472 if let Ok(compat_level) = CompatLevel::with_level(level) {
1473 compat_level
1474 } else {
1475 return Err(PyValueError::new_err("invalid compat level"));
1476 }
1477 } else if let Ok(future) = ob.extract::<bool>() {
1478 if future {
1479 CompatLevel::newest()
1480 } else {
1481 CompatLevel::oldest()
1482 }
1483 } else {
1484 return Err(PyTypeError::new_err(
1485 "'compat_level' argument accepts int or bool",
1486 ));
1487 }))
1488 }
1489}
1490
1491#[cfg(feature = "string_normalize")]
1492impl<'py> FromPyObject<'py> for Wrap<UnicodeForm> {
1493 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1494 let parsed = match &*ob.extract::<PyBackedStr>()? {
1495 "NFC" => UnicodeForm::NFC,
1496 "NFKC" => UnicodeForm::NFKC,
1497 "NFD" => UnicodeForm::NFD,
1498 "NFKD" => UnicodeForm::NFKD,
1499 v => {
1500 return Err(PyValueError::new_err(format!(
1501 "`form` must be one of {{'NFC', 'NFKC', 'NFD', 'NFKD'}}, got {v}",
1502 )));
1503 },
1504 };
1505 Ok(Wrap(parsed))
1506 }
1507}
1508
1509#[cfg(feature = "parquet")]
1510impl<'py> FromPyObject<'py> for Wrap<Option<KeyValueMetadata>> {
1511 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1512 #[derive(FromPyObject)]
1513 enum Metadata {
1514 Static(Vec<(String, String)>),
1515 Dynamic(PyObject),
1516 }
1517
1518 let metadata = Option::<Metadata>::extract_bound(ob)?;
1519 let key_value_metadata = metadata.map(|x| match x {
1520 Metadata::Static(kv) => KeyValueMetadata::from_static(kv),
1521 Metadata::Dynamic(func) => KeyValueMetadata::from_py_function(func),
1522 });
1523 Ok(Wrap(key_value_metadata))
1524 }
1525}
1526
1527impl<'py> FromPyObject<'py> for Wrap<Option<TimeZone>> {
1528 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1529 let tz = Option::<Wrap<PlSmallStr>>::extract_bound(ob)?;
1530
1531 let tz = tz.map(|x| x.0);
1532
1533 Ok(Wrap(TimeZone::opt_try_new(tz).map_err(to_py_err)?))
1534 }
1535}
1536
1537impl<'py> FromPyObject<'py> for Wrap<UpcastOrForbid> {
1538 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1539 let parsed = match &*ob.extract::<PyBackedStr>()? {
1540 "upcast" => UpcastOrForbid::Upcast,
1541 "forbid" => UpcastOrForbid::Forbid,
1542 v => {
1543 return Err(PyValueError::new_err(format!(
1544 "cast parameter must be one of {{'upcast', 'forbid'}}, got {v}",
1545 )));
1546 },
1547 };
1548 Ok(Wrap(parsed))
1549 }
1550}
1551
1552impl<'py> FromPyObject<'py> for Wrap<ExtraColumnsPolicy> {
1553 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1554 let parsed = match &*ob.extract::<PyBackedStr>()? {
1555 "ignore" => ExtraColumnsPolicy::Ignore,
1556 "raise" => ExtraColumnsPolicy::Raise,
1557 v => {
1558 return Err(PyValueError::new_err(format!(
1559 "extra column/field parameter must be one of {{'ignore', 'raise'}}, got {v}",
1560 )));
1561 },
1562 };
1563 Ok(Wrap(parsed))
1564 }
1565}
1566
1567impl<'py> FromPyObject<'py> for Wrap<MissingColumnsPolicy> {
1568 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1569 let parsed = match &*ob.extract::<PyBackedStr>()? {
1570 "insert" => MissingColumnsPolicy::Insert,
1571 "raise" => MissingColumnsPolicy::Raise,
1572 v => {
1573 return Err(PyValueError::new_err(format!(
1574 "missing column/field parameter must be one of {{'insert', 'raise'}}, got {v}",
1575 )));
1576 },
1577 };
1578 Ok(Wrap(parsed))
1579 }
1580}
1581
1582impl<'py> FromPyObject<'py> for Wrap<MissingColumnsPolicyOrExpr> {
1583 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1584 if let Ok(pyexpr) = ob.extract::<PyExpr>() {
1585 return Ok(Wrap(MissingColumnsPolicyOrExpr::InsertWith(pyexpr.inner)));
1586 }
1587
1588 let parsed = match &*ob.extract::<PyBackedStr>()? {
1589 "insert" => MissingColumnsPolicyOrExpr::Insert,
1590 "raise" => MissingColumnsPolicyOrExpr::Raise,
1591 v => {
1592 return Err(PyValueError::new_err(format!(
1593 "missing column/field parameter must be one of {{'insert', 'raise', expression}}, got {v}",
1594 )));
1595 },
1596 };
1597 Ok(Wrap(parsed))
1598 }
1599}