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::RollingFunction;
11use polars_plan::dsl::function_expr::rolling_by::RollingFunctionBy;
12use polars_plan::dsl::{BooleanFunction, StringFunction, TemporalFunction};
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::exceptions::PyNotImplementedError;
20use pyo3::prelude::*;
21use pyo3::types::PyTuple;
22use pyo3::IntoPyObjectExt;
23
24use crate::series::PySeries;
25use crate::Wrap;
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 LiteralValue::*;
547            let dtype: PyObject = Wrap(lit.get_datatype()).into_py_any(py)?;
548            match lit {
549                Float(v) => Literal {
550                    value: v.into_py_any(py)?,
551                    dtype,
552                },
553                Float32(v) => Literal {
554                    value: v.into_py_any(py)?,
555                    dtype,
556                },
557                Float64(v) => Literal {
558                    value: v.into_py_any(py)?,
559                    dtype,
560                },
561                Int(v) => Literal {
562                    value: v.into_py_any(py)?,
563                    dtype,
564                },
565                Int8(v) => Literal {
566                    value: v.into_py_any(py)?,
567                    dtype,
568                },
569                Int16(v) => Literal {
570                    value: v.into_py_any(py)?,
571                    dtype,
572                },
573                Int32(v) => Literal {
574                    value: v.into_py_any(py)?,
575                    dtype,
576                },
577                Int64(v) => Literal {
578                    value: v.into_py_any(py)?,
579                    dtype,
580                },
581                Int128(v) => Literal {
582                    value: v.into_py_any(py)?,
583                    dtype,
584                },
585                UInt8(v) => Literal {
586                    value: v.into_py_any(py)?,
587                    dtype,
588                },
589                UInt16(v) => Literal {
590                    value: v.into_py_any(py)?,
591                    dtype,
592                },
593                UInt32(v) => Literal {
594                    value: v.into_py_any(py)?,
595                    dtype,
596                },
597                UInt64(v) => Literal {
598                    value: v.into_py_any(py)?,
599                    dtype,
600                },
601                Boolean(v) => Literal {
602                    value: v.into_py_any(py)?,
603                    dtype,
604                },
605                StrCat(v) => Literal {
606                    value: v.into_py_any(py)?,
607                    dtype,
608                },
609                String(v) => Literal {
610                    value: v.into_py_any(py)?,
611                    dtype,
612                },
613                Null => Literal {
614                    value: py.None(),
615                    dtype,
616                },
617                Binary(_) => return Err(PyNotImplementedError::new_err("binary literal")),
618                Range { .. } => return Err(PyNotImplementedError::new_err("range literal")),
619                OtherScalar { .. } => return Err(PyNotImplementedError::new_err("scalar literal")),
620                Date(..) | DateTime(..) | Decimal(..) => Literal {
621                    value: Wrap(lit.to_any_value().unwrap()).into_py_any(py)?,
622                    dtype,
623                },
624                Duration(v, _) => Literal {
625                    value: v.into_py_any(py)?,
626                    dtype,
627                },
628                Time(ns) => Literal {
629                    value: ns.into_py_any(py)?,
630                    dtype,
631                },
632                Series(s) => Literal {
633                    value: PySeries::new((**s).clone()).into_py_any(py)?,
634                    dtype,
635                },
636            }
637        }
638        .into_py_any(py),
639        AExpr::BinaryExpr { left, op, right } => BinaryExpr {
640            left: left.0,
641            op: Wrap(*op).into_py_any(py)?,
642            right: right.0,
643        }
644        .into_py_any(py),
645        AExpr::Cast {
646            expr,
647            dtype,
648            options,
649        } => Cast {
650            expr: expr.0,
651            dtype: Wrap(dtype.clone()).into_py_any(py)?,
652            options: *options as u8,
653        }
654        .into_py_any(py),
655        AExpr::Sort { expr, options } => Sort {
656            expr: expr.0,
657            options: (
658                options.maintain_order,
659                options.nulls_last,
660                options.descending,
661            ),
662        }
663        .into_py_any(py),
664        AExpr::Gather {
665            expr,
666            idx,
667            returns_scalar,
668        } => Gather {
669            expr: expr.0,
670            idx: idx.0,
671            scalar: *returns_scalar,
672        }
673        .into_py_any(py),
674        AExpr::Filter { input, by } => Filter {
675            input: input.0,
676            by: by.0,
677        }
678        .into_py_any(py),
679        AExpr::SortBy {
680            expr,
681            by,
682            sort_options,
683        } => SortBy {
684            expr: expr.0,
685            by: by.iter().map(|n| n.0).collect(),
686            sort_options: (
687                sort_options.maintain_order,
688                sort_options.nulls_last.clone(),
689                sort_options.descending.clone(),
690            ),
691        }
692        .into_py_any(py),
693        AExpr::Agg(aggexpr) => match aggexpr {
694            IRAggExpr::Min {
695                input,
696                propagate_nans,
697            } => Agg {
698                name: "min".into_py_any(py)?,
699                arguments: vec![input.0],
700                options: propagate_nans.into_py_any(py)?,
701            },
702            IRAggExpr::Max {
703                input,
704                propagate_nans,
705            } => Agg {
706                name: "max".into_py_any(py)?,
707                arguments: vec![input.0],
708                options: propagate_nans.into_py_any(py)?,
709            },
710            IRAggExpr::Median(n) => Agg {
711                name: "median".into_py_any(py)?,
712                arguments: vec![n.0],
713                options: py.None(),
714            },
715            IRAggExpr::NUnique(n) => Agg {
716                name: "n_unique".into_py_any(py)?,
717                arguments: vec![n.0],
718                options: py.None(),
719            },
720            IRAggExpr::First(n) => Agg {
721                name: "first".into_py_any(py)?,
722                arguments: vec![n.0],
723                options: py.None(),
724            },
725            IRAggExpr::Last(n) => Agg {
726                name: "last".into_py_any(py)?,
727                arguments: vec![n.0],
728                options: py.None(),
729            },
730            IRAggExpr::Mean(n) => Agg {
731                name: "mean".into_py_any(py)?,
732                arguments: vec![n.0],
733                options: py.None(),
734            },
735            IRAggExpr::Implode(n) => Agg {
736                name: "implode".into_py_any(py)?,
737                arguments: vec![n.0],
738                options: py.None(),
739            },
740            IRAggExpr::Quantile {
741                expr,
742                quantile,
743                method: interpol,
744            } => Agg {
745                name: "quantile".into_py_any(py)?,
746                arguments: vec![expr.0, quantile.0],
747                options: Into::<&str>::into(interpol).into_py_any(py)?,
748            },
749            IRAggExpr::Sum(n) => Agg {
750                name: "sum".into_py_any(py)?,
751                arguments: vec![n.0],
752                options: py.None(),
753            },
754            IRAggExpr::Count(n, include_null) => Agg {
755                name: "count".into_py_any(py)?,
756                arguments: vec![n.0],
757                options: include_null.into_py_any(py)?,
758            },
759            IRAggExpr::Std(n, ddof) => Agg {
760                name: "std".into_py_any(py)?,
761                arguments: vec![n.0],
762                options: ddof.into_py_any(py)?,
763            },
764            IRAggExpr::Var(n, ddof) => Agg {
765                name: "var".into_py_any(py)?,
766                arguments: vec![n.0],
767                options: ddof.into_py_any(py)?,
768            },
769            IRAggExpr::AggGroups(n) => Agg {
770                name: "agg_groups".into_py_any(py)?,
771                arguments: vec![n.0],
772                options: py.None(),
773            },
774        }
775        .into_py_any(py),
776        AExpr::Ternary {
777            predicate,
778            truthy,
779            falsy,
780        } => Ternary {
781            predicate: predicate.0,
782            truthy: truthy.0,
783            falsy: falsy.0,
784        }
785        .into_py_any(py),
786        AExpr::AnonymousFunction { .. } => Err(PyNotImplementedError::new_err("anonymousfunction")),
787        AExpr::Function {
788            input,
789            function,
790            // TODO: expose options
791            options: _,
792        } => Function {
793            input: input.iter().map(|n| n.node().0).collect(),
794            function_data: match function {
795                FunctionExpr::ArrayExpr(_) => {
796                    return Err(PyNotImplementedError::new_err("array expr"))
797                },
798                FunctionExpr::BinaryExpr(_) => {
799                    return Err(PyNotImplementedError::new_err("binary expr"))
800                },
801                FunctionExpr::Categorical(_) => {
802                    return Err(PyNotImplementedError::new_err("categorical expr"))
803                },
804                FunctionExpr::ListExpr(_) => {
805                    return Err(PyNotImplementedError::new_err("list expr"))
806                },
807                FunctionExpr::Bitwise(_) => {
808                    return Err(PyNotImplementedError::new_err("bitwise expr"))
809                },
810                FunctionExpr::StringExpr(strfun) => match strfun {
811                    StringFunction::ConcatHorizontal {
812                        delimiter,
813                        ignore_nulls,
814                    } => (
815                        PyStringFunction::ConcatHorizontal,
816                        delimiter.as_str(),
817                        ignore_nulls,
818                    )
819                        .into_py_any(py),
820                    StringFunction::ConcatVertical {
821                        delimiter,
822                        ignore_nulls,
823                    } => (
824                        PyStringFunction::ConcatVertical,
825                        delimiter.as_str(),
826                        ignore_nulls,
827                    )
828                        .into_py_any(py),
829                    #[cfg(feature = "regex")]
830                    StringFunction::Contains { literal, strict } => {
831                        (PyStringFunction::Contains, literal, strict).into_py_any(py)
832                    },
833                    StringFunction::CountMatches(literal) => {
834                        (PyStringFunction::CountMatches, literal).into_py_any(py)
835                    },
836                    StringFunction::EndsWith => (PyStringFunction::EndsWith,).into_py_any(py),
837                    StringFunction::Extract(group_index) => {
838                        (PyStringFunction::Extract, group_index).into_py_any(py)
839                    },
840                    StringFunction::ExtractAll => (PyStringFunction::ExtractAll,).into_py_any(py),
841                    #[cfg(feature = "extract_groups")]
842                    StringFunction::ExtractGroups { dtype, pat } => (
843                        PyStringFunction::ExtractGroups,
844                        &Wrap(dtype.clone()),
845                        pat.as_str(),
846                    )
847                        .into_py_any(py),
848                    #[cfg(feature = "regex")]
849                    StringFunction::Find { literal, strict } => {
850                        (PyStringFunction::Find, literal, strict).into_py_any(py)
851                    },
852                    StringFunction::ToInteger(strict) => {
853                        (PyStringFunction::ToInteger, strict).into_py_any(py)
854                    },
855                    StringFunction::LenBytes => (PyStringFunction::LenBytes,).into_py_any(py),
856                    StringFunction::LenChars => (PyStringFunction::LenChars,).into_py_any(py),
857                    StringFunction::Lowercase => (PyStringFunction::Lowercase,).into_py_any(py),
858                    #[cfg(feature = "extract_jsonpath")]
859                    StringFunction::JsonDecode {
860                        dtype: _,
861                        infer_schema_len,
862                    } => (PyStringFunction::JsonDecode, infer_schema_len).into_py_any(py),
863                    #[cfg(feature = "extract_jsonpath")]
864                    StringFunction::JsonPathMatch => {
865                        (PyStringFunction::JsonPathMatch,).into_py_any(py)
866                    },
867                    #[cfg(feature = "regex")]
868                    StringFunction::Replace { n, literal } => {
869                        (PyStringFunction::Replace, n, literal).into_py_any(py)
870                    },
871                    StringFunction::Normalize { form } => (
872                        PyStringFunction::Normalize,
873                        match form {
874                            UnicodeForm::NFC => "nfc",
875                            UnicodeForm::NFKC => "nfkc",
876                            UnicodeForm::NFD => "nfd",
877                            UnicodeForm::NFKD => "nfkd",
878                        },
879                    )
880                        .into_py_any(py),
881                    StringFunction::Reverse => (PyStringFunction::Reverse,).into_py_any(py),
882                    StringFunction::PadStart { length, fill_char } => {
883                        (PyStringFunction::PadStart, length, fill_char).into_py_any(py)
884                    },
885                    StringFunction::PadEnd { length, fill_char } => {
886                        (PyStringFunction::PadEnd, length, fill_char).into_py_any(py)
887                    },
888                    StringFunction::Slice => (PyStringFunction::Slice,).into_py_any(py),
889                    StringFunction::Head => (PyStringFunction::Head,).into_py_any(py),
890                    StringFunction::Tail => (PyStringFunction::Tail,).into_py_any(py),
891                    StringFunction::HexEncode => (PyStringFunction::HexEncode,).into_py_any(py),
892                    #[cfg(feature = "binary_encoding")]
893                    StringFunction::HexDecode(strict) => {
894                        (PyStringFunction::HexDecode, strict).into_py_any(py)
895                    },
896                    StringFunction::Base64Encode => {
897                        (PyStringFunction::Base64Encode,).into_py_any(py)
898                    },
899                    #[cfg(feature = "binary_encoding")]
900                    StringFunction::Base64Decode(strict) => {
901                        (PyStringFunction::Base64Decode, strict).into_py_any(py)
902                    },
903                    StringFunction::StartsWith => (PyStringFunction::StartsWith,).into_py_any(py),
904                    StringFunction::StripChars => (PyStringFunction::StripChars,).into_py_any(py),
905                    StringFunction::StripCharsStart => {
906                        (PyStringFunction::StripCharsStart,).into_py_any(py)
907                    },
908                    StringFunction::StripCharsEnd => {
909                        (PyStringFunction::StripCharsEnd,).into_py_any(py)
910                    },
911                    StringFunction::StripPrefix => (PyStringFunction::StripPrefix,).into_py_any(py),
912                    StringFunction::StripSuffix => (PyStringFunction::StripSuffix,).into_py_any(py),
913                    StringFunction::SplitExact { n, inclusive } => {
914                        (PyStringFunction::SplitExact, n, inclusive).into_py_any(py)
915                    },
916                    StringFunction::SplitN(n) => (PyStringFunction::SplitN, n).into_py_any(py),
917                    StringFunction::Strptime(_, options) => (
918                        PyStringFunction::Strptime,
919                        options.format.as_ref().map(|s| s.as_str()),
920                        options.strict,
921                        options.exact,
922                        options.cache,
923                    )
924                        .into_py_any(py),
925                    StringFunction::Split(inclusive) => {
926                        (PyStringFunction::Split, inclusive).into_py_any(py)
927                    },
928                    StringFunction::ToDecimal(inference_length) => {
929                        (PyStringFunction::ToDecimal, inference_length).into_py_any(py)
930                    },
931                    #[cfg(feature = "nightly")]
932                    StringFunction::Titlecase => (PyStringFunction::Titlecase,).into_py_any(py),
933                    StringFunction::Uppercase => (PyStringFunction::Uppercase,).into_py_any(py),
934                    StringFunction::ZFill => (PyStringFunction::ZFill,).into_py_any(py),
935                    #[cfg(feature = "find_many")]
936                    StringFunction::ContainsAny {
937                        ascii_case_insensitive,
938                    } => (PyStringFunction::ContainsAny, ascii_case_insensitive).into_py_any(py),
939                    #[cfg(feature = "find_many")]
940                    StringFunction::ReplaceMany {
941                        ascii_case_insensitive,
942                    } => (PyStringFunction::ReplaceMany, ascii_case_insensitive).into_py_any(py),
943                    #[cfg(feature = "find_many")]
944                    StringFunction::ExtractMany { .. } => {
945                        return Err(PyNotImplementedError::new_err("extract_many"))
946                    },
947                    #[cfg(feature = "find_many")]
948                    StringFunction::FindMany { .. } => {
949                        return Err(PyNotImplementedError::new_err("find_many"))
950                    },
951                    #[cfg(feature = "regex")]
952                    StringFunction::EscapeRegex => (PyStringFunction::EscapeRegex,).into_py_any(py),
953                },
954                FunctionExpr::StructExpr(_) => {
955                    return Err(PyNotImplementedError::new_err("struct expr"))
956                },
957                FunctionExpr::TemporalExpr(fun) => match fun {
958                    TemporalFunction::Millennium => {
959                        (PyTemporalFunction::Millennium,).into_py_any(py)
960                    },
961                    TemporalFunction::Century => (PyTemporalFunction::Century,).into_py_any(py),
962                    TemporalFunction::Year => (PyTemporalFunction::Year,).into_py_any(py),
963                    TemporalFunction::IsLeapYear => {
964                        (PyTemporalFunction::IsLeapYear,).into_py_any(py)
965                    },
966                    TemporalFunction::IsoYear => (PyTemporalFunction::IsoYear,).into_py_any(py),
967                    TemporalFunction::Quarter => (PyTemporalFunction::Quarter,).into_py_any(py),
968                    TemporalFunction::Month => (PyTemporalFunction::Month,).into_py_any(py),
969                    TemporalFunction::Week => (PyTemporalFunction::Week,).into_py_any(py),
970                    TemporalFunction::WeekDay => (PyTemporalFunction::WeekDay,).into_py_any(py),
971                    TemporalFunction::Day => (PyTemporalFunction::Day,).into_py_any(py),
972                    TemporalFunction::OrdinalDay => {
973                        (PyTemporalFunction::OrdinalDay,).into_py_any(py)
974                    },
975                    TemporalFunction::Time => (PyTemporalFunction::Time,).into_py_any(py),
976                    TemporalFunction::Date => (PyTemporalFunction::Date,).into_py_any(py),
977                    TemporalFunction::Datetime => (PyTemporalFunction::Datetime,).into_py_any(py),
978                    TemporalFunction::Duration(time_unit) => {
979                        (PyTemporalFunction::Duration, Wrap(*time_unit)).into_py_any(py)
980                    },
981                    TemporalFunction::Hour => (PyTemporalFunction::Hour,).into_py_any(py),
982                    TemporalFunction::Minute => (PyTemporalFunction::Minute,).into_py_any(py),
983                    TemporalFunction::Second => (PyTemporalFunction::Second,).into_py_any(py),
984                    TemporalFunction::Millisecond => {
985                        (PyTemporalFunction::Millisecond,).into_py_any(py)
986                    },
987                    TemporalFunction::Microsecond => {
988                        (PyTemporalFunction::Microsecond,).into_py_any(py)
989                    },
990                    TemporalFunction::Nanosecond => {
991                        (PyTemporalFunction::Nanosecond,).into_py_any(py)
992                    },
993                    TemporalFunction::TotalDays => (PyTemporalFunction::TotalDays,).into_py_any(py),
994                    TemporalFunction::TotalHours => {
995                        (PyTemporalFunction::TotalHours,).into_py_any(py)
996                    },
997                    TemporalFunction::TotalMinutes => {
998                        (PyTemporalFunction::TotalMinutes,).into_py_any(py)
999                    },
1000                    TemporalFunction::TotalSeconds => {
1001                        (PyTemporalFunction::TotalSeconds,).into_py_any(py)
1002                    },
1003                    TemporalFunction::TotalMilliseconds => {
1004                        (PyTemporalFunction::TotalMilliseconds,).into_py_any(py)
1005                    },
1006                    TemporalFunction::TotalMicroseconds => {
1007                        (PyTemporalFunction::TotalMicroseconds,).into_py_any(py)
1008                    },
1009                    TemporalFunction::TotalNanoseconds => {
1010                        (PyTemporalFunction::TotalNanoseconds,).into_py_any(py)
1011                    },
1012                    TemporalFunction::ToString(format) => {
1013                        (PyTemporalFunction::ToString, format).into_py_any(py)
1014                    },
1015                    TemporalFunction::CastTimeUnit(time_unit) => {
1016                        (PyTemporalFunction::CastTimeUnit, Wrap(*time_unit)).into_py_any(py)
1017                    },
1018                    TemporalFunction::WithTimeUnit(time_unit) => {
1019                        (PyTemporalFunction::WithTimeUnit, Wrap(*time_unit)).into_py_any(py)
1020                    },
1021                    #[cfg(feature = "timezones")]
1022                    TemporalFunction::ConvertTimeZone(time_zone) => {
1023                        (PyTemporalFunction::ConvertTimeZone, time_zone.as_str()).into_py_any(py)
1024                    },
1025                    TemporalFunction::TimeStamp(time_unit) => {
1026                        (PyTemporalFunction::TimeStamp, Wrap(*time_unit)).into_py_any(py)
1027                    },
1028                    TemporalFunction::Truncate => (PyTemporalFunction::Truncate,).into_py_any(py),
1029                    TemporalFunction::OffsetBy => (PyTemporalFunction::OffsetBy,).into_py_any(py),
1030                    TemporalFunction::MonthStart => {
1031                        (PyTemporalFunction::MonthStart,).into_py_any(py)
1032                    },
1033                    TemporalFunction::MonthEnd => (PyTemporalFunction::MonthEnd,).into_py_any(py),
1034                    #[cfg(feature = "timezones")]
1035                    TemporalFunction::BaseUtcOffset => {
1036                        (PyTemporalFunction::BaseUtcOffset,).into_py_any(py)
1037                    },
1038                    #[cfg(feature = "timezones")]
1039                    TemporalFunction::DSTOffset => (PyTemporalFunction::DSTOffset,).into_py_any(py),
1040                    TemporalFunction::Round => (PyTemporalFunction::Round,).into_py_any(py),
1041                    TemporalFunction::Replace => (PyTemporalFunction::Replace).into_py_any(py),
1042                    #[cfg(feature = "timezones")]
1043                    TemporalFunction::ReplaceTimeZone(time_zone, non_existent) => (
1044                        PyTemporalFunction::ReplaceTimeZone,
1045                        time_zone.as_ref().map(|s| s.as_str()),
1046                        Into::<&str>::into(non_existent),
1047                    )
1048                        .into_py_any(py),
1049                    TemporalFunction::Combine(time_unit) => {
1050                        (PyTemporalFunction::Combine, Wrap(*time_unit)).into_py_any(py)
1051                    },
1052                    TemporalFunction::DatetimeFunction {
1053                        time_unit,
1054                        time_zone,
1055                    } => (
1056                        PyTemporalFunction::DatetimeFunction,
1057                        Wrap(*time_unit),
1058                        time_zone.as_ref().map(|s| s.as_str()),
1059                    )
1060                        .into_py_any(py),
1061                },
1062                FunctionExpr::Boolean(boolfun) => match boolfun {
1063                    BooleanFunction::Any { ignore_nulls } => {
1064                        (PyBooleanFunction::Any, *ignore_nulls).into_py_any(py)
1065                    },
1066                    BooleanFunction::All { ignore_nulls } => {
1067                        (PyBooleanFunction::All, *ignore_nulls).into_py_any(py)
1068                    },
1069                    BooleanFunction::IsNull => (PyBooleanFunction::IsNull,).into_py_any(py),
1070                    BooleanFunction::IsNotNull => (PyBooleanFunction::IsNotNull,).into_py_any(py),
1071                    BooleanFunction::IsFinite => (PyBooleanFunction::IsFinite,).into_py_any(py),
1072                    BooleanFunction::IsInfinite => (PyBooleanFunction::IsInfinite,).into_py_any(py),
1073                    BooleanFunction::IsNan => (PyBooleanFunction::IsNan,).into_py_any(py),
1074                    BooleanFunction::IsNotNan => (PyBooleanFunction::IsNotNan,).into_py_any(py),
1075                    BooleanFunction::IsFirstDistinct => {
1076                        (PyBooleanFunction::IsFirstDistinct,).into_py_any(py)
1077                    },
1078                    BooleanFunction::IsLastDistinct => {
1079                        (PyBooleanFunction::IsLastDistinct,).into_py_any(py)
1080                    },
1081                    BooleanFunction::IsUnique => (PyBooleanFunction::IsUnique,).into_py_any(py),
1082                    BooleanFunction::IsDuplicated => {
1083                        (PyBooleanFunction::IsDuplicated,).into_py_any(py)
1084                    },
1085                    BooleanFunction::IsBetween { closed } => {
1086                        (PyBooleanFunction::IsBetween, Into::<&str>::into(closed)).into_py_any(py)
1087                    },
1088                    #[cfg(feature = "is_in")]
1089                    BooleanFunction::IsIn => (PyBooleanFunction::IsIn,).into_py_any(py),
1090                    BooleanFunction::AllHorizontal => {
1091                        (PyBooleanFunction::AllHorizontal,).into_py_any(py)
1092                    },
1093                    BooleanFunction::AnyHorizontal => {
1094                        (PyBooleanFunction::AnyHorizontal,).into_py_any(py)
1095                    },
1096                    BooleanFunction::Not => (PyBooleanFunction::Not,).into_py_any(py),
1097                },
1098                FunctionExpr::Abs => ("abs",).into_py_any(py),
1099                #[cfg(feature = "hist")]
1100                FunctionExpr::Hist {
1101                    bin_count,
1102                    include_category,
1103                    include_breakpoint,
1104                } => ("hist", bin_count, include_category, include_breakpoint).into_py_any(py),
1105                FunctionExpr::NullCount => ("null_count",).into_py_any(py),
1106                FunctionExpr::Pow(f) => match f {
1107                    PowFunction::Generic => ("pow",).into_py_any(py),
1108                    PowFunction::Sqrt => ("sqrt",).into_py_any(py),
1109                    PowFunction::Cbrt => ("cbrt",).into_py_any(py),
1110                },
1111                FunctionExpr::Hash(seed, seed_1, seed_2, seed_3) => {
1112                    ("hash", seed, seed_1, seed_2, seed_3).into_py_any(py)
1113                },
1114                FunctionExpr::ArgWhere => ("argwhere",).into_py_any(py),
1115                #[cfg(feature = "index_of")]
1116                FunctionExpr::IndexOf => ("index_of",).into_py_any(py),
1117                #[cfg(feature = "search_sorted")]
1118                FunctionExpr::SearchSorted(side) => (
1119                    "search_sorted",
1120                    match side {
1121                        SearchSortedSide::Any => "any",
1122                        SearchSortedSide::Left => "left",
1123                        SearchSortedSide::Right => "right",
1124                    },
1125                )
1126                    .into_py_any(py),
1127                FunctionExpr::Range(_) => return Err(PyNotImplementedError::new_err("range")),
1128                #[cfg(feature = "trigonometry")]
1129                FunctionExpr::Trigonometry(trigfun) => {
1130                    use polars_plan::dsl::function_expr::trigonometry::TrigonometricFunction;
1131
1132                    match trigfun {
1133                        TrigonometricFunction::Cos => ("cos",),
1134                        TrigonometricFunction::Cot => ("cot",),
1135                        TrigonometricFunction::Sin => ("sin",),
1136                        TrigonometricFunction::Tan => ("tan",),
1137                        TrigonometricFunction::ArcCos => ("arccos",),
1138                        TrigonometricFunction::ArcSin => ("arcsin",),
1139                        TrigonometricFunction::ArcTan => ("arctan",),
1140                        TrigonometricFunction::Cosh => ("cosh",),
1141                        TrigonometricFunction::Sinh => ("sinh",),
1142                        TrigonometricFunction::Tanh => ("tanh",),
1143                        TrigonometricFunction::ArcCosh => ("arccosh",),
1144                        TrigonometricFunction::ArcSinh => ("arcsinh",),
1145                        TrigonometricFunction::ArcTanh => ("arctanh",),
1146                        TrigonometricFunction::Degrees => ("degrees",),
1147                        TrigonometricFunction::Radians => ("radians",),
1148                    }
1149                    .into_py_any(py)
1150                },
1151                #[cfg(feature = "trigonometry")]
1152                FunctionExpr::Atan2 => ("atan2",).into_py_any(py),
1153                #[cfg(feature = "sign")]
1154                FunctionExpr::Sign => ("sign",).into_py_any(py),
1155                FunctionExpr::FillNull => ("fill_null",).into_py_any(py),
1156                FunctionExpr::RollingExpr(rolling) => match rolling {
1157                    RollingFunction::Min(_) => {
1158                        return Err(PyNotImplementedError::new_err("rolling min"))
1159                    },
1160                    RollingFunction::Max(_) => {
1161                        return Err(PyNotImplementedError::new_err("rolling max"))
1162                    },
1163                    RollingFunction::Mean(_) => {
1164                        return Err(PyNotImplementedError::new_err("rolling mean"))
1165                    },
1166                    RollingFunction::Sum(_) => {
1167                        return Err(PyNotImplementedError::new_err("rolling sum"))
1168                    },
1169                    RollingFunction::Quantile(_) => {
1170                        return Err(PyNotImplementedError::new_err("rolling quantile"))
1171                    },
1172                    RollingFunction::Var(_) => {
1173                        return Err(PyNotImplementedError::new_err("rolling var"))
1174                    },
1175                    RollingFunction::Std(_) => {
1176                        return Err(PyNotImplementedError::new_err("rolling std"))
1177                    },
1178                    RollingFunction::Skew(_, _) => {
1179                        return Err(PyNotImplementedError::new_err("rolling skew"))
1180                    },
1181                    RollingFunction::CorrCov { .. } => {
1182                        return Err(PyNotImplementedError::new_err("rolling cor_cov"))
1183                    },
1184                },
1185                FunctionExpr::RollingExprBy(rolling) => match rolling {
1186                    RollingFunctionBy::MinBy(_) => {
1187                        return Err(PyNotImplementedError::new_err("rolling min by"))
1188                    },
1189                    RollingFunctionBy::MaxBy(_) => {
1190                        return Err(PyNotImplementedError::new_err("rolling max by"))
1191                    },
1192                    RollingFunctionBy::MeanBy(_) => {
1193                        return Err(PyNotImplementedError::new_err("rolling mean by"))
1194                    },
1195                    RollingFunctionBy::SumBy(_) => {
1196                        return Err(PyNotImplementedError::new_err("rolling sum by"))
1197                    },
1198                    RollingFunctionBy::QuantileBy(_) => {
1199                        return Err(PyNotImplementedError::new_err("rolling quantile by"))
1200                    },
1201                    RollingFunctionBy::VarBy(_) => {
1202                        return Err(PyNotImplementedError::new_err("rolling var by"))
1203                    },
1204                    RollingFunctionBy::StdBy(_) => {
1205                        return Err(PyNotImplementedError::new_err("rolling std by"))
1206                    },
1207                },
1208                FunctionExpr::ShiftAndFill => ("shift_and_fill",).into_py_any(py),
1209                FunctionExpr::Shift => ("shift",).into_py_any(py),
1210                FunctionExpr::DropNans => ("drop_nans",).into_py_any(py),
1211                FunctionExpr::DropNulls => ("drop_nulls",).into_py_any(py),
1212                FunctionExpr::Mode => ("mode",).into_py_any(py),
1213                FunctionExpr::Skew(bias) => ("skew", bias).into_py_any(py),
1214                FunctionExpr::Kurtosis(fisher, bias) => ("kurtosis", fisher, bias).into_py_any(py),
1215                FunctionExpr::Reshape(_) => return Err(PyNotImplementedError::new_err("reshape")),
1216                #[cfg(feature = "repeat_by")]
1217                FunctionExpr::RepeatBy => ("repeat_by",).into_py_any(py),
1218                FunctionExpr::ArgUnique => ("arg_unique",).into_py_any(py),
1219                FunctionExpr::Repeat => ("repeat",).into_py_any(py),
1220                FunctionExpr::Rank {
1221                    options: _,
1222                    seed: _,
1223                } => return Err(PyNotImplementedError::new_err("rank")),
1224                FunctionExpr::Clip { has_min, has_max } => {
1225                    ("clip", has_min, has_max).into_py_any(py)
1226                },
1227                FunctionExpr::AsStruct => ("as_struct",).into_py_any(py),
1228                #[cfg(feature = "top_k")]
1229                FunctionExpr::TopK { descending } => ("top_k", descending).into_py_any(py),
1230                FunctionExpr::CumCount { reverse } => ("cum_count", reverse).into_py_any(py),
1231                FunctionExpr::CumSum { reverse } => ("cum_sum", reverse).into_py_any(py),
1232                FunctionExpr::CumProd { reverse } => ("cum_prod", reverse).into_py_any(py),
1233                FunctionExpr::CumMin { reverse } => ("cum_min", reverse).into_py_any(py),
1234                FunctionExpr::CumMax { reverse } => ("cum_max", reverse).into_py_any(py),
1235                FunctionExpr::Reverse => ("reverse",).into_py_any(py),
1236                FunctionExpr::ValueCounts {
1237                    sort,
1238                    parallel,
1239                    name,
1240                    normalize,
1241                } => ("value_counts", sort, parallel, name.as_str(), normalize).into_py_any(py),
1242                FunctionExpr::UniqueCounts => ("unique_counts",).into_py_any(py),
1243                FunctionExpr::ApproxNUnique => ("approx_n_unique",).into_py_any(py),
1244                FunctionExpr::Coalesce => ("coalesce",).into_py_any(py),
1245                FunctionExpr::ShrinkType => ("shrink_dtype",).into_py_any(py),
1246                FunctionExpr::Diff(n, null_behaviour) => (
1247                    "diff",
1248                    n,
1249                    match null_behaviour {
1250                        NullBehavior::Drop => "drop",
1251                        NullBehavior::Ignore => "ignore",
1252                    },
1253                )
1254                    .into_py_any(py),
1255                #[cfg(feature = "pct_change")]
1256                FunctionExpr::PctChange => ("pct_change",).into_py_any(py),
1257                FunctionExpr::Interpolate(method) => (
1258                    "interpolate",
1259                    match method {
1260                        InterpolationMethod::Linear => "linear",
1261                        InterpolationMethod::Nearest => "nearest",
1262                    },
1263                )
1264                    .into_py_any(py),
1265                FunctionExpr::InterpolateBy => ("interpolate_by",).into_py_any(py),
1266                FunctionExpr::Entropy { base, normalize } => {
1267                    ("entropy", base, normalize).into_py_any(py)
1268                },
1269                FunctionExpr::Log { base } => ("log", base).into_py_any(py),
1270                FunctionExpr::Log1p => ("log1p",).into_py_any(py),
1271                FunctionExpr::Exp => ("exp",).into_py_any(py),
1272                FunctionExpr::Unique(maintain_order) => ("unique", maintain_order).into_py_any(py),
1273                FunctionExpr::Round { decimals } => ("round", decimals).into_py_any(py),
1274                FunctionExpr::RoundSF { digits } => ("round_sig_figs", digits).into_py_any(py),
1275                FunctionExpr::Floor => ("floor",).into_py_any(py),
1276                FunctionExpr::Ceil => ("ceil",).into_py_any(py),
1277                FunctionExpr::UpperBound => ("upper_bound",).into_py_any(py),
1278                FunctionExpr::LowerBound => ("lower_bound",).into_py_any(py),
1279                FunctionExpr::Fused(_) => return Err(PyNotImplementedError::new_err("fused")),
1280                FunctionExpr::ConcatExpr(_) => {
1281                    return Err(PyNotImplementedError::new_err("concat expr"))
1282                },
1283                FunctionExpr::Correlation { .. } => {
1284                    return Err(PyNotImplementedError::new_err("corr"))
1285                },
1286                #[cfg(feature = "peaks")]
1287                FunctionExpr::PeakMin => ("peak_max",).into_py_any(py),
1288                #[cfg(feature = "peaks")]
1289                FunctionExpr::PeakMax => ("peak_min",).into_py_any(py),
1290                #[cfg(feature = "cutqcut")]
1291                FunctionExpr::Cut { .. } => return Err(PyNotImplementedError::new_err("cut")),
1292                #[cfg(feature = "cutqcut")]
1293                FunctionExpr::QCut { .. } => return Err(PyNotImplementedError::new_err("qcut")),
1294                #[cfg(feature = "rle")]
1295                FunctionExpr::RLE => ("rle",).into_py_any(py),
1296                #[cfg(feature = "rle")]
1297                FunctionExpr::RLEID => ("rle_id",).into_py_any(py),
1298                FunctionExpr::ToPhysical => ("to_physical",).into_py_any(py),
1299                FunctionExpr::Random { .. } => {
1300                    return Err(PyNotImplementedError::new_err("random"))
1301                },
1302                FunctionExpr::SetSortedFlag(sorted) => (
1303                    "set_sorted",
1304                    match sorted {
1305                        IsSorted::Ascending => "ascending",
1306                        IsSorted::Descending => "descending",
1307                        IsSorted::Not => "not",
1308                    },
1309                )
1310                    .into_py_any(py),
1311                #[cfg(feature = "ffi_plugin")]
1312                FunctionExpr::FfiPlugin { .. } => {
1313                    return Err(PyNotImplementedError::new_err("ffi plugin"))
1314                },
1315                FunctionExpr::BackwardFill { limit } => ("backward_fill", limit).into_py_any(py),
1316                FunctionExpr::ForwardFill { limit } => ("forward_fill", limit).into_py_any(py),
1317                FunctionExpr::SumHorizontal { ignore_nulls } => {
1318                    ("sum_horizontal", ignore_nulls).into_py_any(py)
1319                },
1320                FunctionExpr::MaxHorizontal => ("max_horizontal",).into_py_any(py),
1321                FunctionExpr::MeanHorizontal { ignore_nulls } => {
1322                    ("mean_horizontal", ignore_nulls).into_py_any(py)
1323                },
1324                FunctionExpr::MinHorizontal => ("min_horizontal",).into_py_any(py),
1325                FunctionExpr::EwmMean { options: _ } => {
1326                    return Err(PyNotImplementedError::new_err("ewm mean"))
1327                },
1328                FunctionExpr::EwmStd { options: _ } => {
1329                    return Err(PyNotImplementedError::new_err("ewm std"))
1330                },
1331                FunctionExpr::EwmVar { options: _ } => {
1332                    return Err(PyNotImplementedError::new_err("ewm var"))
1333                },
1334                FunctionExpr::Replace => ("replace",).into_py_any(py),
1335                FunctionExpr::ReplaceStrict { return_dtype: _ } => {
1336                    // Can ignore the return dtype because it is encoded in the schema.
1337                    ("replace_strict",).into_py_any(py)
1338                },
1339                FunctionExpr::Negate => ("negate",).into_py_any(py),
1340                FunctionExpr::FillNullWithStrategy(_) => {
1341                    return Err(PyNotImplementedError::new_err("fill null with strategy"))
1342                },
1343                FunctionExpr::GatherEvery { n, offset } => {
1344                    ("gather_every", offset, n).into_py_any(py)
1345                },
1346                FunctionExpr::Reinterpret(signed) => ("reinterpret", signed).into_py_any(py),
1347                FunctionExpr::ExtendConstant => ("extend_constant",).into_py_any(py),
1348                FunctionExpr::Business(_) => {
1349                    return Err(PyNotImplementedError::new_err("business"))
1350                },
1351                #[cfg(feature = "top_k")]
1352                FunctionExpr::TopKBy { descending } => ("top_k_by", descending).into_py_any(py),
1353                FunctionExpr::EwmMeanBy { half_life: _ } => {
1354                    return Err(PyNotImplementedError::new_err("ewm_mean_by"))
1355                },
1356            }?,
1357            options: py.None(),
1358        }
1359        .into_py_any(py),
1360        AExpr::Window {
1361            function,
1362            partition_by,
1363            order_by,
1364            options,
1365        } => {
1366            let function = function.0;
1367            let partition_by = partition_by.iter().map(|n| n.0).collect();
1368            let order_by_descending = order_by
1369                .map(|(_, options)| options.descending)
1370                .unwrap_or(false);
1371            let order_by_nulls_last = order_by
1372                .map(|(_, options)| options.nulls_last)
1373                .unwrap_or(false);
1374            let order_by = order_by.map(|(n, _)| n.0);
1375
1376            let options = match options {
1377                WindowType::Over(options) => PyWindowMapping { inner: *options }.into_py_any(py)?,
1378                WindowType::Rolling(options) => PyRollingGroupOptions {
1379                    inner: options.clone(),
1380                }
1381                .into_py_any(py)?,
1382            };
1383            Window {
1384                function,
1385                partition_by,
1386                order_by,
1387                order_by_descending,
1388                order_by_nulls_last,
1389                options,
1390            }
1391            .into_py_any(py)
1392        },
1393        AExpr::Slice {
1394            input,
1395            offset,
1396            length,
1397        } => Slice {
1398            input: input.0,
1399            offset: offset.0,
1400            length: length.0,
1401        }
1402        .into_py_any(py),
1403        AExpr::Len => Len {}.into_py_any(py),
1404    }
1405}