Skip to content

Commit e2f8e48

Browse files
authored
fix(python): Guard against dictionaries being passed to with_columns (#22928)
1 parent 9d8139b commit e2f8e48

File tree

3 files changed

+40
-2
lines changed

3 files changed

+40
-2
lines changed

py-polars/polars/_utils/parse/expr.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import contextlib
4-
from collections.abc import Iterable
4+
from collections.abc import Iterable, Mapping
55
from typing import TYPE_CHECKING, Any
66

77
import polars._reexport as pl
@@ -120,6 +120,18 @@ def _parse_inputs_as_iterable(
120120
if not inputs:
121121
return []
122122

123+
# Ensures that the outermost element cannot be a Dictionary (as an iterable)
124+
if len(inputs) == 1 and isinstance(inputs[0], Mapping):
125+
msg = (
126+
"Cannot pass a dictionary as a single positional argument.\n"
127+
"If you merely want the *keys*, use:\n"
128+
" • df.method(*your_dict.keys())\n"
129+
"If you need the key value pairs, use one of:\n"
130+
" • unpack as keywords: df.method(**your_dict)\n"
131+
" • build expressions: df.method(expr.alias(k) for k, expr in your_dict.items())"
132+
)
133+
raise TypeError(msg)
134+
123135
# Treat elements of a single iterable as separate inputs
124136
if len(inputs) == 1 and _is_iterable(inputs[0]):
125137
return inputs[0]

py-polars/tests/unit/dataframe/test_df.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3249,3 +3249,29 @@ def test_nan_to_null() -> None:
32493249
)
32503250

32513251
assert_frame_equal(df1, df2)
3252+
3253+
3254+
# Below 3 tests for https://github.com/pola-rs/polars/issues/17879
3255+
3256+
3257+
def test_with_columns_dict_direct_typeerror() -> None:
3258+
data = {"a": pl.col("a") * 2}
3259+
df = pl.select(a=1)
3260+
with pytest.raises(
3261+
TypeError, match="Cannot pass a dictionary as a single positional argument"
3262+
):
3263+
df.with_columns(data)
3264+
3265+
3266+
def test_with_columns_dict_unpacking() -> None:
3267+
data = {"a": pl.col("a") * 2}
3268+
df = pl.select(a=1).with_columns(**data)
3269+
expected = pl.DataFrame({"a": [2]})
3270+
assert df.equals(expected)
3271+
3272+
3273+
def test_with_columns_generator_alias() -> None:
3274+
data = {"a": pl.col("a") * 2}
3275+
df = pl.select(a=1).with_columns(expr.alias(name) for name, expr in data.items())
3276+
expected = pl.DataFrame({"a": [2]})
3277+
assert df.equals(expected)

py-polars/tests/unit/test_projections.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ def test_projection_join_names_9955() -> None:
356356
how="inner",
357357
)
358358

359-
q = q.select(batting.collect_schema())
359+
q = q.select(*batting.collect_schema().keys())
360360

361361
assert q.collect().schema == {
362362
"playerID": pl.String,

0 commit comments

Comments
 (0)