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