diff --git a/crates/polars-plan/src/dsl/functions/coerce.rs b/crates/polars-plan/src/dsl/functions/coerce.rs index b821ae247d705..40f4fd3c8a019 100644 --- a/crates/polars-plan/src/dsl/functions/coerce.rs +++ b/crates/polars-plan/src/dsl/functions/coerce.rs @@ -1,7 +1,13 @@ use super::*; /// Take several expressions and collect them into a [`StructChunked`]. +/// # Panics +/// panics if `exprs` is empty. pub fn as_struct(exprs: Vec) -> Expr { + assert!( + !exprs.is_empty(), + "expected at least 1 field in 'as_struct'" + ); Expr::Function { input: exprs, function: FunctionExpr::AsStruct, diff --git a/py-polars/polars/functions/as_datatype.py b/py-polars/polars/functions/as_datatype.py index d09a8cead6dce..a3ed5bb864142 100644 --- a/py-polars/polars/functions/as_datatype.py +++ b/py-polars/polars/functions/as_datatype.py @@ -596,7 +596,6 @@ def struct( Schema({'my_struct': Struct({'p': Int64, 'q': Boolean})}) """ pyexprs = parse_into_list_of_expressions(*exprs, **named_exprs) - expr = wrap_expr(plr.as_struct(pyexprs)) if schema: if not exprs: @@ -604,7 +603,11 @@ def struct( expr = wrap_expr( plr.as_struct(parse_into_list_of_expressions(list(schema.keys()))) ) + else: + expr = wrap_expr(plr.as_struct(pyexprs)) expr = expr.cast(Struct(schema), strict=False) + else: + expr = wrap_expr(plr.as_struct(pyexprs)) if eager: return F.select(expr).to_series() diff --git a/py-polars/src/functions/lazy.rs b/py-polars/src/functions/lazy.rs index c681e1aae6f57..49325e6171705 100644 --- a/py-polars/src/functions/lazy.rs +++ b/py-polars/src/functions/lazy.rs @@ -1,7 +1,7 @@ use polars::lazy::dsl; use polars::prelude::*; use polars_plan::prelude::UnionArgs; -use pyo3::exceptions::PyTypeError; +use pyo3::exceptions::{PyTypeError, PyValueError}; use pyo3::prelude::*; use pyo3::types::{PyBool, PyBytes, PyFloat, PyInt, PyString}; @@ -85,9 +85,14 @@ pub fn arg_where(condition: PyExpr) -> PyExpr { } #[pyfunction] -pub fn as_struct(exprs: Vec) -> PyExpr { +pub fn as_struct(exprs: Vec) -> PyResult { let exprs = exprs.to_exprs(); - dsl::as_struct(exprs).into() + if exprs.is_empty() { + return Err(PyValueError::new_err( + "expected at least 1 expression in 'as_struct'", + )); + } + Ok(dsl::as_struct(exprs).into()) } #[pyfunction] diff --git a/py-polars/tests/unit/datatypes/test_struct.py b/py-polars/tests/unit/datatypes/test_struct.py index f31a610d99398..eea22ee8f277d 100644 --- a/py-polars/tests/unit/datatypes/test_struct.py +++ b/py-polars/tests/unit/datatypes/test_struct.py @@ -965,3 +965,8 @@ def test_struct_out_nullability_from_arrow() -> None: df = pl.DataFrame(pd.DataFrame({"abc": [{"a": 1.0, "b": pd.NA}, pd.NA]})) res = df.select(a=pl.col("abc").struct.field("a")) assert res.to_dicts() == [{"a": 1.0}, {"a": None}] + + +def test_empty_struct_raise() -> None: + with pytest.raises(ValueError): + pl.struct()