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                    StringFunction::Normalize { form } => (
810                        PyStringFunction::Normalize,
811                        match form {
812                            UnicodeForm::NFC => "nfc",
813                            UnicodeForm::NFKC => "nfkc",
814                            UnicodeForm::NFD => "nfd",
815                            UnicodeForm::NFKD => "nfkd",
816                        },
817                    )
818                        .into_py_any(py),
819                    StringFunction::Reverse => (PyStringFunction::Reverse,).into_py_any(py),
820                    StringFunction::PadStart { length, fill_char } => {
821                        (PyStringFunction::PadStart, length, fill_char).into_py_any(py)
822                    },
823                    StringFunction::PadEnd { length, fill_char } => {
824                        (PyStringFunction::PadEnd, length, fill_char).into_py_any(py)
825                    },
826                    StringFunction::Slice => (PyStringFunction::Slice,).into_py_any(py),
827                    StringFunction::Head => (PyStringFunction::Head,).into_py_any(py),
828                    StringFunction::Tail => (PyStringFunction::Tail,).into_py_any(py),
829                    StringFunction::HexEncode => (PyStringFunction::HexEncode,).into_py_any(py),
830                    #[cfg(feature = "binary_encoding")]
831                    StringFunction::HexDecode(strict) => {
832                        (PyStringFunction::HexDecode, strict).into_py_any(py)
833                    },
834                    StringFunction::Base64Encode => {
835                        (PyStringFunction::Base64Encode,).into_py_any(py)
836                    },
837                    #[cfg(feature = "binary_encoding")]
838                    StringFunction::Base64Decode(strict) => {
839                        (PyStringFunction::Base64Decode, strict).into_py_any(py)
840                    },
841                    StringFunction::StartsWith => (PyStringFunction::StartsWith,).into_py_any(py),
842                    StringFunction::StripChars => (PyStringFunction::StripChars,).into_py_any(py),
843                    StringFunction::StripCharsStart => {
844                        (PyStringFunction::StripCharsStart,).into_py_any(py)
845                    },
846                    StringFunction::StripCharsEnd => {
847                        (PyStringFunction::StripCharsEnd,).into_py_any(py)
848                    },
849                    StringFunction::StripPrefix => (PyStringFunction::StripPrefix,).into_py_any(py),
850                    StringFunction::StripSuffix => (PyStringFunction::StripSuffix,).into_py_any(py),
851                    StringFunction::SplitExact { n, inclusive } => {
852                        (PyStringFunction::SplitExact, n, inclusive).into_py_any(py)
853                    },
854                    StringFunction::SplitN(n) => (PyStringFunction::SplitN, n).into_py_any(py),
855                    StringFunction::Strptime(_, options) => (
856                        PyStringFunction::Strptime,
857                        options.format.as_ref().map(|s| s.as_str()),
858                        options.strict,
859                        options.exact,
860                        options.cache,
861                    )
862                        .into_py_any(py),
863                    StringFunction::Split(inclusive) => {
864                        (PyStringFunction::Split, inclusive).into_py_any(py)
865                    },
866                    StringFunction::ToDecimal(inference_length) => {
867                        (PyStringFunction::ToDecimal, inference_length).into_py_any(py)
868                    },
869                    #[cfg(feature = "nightly")]
870                    StringFunction::Titlecase => (PyStringFunction::Titlecase,).into_py_any(py),
871                    StringFunction::Uppercase => (PyStringFunction::Uppercase,).into_py_any(py),
872                    StringFunction::ZFill => (PyStringFunction::ZFill,).into_py_any(py),
873                    #[cfg(feature = "find_many")]
874                    StringFunction::ContainsAny {
875                        ascii_case_insensitive,
876                    } => (PyStringFunction::ContainsAny, ascii_case_insensitive).into_py_any(py),
877                    #[cfg(feature = "find_many")]
878                    StringFunction::ReplaceMany {
879                        ascii_case_insensitive,
880                    } => (PyStringFunction::ReplaceMany, ascii_case_insensitive).into_py_any(py),
881                    #[cfg(feature = "find_many")]
882                    StringFunction::ExtractMany { .. } => {
883                        return Err(PyNotImplementedError::new_err("extract_many"));
884                    },
885                    #[cfg(feature = "find_many")]
886                    StringFunction::FindMany { .. } => {
887                        return Err(PyNotImplementedError::new_err("find_many"));
888                    },
889                    #[cfg(feature = "regex")]
890                    StringFunction::EscapeRegex => (PyStringFunction::EscapeRegex,).into_py_any(py),
891                },
892                FunctionExpr::StructExpr(_) => {
893                    return Err(PyNotImplementedError::new_err("struct expr"));
894                },
895                FunctionExpr::TemporalExpr(fun) => match fun {
896                    TemporalFunction::Millennium => {
897                        (PyTemporalFunction::Millennium,).into_py_any(py)
898                    },
899                    TemporalFunction::Century => (PyTemporalFunction::Century,).into_py_any(py),
900                    TemporalFunction::Year => (PyTemporalFunction::Year,).into_py_any(py),
901                    TemporalFunction::IsLeapYear => {
902                        (PyTemporalFunction::IsLeapYear,).into_py_any(py)
903                    },
904                    TemporalFunction::IsoYear => (PyTemporalFunction::IsoYear,).into_py_any(py),
905                    TemporalFunction::Quarter => (PyTemporalFunction::Quarter,).into_py_any(py),
906                    TemporalFunction::Month => (PyTemporalFunction::Month,).into_py_any(py),
907                    TemporalFunction::Week => (PyTemporalFunction::Week,).into_py_any(py),
908                    TemporalFunction::WeekDay => (PyTemporalFunction::WeekDay,).into_py_any(py),
909                    TemporalFunction::Day => (PyTemporalFunction::Day,).into_py_any(py),
910                    TemporalFunction::OrdinalDay => {
911                        (PyTemporalFunction::OrdinalDay,).into_py_any(py)
912                    },
913                    TemporalFunction::Time => (PyTemporalFunction::Time,).into_py_any(py),
914                    TemporalFunction::Date => (PyTemporalFunction::Date,).into_py_any(py),
915                    TemporalFunction::Datetime => (PyTemporalFunction::Datetime,).into_py_any(py),
916                    TemporalFunction::Duration(time_unit) => {
917                        (PyTemporalFunction::Duration, Wrap(*time_unit)).into_py_any(py)
918                    },
919                    TemporalFunction::Hour => (PyTemporalFunction::Hour,).into_py_any(py),
920                    TemporalFunction::Minute => (PyTemporalFunction::Minute,).into_py_any(py),
921                    TemporalFunction::Second => (PyTemporalFunction::Second,).into_py_any(py),
922                    TemporalFunction::Millisecond => {
923                        (PyTemporalFunction::Millisecond,).into_py_any(py)
924                    },
925                    TemporalFunction::Microsecond => {
926                        (PyTemporalFunction::Microsecond,).into_py_any(py)
927                    },
928                    TemporalFunction::Nanosecond => {
929                        (PyTemporalFunction::Nanosecond,).into_py_any(py)
930                    },
931                    TemporalFunction::TotalDays => (PyTemporalFunction::TotalDays,).into_py_any(py),
932                    TemporalFunction::TotalHours => {
933                        (PyTemporalFunction::TotalHours,).into_py_any(py)
934                    },
935                    TemporalFunction::TotalMinutes => {
936                        (PyTemporalFunction::TotalMinutes,).into_py_any(py)
937                    },
938                    TemporalFunction::TotalSeconds => {
939                        (PyTemporalFunction::TotalSeconds,).into_py_any(py)
940                    },
941                    TemporalFunction::TotalMilliseconds => {
942                        (PyTemporalFunction::TotalMilliseconds,).into_py_any(py)
943                    },
944                    TemporalFunction::TotalMicroseconds => {
945                        (PyTemporalFunction::TotalMicroseconds,).into_py_any(py)
946                    },
947                    TemporalFunction::TotalNanoseconds => {
948                        (PyTemporalFunction::TotalNanoseconds,).into_py_any(py)
949                    },
950                    TemporalFunction::ToString(format) => {
951                        (PyTemporalFunction::ToString, format).into_py_any(py)
952                    },
953                    TemporalFunction::CastTimeUnit(time_unit) => {
954                        (PyTemporalFunction::CastTimeUnit, Wrap(*time_unit)).into_py_any(py)
955                    },
956                    TemporalFunction::WithTimeUnit(time_unit) => {
957                        (PyTemporalFunction::WithTimeUnit, Wrap(*time_unit)).into_py_any(py)
958                    },
959                    #[cfg(feature = "timezones")]
960                    TemporalFunction::ConvertTimeZone(time_zone) => {
961                        (PyTemporalFunction::ConvertTimeZone, time_zone.as_str()).into_py_any(py)
962                    },
963                    TemporalFunction::TimeStamp(time_unit) => {
964                        (PyTemporalFunction::TimeStamp, Wrap(*time_unit)).into_py_any(py)
965                    },
966                    TemporalFunction::Truncate => (PyTemporalFunction::Truncate,).into_py_any(py),
967                    TemporalFunction::OffsetBy => (PyTemporalFunction::OffsetBy,).into_py_any(py),
968                    TemporalFunction::MonthStart => {
969                        (PyTemporalFunction::MonthStart,).into_py_any(py)
970                    },
971                    TemporalFunction::MonthEnd => (PyTemporalFunction::MonthEnd,).into_py_any(py),
972                    #[cfg(feature = "timezones")]
973                    TemporalFunction::BaseUtcOffset => {
974                        (PyTemporalFunction::BaseUtcOffset,).into_py_any(py)
975                    },
976                    #[cfg(feature = "timezones")]
977                    TemporalFunction::DSTOffset => (PyTemporalFunction::DSTOffset,).into_py_any(py),
978                    TemporalFunction::Round => (PyTemporalFunction::Round,).into_py_any(py),
979                    TemporalFunction::Replace => (PyTemporalFunction::Replace).into_py_any(py),
980                    #[cfg(feature = "timezones")]
981                    TemporalFunction::ReplaceTimeZone(time_zone, non_existent) => (
982                        PyTemporalFunction::ReplaceTimeZone,
983                        time_zone.as_ref().map(|s| s.as_str()),
984                        Into::<&str>::into(non_existent),
985                    )
986                        .into_py_any(py),
987                    TemporalFunction::Combine(time_unit) => {
988                        (PyTemporalFunction::Combine, Wrap(*time_unit)).into_py_any(py)
989                    },
990                    TemporalFunction::DatetimeFunction {
991                        time_unit,
992                        time_zone,
993                    } => (
994                        PyTemporalFunction::DatetimeFunction,
995                        Wrap(*time_unit),
996                        time_zone.as_ref().map(|s| s.as_str()),
997                    )
998                        .into_py_any(py),
999                },
1000                FunctionExpr::Boolean(boolfun) => match boolfun {
1001                    BooleanFunction::Any { ignore_nulls } => {
1002                        (PyBooleanFunction::Any, *ignore_nulls).into_py_any(py)
1003                    },
1004                    BooleanFunction::All { ignore_nulls } => {
1005                        (PyBooleanFunction::All, *ignore_nulls).into_py_any(py)
1006                    },
1007                    BooleanFunction::IsNull => (PyBooleanFunction::IsNull,).into_py_any(py),
1008                    BooleanFunction::IsNotNull => (PyBooleanFunction::IsNotNull,).into_py_any(py),
1009                    BooleanFunction::IsFinite => (PyBooleanFunction::IsFinite,).into_py_any(py),
1010                    BooleanFunction::IsInfinite => (PyBooleanFunction::IsInfinite,).into_py_any(py),
1011                    BooleanFunction::IsNan => (PyBooleanFunction::IsNan,).into_py_any(py),
1012                    BooleanFunction::IsNotNan => (PyBooleanFunction::IsNotNan,).into_py_any(py),
1013                    BooleanFunction::IsFirstDistinct => {
1014                        (PyBooleanFunction::IsFirstDistinct,).into_py_any(py)
1015                    },
1016                    BooleanFunction::IsLastDistinct => {
1017                        (PyBooleanFunction::IsLastDistinct,).into_py_any(py)
1018                    },
1019                    BooleanFunction::IsUnique => (PyBooleanFunction::IsUnique,).into_py_any(py),
1020                    BooleanFunction::IsDuplicated => {
1021                        (PyBooleanFunction::IsDuplicated,).into_py_any(py)
1022                    },
1023                    BooleanFunction::IsBetween { closed } => {
1024                        (PyBooleanFunction::IsBetween, Into::<&str>::into(closed)).into_py_any(py)
1025                    },
1026                    #[cfg(feature = "is_in")]
1027                    BooleanFunction::IsIn { nulls_equal } => {
1028                        (PyBooleanFunction::IsIn, nulls_equal).into_py_any(py)
1029                    },
1030                    BooleanFunction::AllHorizontal => {
1031                        (PyBooleanFunction::AllHorizontal,).into_py_any(py)
1032                    },
1033                    BooleanFunction::AnyHorizontal => {
1034                        (PyBooleanFunction::AnyHorizontal,).into_py_any(py)
1035                    },
1036                    BooleanFunction::Not => (PyBooleanFunction::Not,).into_py_any(py),
1037                },
1038                FunctionExpr::Abs => ("abs",).into_py_any(py),
1039                #[cfg(feature = "hist")]
1040                FunctionExpr::Hist {
1041                    bin_count,
1042                    include_category,
1043                    include_breakpoint,
1044                } => ("hist", bin_count, include_category, include_breakpoint).into_py_any(py),
1045                FunctionExpr::NullCount => ("null_count",).into_py_any(py),
1046                FunctionExpr::Pow(f) => match f {
1047                    PowFunction::Generic => ("pow",).into_py_any(py),
1048                    PowFunction::Sqrt => ("sqrt",).into_py_any(py),
1049                    PowFunction::Cbrt => ("cbrt",).into_py_any(py),
1050                },
1051                FunctionExpr::Hash(seed, seed_1, seed_2, seed_3) => {
1052                    ("hash", seed, seed_1, seed_2, seed_3).into_py_any(py)
1053                },
1054                FunctionExpr::ArgWhere => ("argwhere",).into_py_any(py),
1055                #[cfg(feature = "index_of")]
1056                FunctionExpr::IndexOf => ("index_of",).into_py_any(py),
1057                #[cfg(feature = "search_sorted")]
1058                FunctionExpr::SearchSorted(side) => (
1059                    "search_sorted",
1060                    match side {
1061                        SearchSortedSide::Any => "any",
1062                        SearchSortedSide::Left => "left",
1063                        SearchSortedSide::Right => "right",
1064                    },
1065                )
1066                    .into_py_any(py),
1067                FunctionExpr::Range(_) => return Err(PyNotImplementedError::new_err("range")),
1068                #[cfg(feature = "trigonometry")]
1069                FunctionExpr::Trigonometry(trigfun) => {
1070                    use polars_plan::dsl::function_expr::trigonometry::TrigonometricFunction;
1071
1072                    match trigfun {
1073                        TrigonometricFunction::Cos => ("cos",),
1074                        TrigonometricFunction::Cot => ("cot",),
1075                        TrigonometricFunction::Sin => ("sin",),
1076                        TrigonometricFunction::Tan => ("tan",),
1077                        TrigonometricFunction::ArcCos => ("arccos",),
1078                        TrigonometricFunction::ArcSin => ("arcsin",),
1079                        TrigonometricFunction::ArcTan => ("arctan",),
1080                        TrigonometricFunction::Cosh => ("cosh",),
1081                        TrigonometricFunction::Sinh => ("sinh",),
1082                        TrigonometricFunction::Tanh => ("tanh",),
1083                        TrigonometricFunction::ArcCosh => ("arccosh",),
1084                        TrigonometricFunction::ArcSinh => ("arcsinh",),
1085                        TrigonometricFunction::ArcTanh => ("arctanh",),
1086                        TrigonometricFunction::Degrees => ("degrees",),
1087                        TrigonometricFunction::Radians => ("radians",),
1088                    }
1089                    .into_py_any(py)
1090                },
1091                #[cfg(feature = "trigonometry")]
1092                FunctionExpr::Atan2 => ("atan2",).into_py_any(py),
1093                #[cfg(feature = "sign")]
1094                FunctionExpr::Sign => ("sign",).into_py_any(py),
1095                FunctionExpr::FillNull => ("fill_null",).into_py_any(py),
1096                FunctionExpr::RollingExpr(rolling) => {
1097                    return Err(PyNotImplementedError::new_err(format!("{}", rolling)));
1098                },
1099                FunctionExpr::RollingExprBy(rolling) => match rolling {
1100                    RollingFunctionBy::MinBy(_) => {
1101                        return Err(PyNotImplementedError::new_err("rolling min by"));
1102                    },
1103                    RollingFunctionBy::MaxBy(_) => {
1104                        return Err(PyNotImplementedError::new_err("rolling max by"));
1105                    },
1106                    RollingFunctionBy::MeanBy(_) => {
1107                        return Err(PyNotImplementedError::new_err("rolling mean by"));
1108                    },
1109                    RollingFunctionBy::SumBy(_) => {
1110                        return Err(PyNotImplementedError::new_err("rolling sum by"));
1111                    },
1112                    RollingFunctionBy::QuantileBy(_) => {
1113                        return Err(PyNotImplementedError::new_err("rolling quantile by"));
1114                    },
1115                    RollingFunctionBy::VarBy(_) => {
1116                        return Err(PyNotImplementedError::new_err("rolling var by"));
1117                    },
1118                    RollingFunctionBy::StdBy(_) => {
1119                        return Err(PyNotImplementedError::new_err("rolling std by"));
1120                    },
1121                },
1122                FunctionExpr::ShiftAndFill => ("shift_and_fill",).into_py_any(py),
1123                FunctionExpr::Shift => ("shift",).into_py_any(py),
1124                FunctionExpr::DropNans => ("drop_nans",).into_py_any(py),
1125                FunctionExpr::DropNulls => ("drop_nulls",).into_py_any(py),
1126                FunctionExpr::Mode => ("mode",).into_py_any(py),
1127                FunctionExpr::Skew(bias) => ("skew", bias).into_py_any(py),
1128                FunctionExpr::Kurtosis(fisher, bias) => ("kurtosis", fisher, bias).into_py_any(py),
1129                FunctionExpr::Reshape(_) => return Err(PyNotImplementedError::new_err("reshape")),
1130                #[cfg(feature = "repeat_by")]
1131                FunctionExpr::RepeatBy => ("repeat_by",).into_py_any(py),
1132                FunctionExpr::ArgUnique => ("arg_unique",).into_py_any(py),
1133                FunctionExpr::Repeat => ("repeat",).into_py_any(py),
1134                FunctionExpr::Rank {
1135                    options: _,
1136                    seed: _,
1137                } => return Err(PyNotImplementedError::new_err("rank")),
1138                FunctionExpr::Clip { has_min, has_max } => {
1139                    ("clip", has_min, has_max).into_py_any(py)
1140                },
1141                FunctionExpr::AsStruct => ("as_struct",).into_py_any(py),
1142                #[cfg(feature = "top_k")]
1143                FunctionExpr::TopK { descending } => ("top_k", descending).into_py_any(py),
1144                FunctionExpr::CumCount { reverse } => ("cum_count", reverse).into_py_any(py),
1145                FunctionExpr::CumSum { reverse } => ("cum_sum", reverse).into_py_any(py),
1146                FunctionExpr::CumProd { reverse } => ("cum_prod", reverse).into_py_any(py),
1147                FunctionExpr::CumMin { reverse } => ("cum_min", reverse).into_py_any(py),
1148                FunctionExpr::CumMax { reverse } => ("cum_max", reverse).into_py_any(py),
1149                FunctionExpr::Reverse => ("reverse",).into_py_any(py),
1150                FunctionExpr::ValueCounts {
1151                    sort,
1152                    parallel,
1153                    name,
1154                    normalize,
1155                } => ("value_counts", sort, parallel, name.as_str(), normalize).into_py_any(py),
1156                FunctionExpr::UniqueCounts => ("unique_counts",).into_py_any(py),
1157                FunctionExpr::ApproxNUnique => ("approx_n_unique",).into_py_any(py),
1158                FunctionExpr::Coalesce => ("coalesce",).into_py_any(py),
1159                FunctionExpr::ShrinkType => ("shrink_dtype",).into_py_any(py),
1160                FunctionExpr::Diff(null_behaviour) => (
1161                    "diff",
1162                    match null_behaviour {
1163                        NullBehavior::Drop => "drop",
1164                        NullBehavior::Ignore => "ignore",
1165                    },
1166                )
1167                    .into_py_any(py),
1168                #[cfg(feature = "pct_change")]
1169                FunctionExpr::PctChange => ("pct_change",).into_py_any(py),
1170                FunctionExpr::Interpolate(method) => (
1171                    "interpolate",
1172                    match method {
1173                        InterpolationMethod::Linear => "linear",
1174                        InterpolationMethod::Nearest => "nearest",
1175                    },
1176                )
1177                    .into_py_any(py),
1178                FunctionExpr::InterpolateBy => ("interpolate_by",).into_py_any(py),
1179                FunctionExpr::Entropy { base, normalize } => {
1180                    ("entropy", base, normalize).into_py_any(py)
1181                },
1182                FunctionExpr::Log { base } => ("log", base).into_py_any(py),
1183                FunctionExpr::Log1p => ("log1p",).into_py_any(py),
1184                FunctionExpr::Exp => ("exp",).into_py_any(py),
1185                FunctionExpr::Unique(maintain_order) => ("unique", maintain_order).into_py_any(py),
1186                FunctionExpr::Round { decimals, mode } => {
1187                    ("round", decimals, Into::<&str>::into(mode)).into_py_any(py)
1188                },
1189                FunctionExpr::RoundSF { digits } => ("round_sig_figs", digits).into_py_any(py),
1190                FunctionExpr::Floor => ("floor",).into_py_any(py),
1191                FunctionExpr::Ceil => ("ceil",).into_py_any(py),
1192                FunctionExpr::UpperBound => ("upper_bound",).into_py_any(py),
1193                FunctionExpr::LowerBound => ("lower_bound",).into_py_any(py),
1194                FunctionExpr::Fused(_) => return Err(PyNotImplementedError::new_err("fused")),
1195                FunctionExpr::ConcatExpr(_) => {
1196                    return Err(PyNotImplementedError::new_err("concat expr"));
1197                },
1198                FunctionExpr::Correlation { .. } => {
1199                    return Err(PyNotImplementedError::new_err("corr"));
1200                },
1201                #[cfg(feature = "peaks")]
1202                FunctionExpr::PeakMin => ("peak_max",).into_py_any(py),
1203                #[cfg(feature = "peaks")]
1204                FunctionExpr::PeakMax => ("peak_min",).into_py_any(py),
1205                #[cfg(feature = "cutqcut")]
1206                FunctionExpr::Cut { .. } => return Err(PyNotImplementedError::new_err("cut")),
1207                #[cfg(feature = "cutqcut")]
1208                FunctionExpr::QCut { .. } => return Err(PyNotImplementedError::new_err("qcut")),
1209                #[cfg(feature = "rle")]
1210                FunctionExpr::RLE => ("rle",).into_py_any(py),
1211                #[cfg(feature = "rle")]
1212                FunctionExpr::RLEID => ("rle_id",).into_py_any(py),
1213                FunctionExpr::ToPhysical => ("to_physical",).into_py_any(py),
1214                FunctionExpr::Random { .. } => {
1215                    return Err(PyNotImplementedError::new_err("random"));
1216                },
1217                FunctionExpr::SetSortedFlag(sorted) => (
1218                    "set_sorted",
1219                    match sorted {
1220                        IsSorted::Ascending => "ascending",
1221                        IsSorted::Descending => "descending",
1222                        IsSorted::Not => "not",
1223                    },
1224                )
1225                    .into_py_any(py),
1226                #[cfg(feature = "ffi_plugin")]
1227                FunctionExpr::FfiPlugin { .. } => {
1228                    return Err(PyNotImplementedError::new_err("ffi plugin"));
1229                },
1230                FunctionExpr::SumHorizontal { ignore_nulls } => {
1231                    ("sum_horizontal", ignore_nulls).into_py_any(py)
1232                },
1233                FunctionExpr::MaxHorizontal => ("max_horizontal",).into_py_any(py),
1234                FunctionExpr::MeanHorizontal { ignore_nulls } => {
1235                    ("mean_horizontal", ignore_nulls).into_py_any(py)
1236                },
1237                FunctionExpr::MinHorizontal => ("min_horizontal",).into_py_any(py),
1238                FunctionExpr::EwmMean { options: _ } => {
1239                    return Err(PyNotImplementedError::new_err("ewm mean"));
1240                },
1241                FunctionExpr::EwmStd { options: _ } => {
1242                    return Err(PyNotImplementedError::new_err("ewm std"));
1243                },
1244                FunctionExpr::EwmVar { options: _ } => {
1245                    return Err(PyNotImplementedError::new_err("ewm var"));
1246                },
1247                FunctionExpr::Replace => ("replace",).into_py_any(py),
1248                FunctionExpr::ReplaceStrict { return_dtype: _ } => {
1249                    // Can ignore the return dtype because it is encoded in the schema.
1250                    ("replace_strict",).into_py_any(py)
1251                },
1252                FunctionExpr::Negate => ("negate",).into_py_any(py),
1253                FunctionExpr::FillNullWithStrategy(_) => {
1254                    return Err(PyNotImplementedError::new_err("fill null with strategy"));
1255                },
1256                FunctionExpr::GatherEvery { n, offset } => {
1257                    ("gather_every", offset, n).into_py_any(py)
1258                },
1259                FunctionExpr::Reinterpret(signed) => ("reinterpret", signed).into_py_any(py),
1260                FunctionExpr::ExtendConstant => ("extend_constant",).into_py_any(py),
1261                FunctionExpr::Business(_) => {
1262                    return Err(PyNotImplementedError::new_err("business"));
1263                },
1264                #[cfg(feature = "top_k")]
1265                FunctionExpr::TopKBy { descending } => ("top_k_by", descending).into_py_any(py),
1266                FunctionExpr::EwmMeanBy { half_life: _ } => {
1267                    return Err(PyNotImplementedError::new_err("ewm_mean_by"));
1268                },
1269            }?,
1270            options: py.None(),
1271        }
1272        .into_py_any(py),
1273        AExpr::Window {
1274            function,
1275            partition_by,
1276            order_by,
1277            options,
1278        } => {
1279            let function = function.0;
1280            let partition_by = partition_by.iter().map(|n| n.0).collect();
1281            let order_by_descending = order_by
1282                .map(|(_, options)| options.descending)
1283                .unwrap_or(false);
1284            let order_by_nulls_last = order_by
1285                .map(|(_, options)| options.nulls_last)
1286                .unwrap_or(false);
1287            let order_by = order_by.map(|(n, _)| n.0);
1288
1289            let options = match options {
1290                WindowType::Over(options) => PyWindowMapping { inner: *options }.into_py_any(py)?,
1291                WindowType::Rolling(options) => PyRollingGroupOptions {
1292                    inner: options.clone(),
1293                }
1294                .into_py_any(py)?,
1295            };
1296            Window {
1297                function,
1298                partition_by,
1299                order_by,
1300                order_by_descending,
1301                order_by_nulls_last,
1302                options,
1303            }
1304            .into_py_any(py)
1305        },
1306        AExpr::Slice {
1307            input,
1308            offset,
1309            length,
1310        } => Slice {
1311            input: input.0,
1312            offset: offset.0,
1313            length: length.0,
1314        }
1315        .into_py_any(py),
1316        AExpr::Len => Len {}.into_py_any(py),
1317    }
1318}