polars_python/lazyframe/visitor/
expr_nodes.rs

1#[cfg(feature = "iejoin")]
2use polars::prelude::InequalityOperator;
3use polars::series::ops::NullBehavior;
4use polars_core::series::IsSorted;
5#[cfg(feature = "string_normalize")]
6use polars_ops::chunked_array::UnicodeForm;
7use polars_ops::series::InterpolationMethod;
8#[cfg(feature = "search_sorted")]
9use polars_ops::series::SearchSortedSide;
10use polars_plan::dsl::function_expr::rolling_by::RollingFunctionBy;
11use polars_plan::dsl::{BooleanFunction, StringFunction, TemporalFunction};
12use polars_plan::plans::DynLiteralValue;
13use polars_plan::prelude::{
14    AExpr, FunctionExpr, GroupbyOptions, IRAggExpr, LiteralValue, Operator, PowFunction,
15    WindowMapping, WindowType,
16};
17use polars_time::prelude::RollingGroupOptions;
18use polars_time::{Duration, DynamicGroupOptions};
19use pyo3::IntoPyObjectExt;
20use pyo3::exceptions::PyNotImplementedError;
21use pyo3::prelude::*;
22use pyo3::types::PyTuple;
23
24use crate::Wrap;
25use crate::series::PySeries;
26
27#[pyclass]
28pub struct Alias {
29    #[pyo3(get)]
30    expr: usize,
31    #[pyo3(get)]
32    name: PyObject,
33}
34
35#[pyclass]
36pub struct Column {
37    #[pyo3(get)]
38    name: PyObject,
39}
40
41#[pyclass]
42pub struct Literal {
43    #[pyo3(get)]
44    value: PyObject,
45    #[pyo3(get)]
46    dtype: PyObject,
47}
48
49#[pyclass(name = "Operator", eq)]
50#[derive(Copy, Clone, PartialEq)]
51pub enum PyOperator {
52    Eq,
53    EqValidity,
54    NotEq,
55    NotEqValidity,
56    Lt,
57    LtEq,
58    Gt,
59    GtEq,
60    Plus,
61    Minus,
62    Multiply,
63    Divide,
64    TrueDivide,
65    FloorDivide,
66    Modulus,
67    And,
68    Or,
69    Xor,
70    LogicalAnd,
71    LogicalOr,
72}
73
74#[pymethods]
75impl PyOperator {
76    fn __hash__(&self) -> isize {
77        *self as isize
78    }
79}
80
81impl<'py> IntoPyObject<'py> for Wrap<Operator> {
82    type Target = PyOperator;
83    type Output = Bound<'py, Self::Target>;
84    type Error = PyErr;
85
86    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
87        match self.0 {
88            Operator::Eq => PyOperator::Eq,
89            Operator::EqValidity => PyOperator::EqValidity,
90            Operator::NotEq => PyOperator::NotEq,
91            Operator::NotEqValidity => PyOperator::NotEqValidity,
92            Operator::Lt => PyOperator::Lt,
93            Operator::LtEq => PyOperator::LtEq,
94            Operator::Gt => PyOperator::Gt,
95            Operator::GtEq => PyOperator::GtEq,
96            Operator::Plus => PyOperator::Plus,
97            Operator::Minus => PyOperator::Minus,
98            Operator::Multiply => PyOperator::Multiply,
99            Operator::Divide => PyOperator::Divide,
100            Operator::TrueDivide => PyOperator::TrueDivide,
101            Operator::FloorDivide => PyOperator::FloorDivide,
102            Operator::Modulus => PyOperator::Modulus,
103            Operator::And => PyOperator::And,
104            Operator::Or => PyOperator::Or,
105            Operator::Xor => PyOperator::Xor,
106            Operator::LogicalAnd => PyOperator::LogicalAnd,
107            Operator::LogicalOr => PyOperator::LogicalOr,
108        }
109        .into_pyobject(py)
110    }
111}
112
113#[cfg(feature = "iejoin")]
114impl<'py> IntoPyObject<'py> for Wrap<InequalityOperator> {
115    type Target = PyOperator;
116    type Output = Bound<'py, Self::Target>;
117    type Error = PyErr;
118
119    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
120        match self.0 {
121            InequalityOperator::Lt => PyOperator::Lt,
122            InequalityOperator::LtEq => PyOperator::LtEq,
123            InequalityOperator::Gt => PyOperator::Gt,
124            InequalityOperator::GtEq => PyOperator::GtEq,
125        }
126        .into_pyobject(py)
127    }
128}
129
130#[pyclass(name = "StringFunction", eq)]
131#[derive(Copy, Clone, PartialEq)]
132pub enum PyStringFunction {
133    ConcatHorizontal,
134    ConcatVertical,
135    Contains,
136    CountMatches,
137    EndsWith,
138    Extract,
139    ExtractAll,
140    ExtractGroups,
141    Find,
142    ToInteger,
143    LenBytes,
144    LenChars,
145    Lowercase,
146    JsonDecode,
147    JsonPathMatch,
148    Replace,
149    Reverse,
150    PadStart,
151    PadEnd,
152    Slice,
153    Head,
154    Tail,
155    HexEncode,
156    HexDecode,
157    Base64Encode,
158    Base64Decode,
159    StartsWith,
160    StripChars,
161    StripCharsStart,
162    StripCharsEnd,
163    StripPrefix,
164    StripSuffix,
165    SplitExact,
166    SplitN,
167    Strptime,
168    Split,
169    ToDecimal,
170    Titlecase,
171    Uppercase,
172    ZFill,
173    ContainsAny,
174    ReplaceMany,
175    EscapeRegex,
176    Normalize,
177}
178
179#[pymethods]
180impl PyStringFunction {
181    fn __hash__(&self) -> isize {
182        *self as isize
183    }
184}
185
186#[pyclass(name = "BooleanFunction", eq)]
187#[derive(Copy, Clone, PartialEq)]
188pub enum PyBooleanFunction {
189    Any,
190    All,
191    IsNull,
192    IsNotNull,
193    IsFinite,
194    IsInfinite,
195    IsNan,
196    IsNotNan,
197    IsFirstDistinct,
198    IsLastDistinct,
199    IsUnique,
200    IsDuplicated,
201    IsBetween,
202    IsIn,
203    AllHorizontal,
204    AnyHorizontal,
205    Not,
206}
207
208#[pymethods]
209impl PyBooleanFunction {
210    fn __hash__(&self) -> isize {
211        *self as isize
212    }
213}
214
215#[pyclass(name = "TemporalFunction", eq)]
216#[derive(Copy, Clone, PartialEq)]
217pub enum PyTemporalFunction {
218    Millennium,
219    Century,
220    Year,
221    IsLeapYear,
222    IsoYear,
223    Quarter,
224    Month,
225    Week,
226    WeekDay,
227    Day,
228    OrdinalDay,
229    Time,
230    Date,
231    Datetime,
232    Duration,
233    Hour,
234    Minute,
235    Second,
236    Millisecond,
237    Microsecond,
238    Nanosecond,
239    TotalDays,
240    TotalHours,
241    TotalMinutes,
242    TotalSeconds,
243    TotalMilliseconds,
244    TotalMicroseconds,
245    TotalNanoseconds,
246    ToString,
247    CastTimeUnit,
248    WithTimeUnit,
249    ConvertTimeZone,
250    TimeStamp,
251    Truncate,
252    OffsetBy,
253    MonthStart,
254    MonthEnd,
255    BaseUtcOffset,
256    DSTOffset,
257    Round,
258    Replace,
259    ReplaceTimeZone,
260    Combine,
261    DatetimeFunction,
262}
263
264#[pymethods]
265impl PyTemporalFunction {
266    fn __hash__(&self) -> isize {
267        *self as isize
268    }
269}
270
271#[pyclass]
272pub struct BinaryExpr {
273    #[pyo3(get)]
274    left: usize,
275    #[pyo3(get)]
276    op: PyObject,
277    #[pyo3(get)]
278    right: usize,
279}
280
281#[pyclass]
282pub struct Cast {
283    #[pyo3(get)]
284    expr: usize,
285    #[pyo3(get)]
286    dtype: PyObject,
287    // 0: strict
288    // 1: non-strict
289    // 2: overflow
290    #[pyo3(get)]
291    options: u8,
292}
293
294#[pyclass]
295pub struct Sort {
296    #[pyo3(get)]
297    expr: usize,
298    #[pyo3(get)]
299    /// maintain_order, nulls_last, descending
300    options: (bool, bool, bool),
301}
302
303#[pyclass]
304pub struct Gather {
305    #[pyo3(get)]
306    expr: usize,
307    #[pyo3(get)]
308    idx: usize,
309    #[pyo3(get)]
310    scalar: bool,
311}
312
313#[pyclass]
314pub struct Filter {
315    #[pyo3(get)]
316    input: usize,
317    #[pyo3(get)]
318    by: usize,
319}
320
321#[pyclass]
322pub struct SortBy {
323    #[pyo3(get)]
324    expr: usize,
325    #[pyo3(get)]
326    by: Vec<usize>,
327    #[pyo3(get)]
328    /// maintain_order, nulls_last, descending
329    sort_options: (bool, Vec<bool>, Vec<bool>),
330}
331
332#[pyclass]
333pub struct Agg {
334    #[pyo3(get)]
335    name: PyObject,
336    #[pyo3(get)]
337    arguments: Vec<usize>,
338    #[pyo3(get)]
339    // Arbitrary control options
340    options: PyObject,
341}
342
343#[pyclass]
344pub struct Ternary {
345    #[pyo3(get)]
346    predicate: usize,
347    #[pyo3(get)]
348    truthy: usize,
349    #[pyo3(get)]
350    falsy: usize,
351}
352
353#[pyclass]
354pub struct Function {
355    #[pyo3(get)]
356    input: Vec<usize>,
357    #[pyo3(get)]
358    function_data: PyObject,
359    #[pyo3(get)]
360    options: PyObject,
361}
362
363#[pyclass]
364pub struct Slice {
365    #[pyo3(get)]
366    input: usize,
367    #[pyo3(get)]
368    offset: usize,
369    #[pyo3(get)]
370    length: usize,
371}
372
373#[pyclass]
374pub struct Len {}
375
376#[pyclass]
377pub struct Window {
378    #[pyo3(get)]
379    function: usize,
380    #[pyo3(get)]
381    partition_by: Vec<usize>,
382    #[pyo3(get)]
383    order_by: Option<usize>,
384    #[pyo3(get)]
385    order_by_descending: bool,
386    #[pyo3(get)]
387    order_by_nulls_last: bool,
388    #[pyo3(get)]
389    options: PyObject,
390}
391
392#[pyclass(name = "WindowMapping")]
393pub struct PyWindowMapping {
394    inner: WindowMapping,
395}
396
397#[pymethods]
398impl PyWindowMapping {
399    #[getter]
400    fn kind(&self) -> &str {
401        self.inner.into()
402    }
403}
404
405impl<'py> IntoPyObject<'py> for Wrap<Duration> {
406    type Target = PyTuple;
407    type Output = Bound<'py, Self::Target>;
408    type Error = PyErr;
409
410    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
411        (
412            self.0.months(),
413            self.0.weeks(),
414            self.0.days(),
415            self.0.nanoseconds(),
416            self.0.parsed_int,
417            self.0.negative(),
418        )
419            .into_pyobject(py)
420    }
421}
422
423#[pyclass(name = "RollingGroupOptions")]
424pub struct PyRollingGroupOptions {
425    inner: RollingGroupOptions,
426}
427
428#[pymethods]
429impl PyRollingGroupOptions {
430    #[getter]
431    fn index_column(&self) -> &str {
432        self.inner.index_column.as_str()
433    }
434
435    #[getter]
436    fn period(&self) -> Wrap<Duration> {
437        Wrap(self.inner.period)
438    }
439
440    #[getter]
441    fn offset(&self) -> Wrap<Duration> {
442        Wrap(self.inner.offset)
443    }
444
445    #[getter]
446    fn closed_window(&self) -> &str {
447        self.inner.closed_window.into()
448    }
449}
450
451#[pyclass(name = "DynamicGroupOptions")]
452pub struct PyDynamicGroupOptions {
453    inner: DynamicGroupOptions,
454}
455
456#[pymethods]
457impl PyDynamicGroupOptions {
458    #[getter]
459    fn index_column(&self) -> &str {
460        self.inner.index_column.as_str()
461    }
462
463    #[getter]
464    fn every(&self) -> Wrap<Duration> {
465        Wrap(self.inner.every)
466    }
467
468    #[getter]
469    fn period(&self) -> Wrap<Duration> {
470        Wrap(self.inner.period)
471    }
472
473    #[getter]
474    fn offset(&self) -> Wrap<Duration> {
475        Wrap(self.inner.offset)
476    }
477
478    #[getter]
479    fn label(&self) -> &str {
480        self.inner.label.into()
481    }
482
483    #[getter]
484    fn include_boundaries(&self) -> bool {
485        self.inner.include_boundaries
486    }
487
488    #[getter]
489    fn closed_window(&self) -> &str {
490        self.inner.closed_window.into()
491    }
492    #[getter]
493    fn start_by(&self) -> &str {
494        self.inner.start_by.into()
495    }
496}
497
498#[pyclass(name = "GroupbyOptions")]
499pub struct PyGroupbyOptions {
500    inner: GroupbyOptions,
501}
502
503impl PyGroupbyOptions {
504    pub(crate) fn new(inner: GroupbyOptions) -> Self {
505        Self { inner }
506    }
507}
508
509#[pymethods]
510impl PyGroupbyOptions {
511    #[getter]
512    fn slice(&self) -> Option<(i64, usize)> {
513        self.inner.slice
514    }
515
516    #[getter]
517    fn dynamic(&self) -> Option<PyDynamicGroupOptions> {
518        self.inner
519            .dynamic
520            .as_ref()
521            .map(|f| PyDynamicGroupOptions { inner: f.clone() })
522    }
523
524    #[getter]
525    fn rolling(&self) -> Option<PyRollingGroupOptions> {
526        self.inner
527            .rolling
528            .as_ref()
529            .map(|f| PyRollingGroupOptions { inner: f.clone() })
530    }
531}
532
533pub(crate) fn into_py(py: Python<'_>, expr: &AExpr) -> PyResult<PyObject> {
534    match expr {
535        AExpr::Explode { .. } => Err(PyNotImplementedError::new_err("explode")),
536        AExpr::Alias(inner, name) => Alias {
537            expr: inner.0,
538            name: name.into_py_any(py)?,
539        }
540        .into_py_any(py),
541        AExpr::Column(name) => Column {
542            name: name.into_py_any(py)?,
543        }
544        .into_py_any(py),
545        AExpr::Literal(lit) => {
546            use polars_core::prelude::AnyValue;
547            let dtype: PyObject = Wrap(lit.get_datatype()).into_py_any(py)?;
548            let py_value = match lit {
549                LiteralValue::Dyn(d) => match d {
550                    DynLiteralValue::Int(v) => v.into_py_any(py)?,
551                    DynLiteralValue::Float(v) => v.into_py_any(py)?,
552                    DynLiteralValue::Str(v) => v.into_py_any(py)?,
553                    DynLiteralValue::List(_) => todo!(),
554                },
555                LiteralValue::Scalar(sc) => {
556                    match sc.as_any_value() {
557                        // AnyValue conversion of duration to python's
558                        // datetime.timedelta drops nanoseconds because
559                        // there is no support for them. See
560                        // https://github.com/python/cpython/issues/59648
561                        AnyValue::Duration(delta, _) => delta.into_py_any(py)?,
562                        any => Wrap(any).into_py_any(py)?,
563                    }
564                },
565                LiteralValue::Range(_) => {
566                    return Err(PyNotImplementedError::new_err("range literal"));
567                },
568                LiteralValue::Series(s) => PySeries::new((**s).clone()).into_py_any(py)?,
569            };
570
571            Literal {
572                value: py_value,
573                dtype,
574            }
575        }
576        .into_py_any(py),
577        AExpr::BinaryExpr { left, op, right } => BinaryExpr {
578            left: left.0,
579            op: Wrap(*op).into_py_any(py)?,
580            right: right.0,
581        }
582        .into_py_any(py),
583        AExpr::Cast {
584            expr,
585            dtype,
586            options,
587        } => Cast {
588            expr: expr.0,
589            dtype: Wrap(dtype.clone()).into_py_any(py)?,
590            options: *options as u8,
591        }
592        .into_py_any(py),
593        AExpr::Sort { expr, options } => Sort {
594            expr: expr.0,
595            options: (
596                options.maintain_order,
597                options.nulls_last,
598                options.descending,
599            ),
600        }
601        .into_py_any(py),
602        AExpr::Gather {
603            expr,
604            idx,
605            returns_scalar,
606        } => Gather {
607            expr: expr.0,
608            idx: idx.0,
609            scalar: *returns_scalar,
610        }
611        .into_py_any(py),
612        AExpr::Filter { input, by } => Filter {
613            input: input.0,
614            by: by.0,
615        }
616        .into_py_any(py),
617        AExpr::SortBy {
618            expr,
619            by,
620            sort_options,
621        } => SortBy {
622            expr: expr.0,
623            by: by.iter().map(|n| n.0).collect(),
624            sort_options: (
625                sort_options.maintain_order,
626                sort_options.nulls_last.clone(),
627                sort_options.descending.clone(),
628            ),
629        }
630        .into_py_any(py),
631        AExpr::Agg(aggexpr) => match aggexpr {
632            IRAggExpr::Min {
633                input,
634                propagate_nans,
635            } => Agg {
636                name: "min".into_py_any(py)?,
637                arguments: vec![input.0],
638                options: propagate_nans.into_py_any(py)?,
639            },
640            IRAggExpr::Max {
641                input,
642                propagate_nans,
643            } => Agg {
644                name: "max".into_py_any(py)?,
645                arguments: vec![input.0],
646                options: propagate_nans.into_py_any(py)?,
647            },
648            IRAggExpr::Median(n) => Agg {
649                name: "median".into_py_any(py)?,
650                arguments: vec![n.0],
651                options: py.None(),
652            },
653            IRAggExpr::NUnique(n) => Agg {
654                name: "n_unique".into_py_any(py)?,
655                arguments: vec![n.0],
656                options: py.None(),
657            },
658            IRAggExpr::First(n) => Agg {
659                name: "first".into_py_any(py)?,
660                arguments: vec![n.0],
661                options: py.None(),
662            },
663            IRAggExpr::Last(n) => Agg {
664                name: "last".into_py_any(py)?,
665                arguments: vec![n.0],
666                options: py.None(),
667            },
668            IRAggExpr::Mean(n) => Agg {
669                name: "mean".into_py_any(py)?,
670                arguments: vec![n.0],
671                options: py.None(),
672            },
673            IRAggExpr::Implode(n) => Agg {
674                name: "implode".into_py_any(py)?,
675                arguments: vec![n.0],
676                options: py.None(),
677            },
678            IRAggExpr::Quantile {
679                expr,
680                quantile,
681                method: interpol,
682            } => Agg {
683                name: "quantile".into_py_any(py)?,
684                arguments: vec![expr.0, quantile.0],
685                options: Into::<&str>::into(interpol).into_py_any(py)?,
686            },
687            IRAggExpr::Sum(n) => Agg {
688                name: "sum".into_py_any(py)?,
689                arguments: vec![n.0],
690                options: py.None(),
691            },
692            IRAggExpr::Count(n, include_null) => Agg {
693                name: "count".into_py_any(py)?,
694                arguments: vec![n.0],
695                options: include_null.into_py_any(py)?,
696            },
697            IRAggExpr::Std(n, ddof) => Agg {
698                name: "std".into_py_any(py)?,
699                arguments: vec![n.0],
700                options: ddof.into_py_any(py)?,
701            },
702            IRAggExpr::Var(n, ddof) => Agg {
703                name: "var".into_py_any(py)?,
704                arguments: vec![n.0],
705                options: ddof.into_py_any(py)?,
706            },
707            IRAggExpr::AggGroups(n) => Agg {
708                name: "agg_groups".into_py_any(py)?,
709                arguments: vec![n.0],
710                options: py.None(),
711            },
712        }
713        .into_py_any(py),
714        AExpr::Ternary {
715            predicate,
716            truthy,
717            falsy,
718        } => Ternary {
719            predicate: predicate.0,
720            truthy: truthy.0,
721            falsy: falsy.0,
722        }
723        .into_py_any(py),
724        AExpr::AnonymousFunction { .. } => Err(PyNotImplementedError::new_err("anonymousfunction")),
725        AExpr::Function {
726            input,
727            function,
728            // TODO: expose options
729            options: _,
730        } => Function {
731            input: input.iter().map(|n| n.node().0).collect(),
732            function_data: match function {
733                FunctionExpr::ArrayExpr(_) => {
734                    return Err(PyNotImplementedError::new_err("array expr"));
735                },
736                FunctionExpr::BinaryExpr(_) => {
737                    return Err(PyNotImplementedError::new_err("binary expr"));
738                },
739                FunctionExpr::Categorical(_) => {
740                    return Err(PyNotImplementedError::new_err("categorical expr"));
741                },
742                FunctionExpr::ListExpr(_) => {
743                    return Err(PyNotImplementedError::new_err("list expr"));
744                },
745                FunctionExpr::Bitwise(_) => {
746                    return Err(PyNotImplementedError::new_err("bitwise expr"));
747                },
748                FunctionExpr::StringExpr(strfun) => match strfun {
749                    StringFunction::ConcatHorizontal {
750                        delimiter,
751                        ignore_nulls,
752                    } => (
753                        PyStringFunction::ConcatHorizontal,
754                        delimiter.as_str(),
755                        ignore_nulls,
756                    )
757                        .into_py_any(py),
758                    StringFunction::ConcatVertical {
759                        delimiter,
760                        ignore_nulls,
761                    } => (
762                        PyStringFunction::ConcatVertical,
763                        delimiter.as_str(),
764                        ignore_nulls,
765                    )
766                        .into_py_any(py),
767                    #[cfg(feature = "regex")]
768                    StringFunction::Contains { literal, strict } => {
769                        (PyStringFunction::Contains, literal, strict).into_py_any(py)
770                    },
771                    StringFunction::CountMatches(literal) => {
772                        (PyStringFunction::CountMatches, literal).into_py_any(py)
773                    },
774                    StringFunction::EndsWith => (PyStringFunction::EndsWith,).into_py_any(py),
775                    StringFunction::Extract(group_index) => {
776                        (PyStringFunction::Extract, group_index).into_py_any(py)
777                    },
778                    StringFunction::ExtractAll => (PyStringFunction::ExtractAll,).into_py_any(py),
779                    #[cfg(feature = "extract_groups")]
780                    StringFunction::ExtractGroups { dtype, pat } => (
781                        PyStringFunction::ExtractGroups,
782                        &Wrap(dtype.clone()),
783                        pat.as_str(),
784                    )
785                        .into_py_any(py),
786                    #[cfg(feature = "regex")]
787                    StringFunction::Find { literal, strict } => {
788                        (PyStringFunction::Find, literal, strict).into_py_any(py)
789                    },
790                    StringFunction::ToInteger(strict) => {
791                        (PyStringFunction::ToInteger, strict).into_py_any(py)
792                    },
793                    StringFunction::LenBytes => (PyStringFunction::LenBytes,).into_py_any(py),
794                    StringFunction::LenChars => (PyStringFunction::LenChars,).into_py_any(py),
795                    StringFunction::Lowercase => (PyStringFunction::Lowercase,).into_py_any(py),
796                    #[cfg(feature = "extract_jsonpath")]
797                    StringFunction::JsonDecode {
798                        dtype: _,
799                        infer_schema_len,
800                    } => (PyStringFunction::JsonDecode, infer_schema_len).into_py_any(py),
801                    #[cfg(feature = "extract_jsonpath")]
802                    StringFunction::JsonPathMatch => {
803                        (PyStringFunction::JsonPathMatch,).into_py_any(py)
804                    },
805                    #[cfg(feature = "regex")]
806                    StringFunction::Replace { n, literal } => {
807                        (PyStringFunction::Replace, n, literal).into_py_any(py)
808                    },
809                    #[cfg(feature = "string_normalize")]
810                    StringFunction::Normalize { form } => (
811                        PyStringFunction::Normalize,
812                        match form {
813                            UnicodeForm::NFC => "nfc",
814                            UnicodeForm::NFKC => "nfkc",
815                            UnicodeForm::NFD => "nfd",
816                            UnicodeForm::NFKD => "nfkd",
817                        },
818                    )
819                        .into_py_any(py),
820                    StringFunction::Reverse => (PyStringFunction::Reverse,).into_py_any(py),
821                    StringFunction::PadStart { length, fill_char } => {
822                        (PyStringFunction::PadStart, length, fill_char).into_py_any(py)
823                    },
824                    StringFunction::PadEnd { length, fill_char } => {
825                        (PyStringFunction::PadEnd, length, fill_char).into_py_any(py)
826                    },
827                    StringFunction::Slice => (PyStringFunction::Slice,).into_py_any(py),
828                    StringFunction::Head => (PyStringFunction::Head,).into_py_any(py),
829                    StringFunction::Tail => (PyStringFunction::Tail,).into_py_any(py),
830                    StringFunction::HexEncode => (PyStringFunction::HexEncode,).into_py_any(py),
831                    #[cfg(feature = "binary_encoding")]
832                    StringFunction::HexDecode(strict) => {
833                        (PyStringFunction::HexDecode, strict).into_py_any(py)
834                    },
835                    StringFunction::Base64Encode => {
836                        (PyStringFunction::Base64Encode,).into_py_any(py)
837                    },
838                    #[cfg(feature = "binary_encoding")]
839                    StringFunction::Base64Decode(strict) => {
840                        (PyStringFunction::Base64Decode, strict).into_py_any(py)
841                    },
842                    StringFunction::StartsWith => (PyStringFunction::StartsWith,).into_py_any(py),
843                    StringFunction::StripChars => (PyStringFunction::StripChars,).into_py_any(py),
844                    StringFunction::StripCharsStart => {
845                        (PyStringFunction::StripCharsStart,).into_py_any(py)
846                    },
847                    StringFunction::StripCharsEnd => {
848                        (PyStringFunction::StripCharsEnd,).into_py_any(py)
849                    },
850                    StringFunction::StripPrefix => (PyStringFunction::StripPrefix,).into_py_any(py),
851                    StringFunction::StripSuffix => (PyStringFunction::StripSuffix,).into_py_any(py),
852                    StringFunction::SplitExact { n, inclusive } => {
853                        (PyStringFunction::SplitExact, n, inclusive).into_py_any(py)
854                    },
855                    StringFunction::SplitN(n) => (PyStringFunction::SplitN, n).into_py_any(py),
856                    StringFunction::Strptime(_, options) => (
857                        PyStringFunction::Strptime,
858                        options.format.as_ref().map(|s| s.as_str()),
859                        options.strict,
860                        options.exact,
861                        options.cache,
862                    )
863                        .into_py_any(py),
864                    StringFunction::Split(inclusive) => {
865                        (PyStringFunction::Split, inclusive).into_py_any(py)
866                    },
867                    StringFunction::ToDecimal(inference_length) => {
868                        (PyStringFunction::ToDecimal, inference_length).into_py_any(py)
869                    },
870                    #[cfg(feature = "nightly")]
871                    StringFunction::Titlecase => (PyStringFunction::Titlecase,).into_py_any(py),
872                    StringFunction::Uppercase => (PyStringFunction::Uppercase,).into_py_any(py),
873                    StringFunction::ZFill => (PyStringFunction::ZFill,).into_py_any(py),
874                    #[cfg(feature = "find_many")]
875                    StringFunction::ContainsAny {
876                        ascii_case_insensitive,
877                    } => (PyStringFunction::ContainsAny, ascii_case_insensitive).into_py_any(py),
878                    #[cfg(feature = "find_many")]
879                    StringFunction::ReplaceMany {
880                        ascii_case_insensitive,
881                    } => (PyStringFunction::ReplaceMany, ascii_case_insensitive).into_py_any(py),
882                    #[cfg(feature = "find_many")]
883                    StringFunction::ExtractMany { .. } => {
884                        return Err(PyNotImplementedError::new_err("extract_many"));
885                    },
886                    #[cfg(feature = "find_many")]
887                    StringFunction::FindMany { .. } => {
888                        return Err(PyNotImplementedError::new_err("find_many"));
889                    },
890                    #[cfg(feature = "regex")]
891                    StringFunction::EscapeRegex => (PyStringFunction::EscapeRegex,).into_py_any(py),
892                },
893                FunctionExpr::StructExpr(_) => {
894                    return Err(PyNotImplementedError::new_err("struct expr"));
895                },
896                FunctionExpr::TemporalExpr(fun) => match fun {
897                    TemporalFunction::Millennium => {
898                        (PyTemporalFunction::Millennium,).into_py_any(py)
899                    },
900                    TemporalFunction::Century => (PyTemporalFunction::Century,).into_py_any(py),
901                    TemporalFunction::Year => (PyTemporalFunction::Year,).into_py_any(py),
902                    TemporalFunction::IsLeapYear => {
903                        (PyTemporalFunction::IsLeapYear,).into_py_any(py)
904                    },
905                    TemporalFunction::IsoYear => (PyTemporalFunction::IsoYear,).into_py_any(py),
906                    TemporalFunction::Quarter => (PyTemporalFunction::Quarter,).into_py_any(py),
907                    TemporalFunction::Month => (PyTemporalFunction::Month,).into_py_any(py),
908                    TemporalFunction::Week => (PyTemporalFunction::Week,).into_py_any(py),
909                    TemporalFunction::WeekDay => (PyTemporalFunction::WeekDay,).into_py_any(py),
910                    TemporalFunction::Day => (PyTemporalFunction::Day,).into_py_any(py),
911                    TemporalFunction::OrdinalDay => {
912                        (PyTemporalFunction::OrdinalDay,).into_py_any(py)
913                    },
914                    TemporalFunction::Time => (PyTemporalFunction::Time,).into_py_any(py),
915                    TemporalFunction::Date => (PyTemporalFunction::Date,).into_py_any(py),
916                    TemporalFunction::Datetime => (PyTemporalFunction::Datetime,).into_py_any(py),
917                    TemporalFunction::Duration(time_unit) => {
918                        (PyTemporalFunction::Duration, Wrap(*time_unit)).into_py_any(py)
919                    },
920                    TemporalFunction::Hour => (PyTemporalFunction::Hour,).into_py_any(py),
921                    TemporalFunction::Minute => (PyTemporalFunction::Minute,).into_py_any(py),
922                    TemporalFunction::Second => (PyTemporalFunction::Second,).into_py_any(py),
923                    TemporalFunction::Millisecond => {
924                        (PyTemporalFunction::Millisecond,).into_py_any(py)
925                    },
926                    TemporalFunction::Microsecond => {
927                        (PyTemporalFunction::Microsecond,).into_py_any(py)
928                    },
929                    TemporalFunction::Nanosecond => {
930                        (PyTemporalFunction::Nanosecond,).into_py_any(py)
931                    },
932                    TemporalFunction::TotalDays => (PyTemporalFunction::TotalDays,).into_py_any(py),
933                    TemporalFunction::TotalHours => {
934                        (PyTemporalFunction::TotalHours,).into_py_any(py)
935                    },
936                    TemporalFunction::TotalMinutes => {
937                        (PyTemporalFunction::TotalMinutes,).into_py_any(py)
938                    },
939                    TemporalFunction::TotalSeconds => {
940                        (PyTemporalFunction::TotalSeconds,).into_py_any(py)
941                    },
942                    TemporalFunction::TotalMilliseconds => {
943                        (PyTemporalFunction::TotalMilliseconds,).into_py_any(py)
944                    },
945                    TemporalFunction::TotalMicroseconds => {
946                        (PyTemporalFunction::TotalMicroseconds,).into_py_any(py)
947                    },
948                    TemporalFunction::TotalNanoseconds => {
949                        (PyTemporalFunction::TotalNanoseconds,).into_py_any(py)
950                    },
951                    TemporalFunction::ToString(format) => {
952                        (PyTemporalFunction::ToString, format).into_py_any(py)
953                    },
954                    TemporalFunction::CastTimeUnit(time_unit) => {
955                        (PyTemporalFunction::CastTimeUnit, Wrap(*time_unit)).into_py_any(py)
956                    },
957                    TemporalFunction::WithTimeUnit(time_unit) => {
958                        (PyTemporalFunction::WithTimeUnit, Wrap(*time_unit)).into_py_any(py)
959                    },
960                    #[cfg(feature = "timezones")]
961                    TemporalFunction::ConvertTimeZone(time_zone) => {
962                        (PyTemporalFunction::ConvertTimeZone, time_zone.as_str()).into_py_any(py)
963                    },
964                    TemporalFunction::TimeStamp(time_unit) => {
965                        (PyTemporalFunction::TimeStamp, Wrap(*time_unit)).into_py_any(py)
966                    },
967                    TemporalFunction::Truncate => (PyTemporalFunction::Truncate,).into_py_any(py),
968                    TemporalFunction::OffsetBy => (PyTemporalFunction::OffsetBy,).into_py_any(py),
969                    TemporalFunction::MonthStart => {
970                        (PyTemporalFunction::MonthStart,).into_py_any(py)
971                    },
972                    TemporalFunction::MonthEnd => (PyTemporalFunction::MonthEnd,).into_py_any(py),
973                    #[cfg(feature = "timezones")]
974                    TemporalFunction::BaseUtcOffset => {
975                        (PyTemporalFunction::BaseUtcOffset,).into_py_any(py)
976                    },
977                    #[cfg(feature = "timezones")]
978                    TemporalFunction::DSTOffset => (PyTemporalFunction::DSTOffset,).into_py_any(py),
979                    TemporalFunction::Round => (PyTemporalFunction::Round,).into_py_any(py),
980                    TemporalFunction::Replace => (PyTemporalFunction::Replace).into_py_any(py),
981                    #[cfg(feature = "timezones")]
982                    TemporalFunction::ReplaceTimeZone(time_zone, non_existent) => (
983                        PyTemporalFunction::ReplaceTimeZone,
984                        time_zone.as_ref().map(|s| s.as_str()),
985                        Into::<&str>::into(non_existent),
986                    )
987                        .into_py_any(py),
988                    TemporalFunction::Combine(time_unit) => {
989                        (PyTemporalFunction::Combine, Wrap(*time_unit)).into_py_any(py)
990                    },
991                    TemporalFunction::DatetimeFunction {
992                        time_unit,
993                        time_zone,
994                    } => (
995                        PyTemporalFunction::DatetimeFunction,
996                        Wrap(*time_unit),
997                        time_zone.as_ref().map(|s| s.as_str()),
998                    )
999                        .into_py_any(py),
1000                },
1001                FunctionExpr::Boolean(boolfun) => match boolfun {
1002                    BooleanFunction::Any { ignore_nulls } => {
1003                        (PyBooleanFunction::Any, *ignore_nulls).into_py_any(py)
1004                    },
1005                    BooleanFunction::All { ignore_nulls } => {
1006                        (PyBooleanFunction::All, *ignore_nulls).into_py_any(py)
1007                    },
1008                    BooleanFunction::IsNull => (PyBooleanFunction::IsNull,).into_py_any(py),
1009                    BooleanFunction::IsNotNull => (PyBooleanFunction::IsNotNull,).into_py_any(py),
1010                    BooleanFunction::IsFinite => (PyBooleanFunction::IsFinite,).into_py_any(py),
1011                    BooleanFunction::IsInfinite => (PyBooleanFunction::IsInfinite,).into_py_any(py),
1012                    BooleanFunction::IsNan => (PyBooleanFunction::IsNan,).into_py_any(py),
1013                    BooleanFunction::IsNotNan => (PyBooleanFunction::IsNotNan,).into_py_any(py),
1014                    BooleanFunction::IsFirstDistinct => {
1015                        (PyBooleanFunction::IsFirstDistinct,).into_py_any(py)
1016                    },
1017                    BooleanFunction::IsLastDistinct => {
1018                        (PyBooleanFunction::IsLastDistinct,).into_py_any(py)
1019                    },
1020                    BooleanFunction::IsUnique => (PyBooleanFunction::IsUnique,).into_py_any(py),
1021                    BooleanFunction::IsDuplicated => {
1022                        (PyBooleanFunction::IsDuplicated,).into_py_any(py)
1023                    },
1024                    BooleanFunction::IsBetween { closed } => {
1025                        (PyBooleanFunction::IsBetween, Into::<&str>::into(closed)).into_py_any(py)
1026                    },
1027                    #[cfg(feature = "is_in")]
1028                    BooleanFunction::IsIn { nulls_equal } => {
1029                        (PyBooleanFunction::IsIn, nulls_equal).into_py_any(py)
1030                    },
1031                    BooleanFunction::AllHorizontal => {
1032                        (PyBooleanFunction::AllHorizontal,).into_py_any(py)
1033                    },
1034                    BooleanFunction::AnyHorizontal => {
1035                        (PyBooleanFunction::AnyHorizontal,).into_py_any(py)
1036                    },
1037                    BooleanFunction::Not => (PyBooleanFunction::Not,).into_py_any(py),
1038                },
1039                FunctionExpr::Abs => ("abs",).into_py_any(py),
1040                #[cfg(feature = "hist")]
1041                FunctionExpr::Hist {
1042                    bin_count,
1043                    include_category,
1044                    include_breakpoint,
1045                } => ("hist", bin_count, include_category, include_breakpoint).into_py_any(py),
1046                FunctionExpr::NullCount => ("null_count",).into_py_any(py),
1047                FunctionExpr::Pow(f) => match f {
1048                    PowFunction::Generic => ("pow",).into_py_any(py),
1049                    PowFunction::Sqrt => ("sqrt",).into_py_any(py),
1050                    PowFunction::Cbrt => ("cbrt",).into_py_any(py),
1051                },
1052                FunctionExpr::Hash(seed, seed_1, seed_2, seed_3) => {
1053                    ("hash", seed, seed_1, seed_2, seed_3).into_py_any(py)
1054                },
1055                FunctionExpr::ArgWhere => ("argwhere",).into_py_any(py),
1056                #[cfg(feature = "index_of")]
1057                FunctionExpr::IndexOf => ("index_of",).into_py_any(py),
1058                #[cfg(feature = "search_sorted")]
1059                FunctionExpr::SearchSorted(side) => (
1060                    "search_sorted",
1061                    match side {
1062                        SearchSortedSide::Any => "any",
1063                        SearchSortedSide::Left => "left",
1064                        SearchSortedSide::Right => "right",
1065                    },
1066                )
1067                    .into_py_any(py),
1068                FunctionExpr::Range(_) => return Err(PyNotImplementedError::new_err("range")),
1069                #[cfg(feature = "trigonometry")]
1070                FunctionExpr::Trigonometry(trigfun) => {
1071                    use polars_plan::dsl::function_expr::trigonometry::TrigonometricFunction;
1072
1073                    match trigfun {
1074                        TrigonometricFunction::Cos => ("cos",),
1075                        TrigonometricFunction::Cot => ("cot",),
1076                        TrigonometricFunction::Sin => ("sin",),
1077                        TrigonometricFunction::Tan => ("tan",),
1078                        TrigonometricFunction::ArcCos => ("arccos",),
1079                        TrigonometricFunction::ArcSin => ("arcsin",),
1080                        TrigonometricFunction::ArcTan => ("arctan",),
1081                        TrigonometricFunction::Cosh => ("cosh",),
1082                        TrigonometricFunction::Sinh => ("sinh",),
1083                        TrigonometricFunction::Tanh => ("tanh",),
1084                        TrigonometricFunction::ArcCosh => ("arccosh",),
1085                        TrigonometricFunction::ArcSinh => ("arcsinh",),
1086                        TrigonometricFunction::ArcTanh => ("arctanh",),
1087                        TrigonometricFunction::Degrees => ("degrees",),
1088                        TrigonometricFunction::Radians => ("radians",),
1089                    }
1090                    .into_py_any(py)
1091                },
1092                #[cfg(feature = "trigonometry")]
1093                FunctionExpr::Atan2 => ("atan2",).into_py_any(py),
1094                #[cfg(feature = "sign")]
1095                FunctionExpr::Sign => ("sign",).into_py_any(py),
1096                FunctionExpr::FillNull => ("fill_null",).into_py_any(py),
1097                FunctionExpr::RollingExpr(rolling) => {
1098                    return Err(PyNotImplementedError::new_err(format!("{}", rolling)));
1099                },
1100                FunctionExpr::RollingExprBy(rolling) => match rolling {
1101                    RollingFunctionBy::MinBy(_) => {
1102                        return Err(PyNotImplementedError::new_err("rolling min by"));
1103                    },
1104                    RollingFunctionBy::MaxBy(_) => {
1105                        return Err(PyNotImplementedError::new_err("rolling max by"));
1106                    },
1107                    RollingFunctionBy::MeanBy(_) => {
1108                        return Err(PyNotImplementedError::new_err("rolling mean by"));
1109                    },
1110                    RollingFunctionBy::SumBy(_) => {
1111                        return Err(PyNotImplementedError::new_err("rolling sum by"));
1112                    },
1113                    RollingFunctionBy::QuantileBy(_) => {
1114                        return Err(PyNotImplementedError::new_err("rolling quantile by"));
1115                    },
1116                    RollingFunctionBy::VarBy(_) => {
1117                        return Err(PyNotImplementedError::new_err("rolling var by"));
1118                    },
1119                    RollingFunctionBy::StdBy(_) => {
1120                        return Err(PyNotImplementedError::new_err("rolling std by"));
1121                    },
1122                },
1123                FunctionExpr::ShiftAndFill => ("shift_and_fill",).into_py_any(py),
1124                FunctionExpr::Shift => ("shift",).into_py_any(py),
1125                FunctionExpr::DropNans => ("drop_nans",).into_py_any(py),
1126                FunctionExpr::DropNulls => ("drop_nulls",).into_py_any(py),
1127                FunctionExpr::Mode => ("mode",).into_py_any(py),
1128                FunctionExpr::Skew(bias) => ("skew", bias).into_py_any(py),
1129                FunctionExpr::Kurtosis(fisher, bias) => ("kurtosis", fisher, bias).into_py_any(py),
1130                FunctionExpr::Reshape(_) => return Err(PyNotImplementedError::new_err("reshape")),
1131                #[cfg(feature = "repeat_by")]
1132                FunctionExpr::RepeatBy => ("repeat_by",).into_py_any(py),
1133                FunctionExpr::ArgUnique => ("arg_unique",).into_py_any(py),
1134                FunctionExpr::Repeat => ("repeat",).into_py_any(py),
1135                FunctionExpr::Rank {
1136                    options: _,
1137                    seed: _,
1138                } => return Err(PyNotImplementedError::new_err("rank")),
1139                FunctionExpr::Clip { has_min, has_max } => {
1140                    ("clip", has_min, has_max).into_py_any(py)
1141                },
1142                FunctionExpr::AsStruct => ("as_struct",).into_py_any(py),
1143                #[cfg(feature = "top_k")]
1144                FunctionExpr::TopK { descending } => ("top_k", descending).into_py_any(py),
1145                FunctionExpr::CumCount { reverse } => ("cum_count", reverse).into_py_any(py),
1146                FunctionExpr::CumSum { reverse } => ("cum_sum", reverse).into_py_any(py),
1147                FunctionExpr::CumProd { reverse } => ("cum_prod", reverse).into_py_any(py),
1148                FunctionExpr::CumMin { reverse } => ("cum_min", reverse).into_py_any(py),
1149                FunctionExpr::CumMax { reverse } => ("cum_max", reverse).into_py_any(py),
1150                FunctionExpr::Reverse => ("reverse",).into_py_any(py),
1151                FunctionExpr::ValueCounts {
1152                    sort,
1153                    parallel,
1154                    name,
1155                    normalize,
1156                } => ("value_counts", sort, parallel, name.as_str(), normalize).into_py_any(py),
1157                FunctionExpr::UniqueCounts => ("unique_counts",).into_py_any(py),
1158                FunctionExpr::ApproxNUnique => ("approx_n_unique",).into_py_any(py),
1159                FunctionExpr::Coalesce => ("coalesce",).into_py_any(py),
1160                FunctionExpr::ShrinkType => ("shrink_dtype",).into_py_any(py),
1161                FunctionExpr::Diff(null_behaviour) => (
1162                    "diff",
1163                    match null_behaviour {
1164                        NullBehavior::Drop => "drop",
1165                        NullBehavior::Ignore => "ignore",
1166                    },
1167                )
1168                    .into_py_any(py),
1169                #[cfg(feature = "pct_change")]
1170                FunctionExpr::PctChange => ("pct_change",).into_py_any(py),
1171                FunctionExpr::Interpolate(method) => (
1172                    "interpolate",
1173                    match method {
1174                        InterpolationMethod::Linear => "linear",
1175                        InterpolationMethod::Nearest => "nearest",
1176                    },
1177                )
1178                    .into_py_any(py),
1179                FunctionExpr::InterpolateBy => ("interpolate_by",).into_py_any(py),
1180                FunctionExpr::Entropy { base, normalize } => {
1181                    ("entropy", base, normalize).into_py_any(py)
1182                },
1183                FunctionExpr::Log { base } => ("log", base).into_py_any(py),
1184                FunctionExpr::Log1p => ("log1p",).into_py_any(py),
1185                FunctionExpr::Exp => ("exp",).into_py_any(py),
1186                FunctionExpr::Unique(maintain_order) => ("unique", maintain_order).into_py_any(py),
1187                FunctionExpr::Round { decimals, mode } => {
1188                    ("round", decimals, Into::<&str>::into(mode)).into_py_any(py)
1189                },
1190                FunctionExpr::RoundSF { digits } => ("round_sig_figs", digits).into_py_any(py),
1191                FunctionExpr::Floor => ("floor",).into_py_any(py),
1192                FunctionExpr::Ceil => ("ceil",).into_py_any(py),
1193                FunctionExpr::UpperBound => ("upper_bound",).into_py_any(py),
1194                FunctionExpr::LowerBound => ("lower_bound",).into_py_any(py),
1195                FunctionExpr::Fused(_) => return Err(PyNotImplementedError::new_err("fused")),
1196                FunctionExpr::ConcatExpr(_) => {
1197                    return Err(PyNotImplementedError::new_err("concat expr"));
1198                },
1199                FunctionExpr::Correlation { .. } => {
1200                    return Err(PyNotImplementedError::new_err("corr"));
1201                },
1202                #[cfg(feature = "peaks")]
1203                FunctionExpr::PeakMin => ("peak_max",).into_py_any(py),
1204                #[cfg(feature = "peaks")]
1205                FunctionExpr::PeakMax => ("peak_min",).into_py_any(py),
1206                #[cfg(feature = "cutqcut")]
1207                FunctionExpr::Cut { .. } => return Err(PyNotImplementedError::new_err("cut")),
1208                #[cfg(feature = "cutqcut")]
1209                FunctionExpr::QCut { .. } => return Err(PyNotImplementedError::new_err("qcut")),
1210                #[cfg(feature = "rle")]
1211                FunctionExpr::RLE => ("rle",).into_py_any(py),
1212                #[cfg(feature = "rle")]
1213                FunctionExpr::RLEID => ("rle_id",).into_py_any(py),
1214                FunctionExpr::ToPhysical => ("to_physical",).into_py_any(py),
1215                FunctionExpr::Random { .. } => {
1216                    return Err(PyNotImplementedError::new_err("random"));
1217                },
1218                FunctionExpr::SetSortedFlag(sorted) => (
1219                    "set_sorted",
1220                    match sorted {
1221                        IsSorted::Ascending => "ascending",
1222                        IsSorted::Descending => "descending",
1223                        IsSorted::Not => "not",
1224                    },
1225                )
1226                    .into_py_any(py),
1227                #[cfg(feature = "ffi_plugin")]
1228                FunctionExpr::FfiPlugin { .. } => {
1229                    return Err(PyNotImplementedError::new_err("ffi plugin"));
1230                },
1231                FunctionExpr::SumHorizontal { ignore_nulls } => {
1232                    ("sum_horizontal", ignore_nulls).into_py_any(py)
1233                },
1234                FunctionExpr::MaxHorizontal => ("max_horizontal",).into_py_any(py),
1235                FunctionExpr::MeanHorizontal { ignore_nulls } => {
1236                    ("mean_horizontal", ignore_nulls).into_py_any(py)
1237                },
1238                FunctionExpr::MinHorizontal => ("min_horizontal",).into_py_any(py),
1239                FunctionExpr::EwmMean { options: _ } => {
1240                    return Err(PyNotImplementedError::new_err("ewm mean"));
1241                },
1242                FunctionExpr::EwmStd { options: _ } => {
1243                    return Err(PyNotImplementedError::new_err("ewm std"));
1244                },
1245                FunctionExpr::EwmVar { options: _ } => {
1246                    return Err(PyNotImplementedError::new_err("ewm var"));
1247                },
1248                FunctionExpr::Replace => ("replace",).into_py_any(py),
1249                FunctionExpr::ReplaceStrict { return_dtype: _ } => {
1250                    // Can ignore the return dtype because it is encoded in the schema.
1251                    ("replace_strict",).into_py_any(py)
1252                },
1253                FunctionExpr::Negate => ("negate",).into_py_any(py),
1254                FunctionExpr::FillNullWithStrategy(_) => {
1255                    return Err(PyNotImplementedError::new_err("fill null with strategy"));
1256                },
1257                FunctionExpr::GatherEvery { n, offset } => {
1258                    ("gather_every", offset, n).into_py_any(py)
1259                },
1260                FunctionExpr::Reinterpret(signed) => ("reinterpret", signed).into_py_any(py),
1261                FunctionExpr::ExtendConstant => ("extend_constant",).into_py_any(py),
1262                FunctionExpr::Business(_) => {
1263                    return Err(PyNotImplementedError::new_err("business"));
1264                },
1265                #[cfg(feature = "top_k")]
1266                FunctionExpr::TopKBy { descending } => ("top_k_by", descending).into_py_any(py),
1267                FunctionExpr::EwmMeanBy { half_life: _ } => {
1268                    return Err(PyNotImplementedError::new_err("ewm_mean_by"));
1269                },
1270            }?,
1271            options: py.None(),
1272        }
1273        .into_py_any(py),
1274        AExpr::Window {
1275            function,
1276            partition_by,
1277            order_by,
1278            options,
1279        } => {
1280            let function = function.0;
1281            let partition_by = partition_by.iter().map(|n| n.0).collect();
1282            let order_by_descending = order_by
1283                .map(|(_, options)| options.descending)
1284                .unwrap_or(false);
1285            let order_by_nulls_last = order_by
1286                .map(|(_, options)| options.nulls_last)
1287                .unwrap_or(false);
1288            let order_by = order_by.map(|(n, _)| n.0);
1289
1290            let options = match options {
1291                WindowType::Over(options) => PyWindowMapping { inner: *options }.into_py_any(py)?,
1292                WindowType::Rolling(options) => PyRollingGroupOptions {
1293                    inner: options.clone(),
1294                }
1295                .into_py_any(py)?,
1296            };
1297            Window {
1298                function,
1299                partition_by,
1300                order_by,
1301                order_by_descending,
1302                order_by_nulls_last,
1303                options,
1304            }
1305            .into_py_any(py)
1306        },
1307        AExpr::Slice {
1308            input,
1309            offset,
1310            length,
1311        } => Slice {
1312            input: input.0,
1313            offset: offset.0,
1314            length: length.0,
1315        }
1316        .into_py_any(py),
1317        AExpr::Len => Len {}.into_py_any(py),
1318    }
1319}