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