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::types::{PyDict, PyList, PySequence, PyString};
35
36use crate::error::PyPolarsErr;
37use crate::file::{PythonScanSourceInput, get_python_scan_source_input};
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),
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),
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.into());
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.into()),
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
838impl<'py> FromPyObject<'py> for Wrap<RoundMode> {
839 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
840 let parsed = match &*ob.extract::<PyBackedStr>()? {
841 "half_to_even" => RoundMode::HalfToEven,
842 "half_away_from_zero" => RoundMode::HalfAwayFromZero,
843 v => {
844 return Err(PyValueError::new_err(format!(
845 "`mode` must be one of {{'half_to_even', 'half_away_from_zero'}}, got {v}",
846 )));
847 },
848 };
849 Ok(Wrap(parsed))
850 }
851}
852
853#[cfg(feature = "csv")]
854impl<'py> FromPyObject<'py> for Wrap<CsvEncoding> {
855 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
856 let parsed = match &*ob.extract::<PyBackedStr>()? {
857 "utf8" => CsvEncoding::Utf8,
858 "utf8-lossy" => CsvEncoding::LossyUtf8,
859 v => {
860 return Err(PyValueError::new_err(format!(
861 "csv `encoding` must be one of {{'utf8', 'utf8-lossy'}}, got {v}",
862 )));
863 },
864 };
865 Ok(Wrap(parsed))
866 }
867}
868
869#[cfg(feature = "ipc")]
870impl<'py> FromPyObject<'py> for Wrap<Option<IpcCompression>> {
871 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
872 let parsed = match &*ob.extract::<PyBackedStr>()? {
873 "uncompressed" => None,
874 "lz4" => Some(IpcCompression::LZ4),
875 "zstd" => Some(IpcCompression::ZSTD),
876 v => {
877 return Err(PyValueError::new_err(format!(
878 "ipc `compression` must be one of {{'uncompressed', 'lz4', 'zstd'}}, got {v}",
879 )));
880 },
881 };
882 Ok(Wrap(parsed))
883 }
884}
885
886impl<'py> FromPyObject<'py> for Wrap<JoinType> {
887 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
888 let parsed = match &*ob.extract::<PyBackedStr>()? {
889 "inner" => JoinType::Inner,
890 "left" => JoinType::Left,
891 "right" => JoinType::Right,
892 "full" => JoinType::Full,
893 "semi" => JoinType::Semi,
894 "anti" => JoinType::Anti,
895 #[cfg(feature = "cross_join")]
896 "cross" => JoinType::Cross,
897 v => {
898 return Err(PyValueError::new_err(format!(
899 "`how` must be one of {{'inner', 'left', 'full', 'semi', 'anti', 'cross'}}, got {v}",
900 )));
901 },
902 };
903 Ok(Wrap(parsed))
904 }
905}
906
907impl<'py> FromPyObject<'py> for Wrap<Label> {
908 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
909 let parsed = match &*ob.extract::<PyBackedStr>()? {
910 "left" => Label::Left,
911 "right" => Label::Right,
912 "datapoint" => Label::DataPoint,
913 v => {
914 return Err(PyValueError::new_err(format!(
915 "`label` must be one of {{'left', 'right', 'datapoint'}}, got {v}",
916 )));
917 },
918 };
919 Ok(Wrap(parsed))
920 }
921}
922
923impl<'py> FromPyObject<'py> for Wrap<ListToStructWidthStrategy> {
924 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
925 let parsed = match &*ob.extract::<PyBackedStr>()? {
926 "first_non_null" => ListToStructWidthStrategy::FirstNonNull,
927 "max_width" => ListToStructWidthStrategy::MaxWidth,
928 v => {
929 return Err(PyValueError::new_err(format!(
930 "`n_field_strategy` must be one of {{'first_non_null', 'max_width'}}, got {v}",
931 )));
932 },
933 };
934 Ok(Wrap(parsed))
935 }
936}
937
938impl<'py> FromPyObject<'py> for Wrap<NonExistent> {
939 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
940 let parsed = match &*ob.extract::<PyBackedStr>()? {
941 "null" => NonExistent::Null,
942 "raise" => NonExistent::Raise,
943 v => {
944 return Err(PyValueError::new_err(format!(
945 "`non_existent` must be one of {{'null', 'raise'}}, got {v}",
946 )));
947 },
948 };
949 Ok(Wrap(parsed))
950 }
951}
952
953impl<'py> FromPyObject<'py> for Wrap<NullBehavior> {
954 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
955 let parsed = match &*ob.extract::<PyBackedStr>()? {
956 "drop" => NullBehavior::Drop,
957 "ignore" => NullBehavior::Ignore,
958 v => {
959 return Err(PyValueError::new_err(format!(
960 "`null_behavior` must be one of {{'drop', 'ignore'}}, got {v}",
961 )));
962 },
963 };
964 Ok(Wrap(parsed))
965 }
966}
967
968impl<'py> FromPyObject<'py> for Wrap<NullStrategy> {
969 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
970 let parsed = match &*ob.extract::<PyBackedStr>()? {
971 "ignore" => NullStrategy::Ignore,
972 "propagate" => NullStrategy::Propagate,
973 v => {
974 return Err(PyValueError::new_err(format!(
975 "`null_strategy` must be one of {{'ignore', 'propagate'}}, got {v}",
976 )));
977 },
978 };
979 Ok(Wrap(parsed))
980 }
981}
982
983#[cfg(feature = "parquet")]
984impl<'py> FromPyObject<'py> for Wrap<ParallelStrategy> {
985 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
986 let parsed = match &*ob.extract::<PyBackedStr>()? {
987 "auto" => ParallelStrategy::Auto,
988 "columns" => ParallelStrategy::Columns,
989 "row_groups" => ParallelStrategy::RowGroups,
990 "prefiltered" => ParallelStrategy::Prefiltered,
991 "none" => ParallelStrategy::None,
992 v => {
993 return Err(PyValueError::new_err(format!(
994 "`parallel` must be one of {{'auto', 'columns', 'row_groups', 'prefiltered', 'none'}}, got {v}",
995 )));
996 },
997 };
998 Ok(Wrap(parsed))
999 }
1000}
1001
1002impl<'py> FromPyObject<'py> for Wrap<IndexOrder> {
1003 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1004 let parsed = match &*ob.extract::<PyBackedStr>()? {
1005 "fortran" => IndexOrder::Fortran,
1006 "c" => IndexOrder::C,
1007 v => {
1008 return Err(PyValueError::new_err(format!(
1009 "`order` must be one of {{'fortran', 'c'}}, got {v}",
1010 )));
1011 },
1012 };
1013 Ok(Wrap(parsed))
1014 }
1015}
1016
1017impl<'py> FromPyObject<'py> for Wrap<QuantileMethod> {
1018 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1019 let parsed = match &*ob.extract::<PyBackedStr>()? {
1020 "lower" => QuantileMethod::Lower,
1021 "higher" => QuantileMethod::Higher,
1022 "nearest" => QuantileMethod::Nearest,
1023 "linear" => QuantileMethod::Linear,
1024 "midpoint" => QuantileMethod::Midpoint,
1025 "equiprobable" => QuantileMethod::Equiprobable,
1026 v => {
1027 return Err(PyValueError::new_err(format!(
1028 "`interpolation` must be one of {{'lower', 'higher', 'nearest', 'linear', 'midpoint', 'equiprobable'}}, got {v}",
1029 )));
1030 },
1031 };
1032 Ok(Wrap(parsed))
1033 }
1034}
1035
1036impl<'py> FromPyObject<'py> for Wrap<RankMethod> {
1037 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1038 let parsed = match &*ob.extract::<PyBackedStr>()? {
1039 "min" => RankMethod::Min,
1040 "max" => RankMethod::Max,
1041 "average" => RankMethod::Average,
1042 "dense" => RankMethod::Dense,
1043 "ordinal" => RankMethod::Ordinal,
1044 "random" => RankMethod::Random,
1045 v => {
1046 return Err(PyValueError::new_err(format!(
1047 "rank `method` must be one of {{'min', 'max', 'average', 'dense', 'ordinal', 'random'}}, got {v}",
1048 )));
1049 },
1050 };
1051 Ok(Wrap(parsed))
1052 }
1053}
1054
1055impl<'py> FromPyObject<'py> for Wrap<Roll> {
1056 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1057 let parsed = match &*ob.extract::<PyBackedStr>()? {
1058 "raise" => Roll::Raise,
1059 "forward" => Roll::Forward,
1060 "backward" => Roll::Backward,
1061 v => {
1062 return Err(PyValueError::new_err(format!(
1063 "`roll` must be one of {{'raise', 'forward', 'backward'}}, got {v}",
1064 )));
1065 },
1066 };
1067 Ok(Wrap(parsed))
1068 }
1069}
1070
1071impl<'py> FromPyObject<'py> for Wrap<TimeUnit> {
1072 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1073 let parsed = match &*ob.extract::<PyBackedStr>()? {
1074 "ns" => TimeUnit::Nanoseconds,
1075 "us" => TimeUnit::Microseconds,
1076 "ms" => TimeUnit::Milliseconds,
1077 v => {
1078 return Err(PyValueError::new_err(format!(
1079 "`time_unit` must be one of {{'ns', 'us', 'ms'}}, got {v}",
1080 )));
1081 },
1082 };
1083 Ok(Wrap(parsed))
1084 }
1085}
1086
1087impl<'py> FromPyObject<'py> for Wrap<UniqueKeepStrategy> {
1088 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1089 let parsed = match &*ob.extract::<PyBackedStr>()? {
1090 "first" => UniqueKeepStrategy::First,
1091 "last" => UniqueKeepStrategy::Last,
1092 "none" => UniqueKeepStrategy::None,
1093 "any" => UniqueKeepStrategy::Any,
1094 v => {
1095 return Err(PyValueError::new_err(format!(
1096 "`keep` must be one of {{'first', 'last', 'any', 'none'}}, got {v}",
1097 )));
1098 },
1099 };
1100 Ok(Wrap(parsed))
1101 }
1102}
1103
1104#[cfg(feature = "search_sorted")]
1105impl<'py> FromPyObject<'py> for Wrap<SearchSortedSide> {
1106 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1107 let parsed = match &*ob.extract::<PyBackedStr>()? {
1108 "any" => SearchSortedSide::Any,
1109 "left" => SearchSortedSide::Left,
1110 "right" => SearchSortedSide::Right,
1111 v => {
1112 return Err(PyValueError::new_err(format!(
1113 "sorted `side` must be one of {{'any', 'left', 'right'}}, got {v}",
1114 )));
1115 },
1116 };
1117 Ok(Wrap(parsed))
1118 }
1119}
1120
1121impl<'py> FromPyObject<'py> for Wrap<ClosedInterval> {
1122 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1123 let parsed = match &*ob.extract::<PyBackedStr>()? {
1124 "both" => ClosedInterval::Both,
1125 "left" => ClosedInterval::Left,
1126 "right" => ClosedInterval::Right,
1127 "none" => ClosedInterval::None,
1128 v => {
1129 return Err(PyValueError::new_err(format!(
1130 "`closed` must be one of {{'both', 'left', 'right', 'none'}}, got {v}",
1131 )));
1132 },
1133 };
1134 Ok(Wrap(parsed))
1135 }
1136}
1137
1138impl<'py> FromPyObject<'py> for Wrap<WindowMapping> {
1139 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1140 let parsed = match &*ob.extract::<PyBackedStr>()? {
1141 "group_to_rows" => WindowMapping::GroupsToRows,
1142 "join" => WindowMapping::Join,
1143 "explode" => WindowMapping::Explode,
1144 v => {
1145 return Err(PyValueError::new_err(format!(
1146 "`mapping_strategy` must be one of {{'group_to_rows', 'join', 'explode'}}, got {v}",
1147 )));
1148 },
1149 };
1150 Ok(Wrap(parsed))
1151 }
1152}
1153
1154impl<'py> FromPyObject<'py> for Wrap<JoinValidation> {
1155 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1156 let parsed = match &*ob.extract::<PyBackedStr>()? {
1157 "1:1" => JoinValidation::OneToOne,
1158 "1:m" => JoinValidation::OneToMany,
1159 "m:m" => JoinValidation::ManyToMany,
1160 "m:1" => JoinValidation::ManyToOne,
1161 v => {
1162 return Err(PyValueError::new_err(format!(
1163 "`validate` must be one of {{'m:m', 'm:1', '1:m', '1:1'}}, got {v}",
1164 )));
1165 },
1166 };
1167 Ok(Wrap(parsed))
1168 }
1169}
1170
1171impl<'py> FromPyObject<'py> for Wrap<MaintainOrderJoin> {
1172 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1173 let parsed = match &*ob.extract::<PyBackedStr>()? {
1174 "none" => MaintainOrderJoin::None,
1175 "left" => MaintainOrderJoin::Left,
1176 "right" => MaintainOrderJoin::Right,
1177 "left_right" => MaintainOrderJoin::LeftRight,
1178 "right_left" => MaintainOrderJoin::RightLeft,
1179 v => {
1180 return Err(PyValueError::new_err(format!(
1181 "`maintain_order` must be one of {{'none', 'left', 'right', 'left_right', 'right_left'}}, got {v}",
1182 )));
1183 },
1184 };
1185 Ok(Wrap(parsed))
1186 }
1187}
1188
1189#[cfg(feature = "csv")]
1190impl<'py> FromPyObject<'py> for Wrap<QuoteStyle> {
1191 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1192 let parsed = match &*ob.extract::<PyBackedStr>()? {
1193 "always" => QuoteStyle::Always,
1194 "necessary" => QuoteStyle::Necessary,
1195 "non_numeric" => QuoteStyle::NonNumeric,
1196 "never" => QuoteStyle::Never,
1197 v => {
1198 return Err(PyValueError::new_err(format!(
1199 "`quote_style` must be one of {{'always', 'necessary', 'non_numeric', 'never'}}, got {v}",
1200 )));
1201 },
1202 };
1203 Ok(Wrap(parsed))
1204 }
1205}
1206
1207#[cfg(feature = "cloud")]
1208pub(crate) fn parse_cloud_options(uri: &str, kv: Vec<(String, String)>) -> PyResult<CloudOptions> {
1209 let out = CloudOptions::from_untyped_config(uri, kv).map_err(PyPolarsErr::from)?;
1210 Ok(out)
1211}
1212
1213#[cfg(feature = "list_sets")]
1214impl<'py> FromPyObject<'py> for Wrap<SetOperation> {
1215 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1216 let parsed = match &*ob.extract::<PyBackedStr>()? {
1217 "union" => SetOperation::Union,
1218 "difference" => SetOperation::Difference,
1219 "intersection" => SetOperation::Intersection,
1220 "symmetric_difference" => SetOperation::SymmetricDifference,
1221 v => {
1222 return Err(PyValueError::new_err(format!(
1223 "set operation must be one of {{'union', 'difference', 'intersection', 'symmetric_difference'}}, got {v}",
1224 )));
1225 },
1226 };
1227 Ok(Wrap(parsed))
1228 }
1229}
1230
1231pub(crate) fn parse_fill_null_strategy(
1232 strategy: &str,
1233 limit: FillNullLimit,
1234) -> PyResult<FillNullStrategy> {
1235 let parsed = match strategy {
1236 "forward" => FillNullStrategy::Forward(limit),
1237 "backward" => FillNullStrategy::Backward(limit),
1238 "min" => FillNullStrategy::Min,
1239 "max" => FillNullStrategy::Max,
1240 "mean" => FillNullStrategy::Mean,
1241 "zero" => FillNullStrategy::Zero,
1242 "one" => FillNullStrategy::One,
1243 e => {
1244 return Err(PyValueError::new_err(format!(
1245 "`strategy` must be one of {{'forward', 'backward', 'min', 'max', 'mean', 'zero', 'one'}}, got {e}",
1246 )));
1247 },
1248 };
1249 Ok(parsed)
1250}
1251
1252#[cfg(feature = "parquet")]
1253pub(crate) fn parse_parquet_compression(
1254 compression: &str,
1255 compression_level: Option<i32>,
1256) -> PyResult<ParquetCompression> {
1257 let parsed = match compression {
1258 "uncompressed" => ParquetCompression::Uncompressed,
1259 "snappy" => ParquetCompression::Snappy,
1260 "gzip" => ParquetCompression::Gzip(
1261 compression_level
1262 .map(|lvl| {
1263 GzipLevel::try_new(lvl as u8)
1264 .map_err(|e| PyValueError::new_err(format!("{e:?}")))
1265 })
1266 .transpose()?,
1267 ),
1268 "lzo" => ParquetCompression::Lzo,
1269 "brotli" => ParquetCompression::Brotli(
1270 compression_level
1271 .map(|lvl| {
1272 BrotliLevel::try_new(lvl as u32)
1273 .map_err(|e| PyValueError::new_err(format!("{e:?}")))
1274 })
1275 .transpose()?,
1276 ),
1277 "lz4" => ParquetCompression::Lz4Raw,
1278 "zstd" => ParquetCompression::Zstd(
1279 compression_level
1280 .map(|lvl| {
1281 ZstdLevel::try_new(lvl).map_err(|e| PyValueError::new_err(format!("{e:?}")))
1282 })
1283 .transpose()?,
1284 ),
1285 e => {
1286 return Err(PyValueError::new_err(format!(
1287 "parquet `compression` must be one of {{'uncompressed', 'snappy', 'gzip', 'lzo', 'brotli', 'lz4', 'zstd'}}, got {e}",
1288 )));
1289 },
1290 };
1291 Ok(parsed)
1292}
1293
1294pub(crate) fn strings_to_pl_smallstr<I, S>(container: I) -> Vec<PlSmallStr>
1295where
1296 I: IntoIterator<Item = S>,
1297 S: AsRef<str>,
1298{
1299 container
1300 .into_iter()
1301 .map(|s| PlSmallStr::from_str(s.as_ref()))
1302 .collect()
1303}
1304
1305#[derive(Debug, Copy, Clone)]
1306pub struct PyCompatLevel(pub CompatLevel);
1307
1308impl<'a> FromPyObject<'a> for PyCompatLevel {
1309 fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
1310 Ok(PyCompatLevel(if let Ok(level) = ob.extract::<u16>() {
1311 if let Ok(compat_level) = CompatLevel::with_level(level) {
1312 compat_level
1313 } else {
1314 return Err(PyValueError::new_err("invalid compat level"));
1315 }
1316 } else if let Ok(future) = ob.extract::<bool>() {
1317 if future {
1318 CompatLevel::newest()
1319 } else {
1320 CompatLevel::oldest()
1321 }
1322 } else {
1323 return Err(PyTypeError::new_err(
1324 "'compat_level' argument accepts int or bool",
1325 ));
1326 }))
1327 }
1328}
1329
1330#[cfg(feature = "string_normalize")]
1331impl<'py> FromPyObject<'py> for Wrap<UnicodeForm> {
1332 fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
1333 let parsed = match &*ob.extract::<PyBackedStr>()? {
1334 "NFC" => UnicodeForm::NFC,
1335 "NFKC" => UnicodeForm::NFKC,
1336 "NFD" => UnicodeForm::NFD,
1337 "NFKD" => UnicodeForm::NFKD,
1338 v => {
1339 return Err(PyValueError::new_err(format!(
1340 "`form` must be one of {{'NFC', 'NFKC', 'NFD', 'NFKD'}}, got {v}",
1341 )));
1342 },
1343 };
1344 Ok(Wrap(parsed))
1345 }
1346}