Skip to content

Commit 58661c6

Browse files
authored
fix!: raise TypeError for invalid property setter arguments (#4068)
1 parent fab318c commit 58661c6

4 files changed

Lines changed: 51 additions & 12 deletions

File tree

altair/utils/schemapi.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,6 +1662,19 @@ def __get__(self, obj, cls):
16621662
return self
16631663

16641664
def __call__(self, *args: Any, **kwargs: Any):
1665+
name = f"{type(self.obj).__name__}.{self.prop}"
1666+
if len(args) > 1:
1667+
msg = (
1668+
f"{name}() accepts at most one positional argument, "
1669+
f"but {len(args)} were given"
1670+
)
1671+
raise TypeError(msg)
1672+
if args and kwargs:
1673+
msg = (
1674+
f"{name}() cannot combine a positional argument with keyword arguments"
1675+
)
1676+
raise TypeError(msg)
1677+
16651678
obj = self.obj.copy()
16661679
# TODO: use schema to validate
16671680
obj[self.prop] = args[0] if args else kwargs

tests/utils/test_schemapi.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,24 @@ def test_to_dict_datetime_unsupported_timezone(tzinfo: dt.timezone) -> None:
11471147
alt.FieldEqualPredicate(datetime.replace(tzinfo=tzinfo), "column 1")
11481148

11491149

1150+
@pytest.mark.parametrize("setter", ["scale", "axis", "sort"])
1151+
def test_property_setter_rejects_multiple_positional_arguments(setter: str) -> None:
1152+
method = getattr(alt.X("field:Q"), setter)
1153+
with pytest.raises(
1154+
TypeError, match=rf"X\.{setter}\(\) accepts at most one positional argument"
1155+
):
1156+
method(["M", "F"], ["#1FC3AA", "#8624F5"])
1157+
1158+
1159+
@pytest.mark.parametrize("setter", ["scale", "axis", "sort"])
1160+
def test_property_setter_rejects_positional_and_keyword_arguments(setter: str) -> None:
1161+
method = getattr(alt.X("field:Q"), setter)
1162+
with pytest.raises(
1163+
TypeError, match=rf"X\.{setter}\(\) cannot combine a positional argument"
1164+
):
1165+
method(["M", "F"], domain=["M", "F"])
1166+
1167+
11501168
def test_to_dict_datetime_typing() -> None:
11511169
"""
11521170
Enumerating various places that need updated annotations.

tests/vegalite/v6/schema/test_channels.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,14 @@ def test_channels_typing() -> None:
6464
):
6565
positional_as_keyword.to_dict()
6666

67-
keyword_as_positional = angle.sort("field:Q", "min", "descending") # type: ignore[call-overload]
68-
with pytest.raises(SchemaValidationError):
69-
keyword_as_positional.to_dict()
67+
with pytest.raises(
68+
TypeError, match=r"sort\(\) accepts at most one positional argument"
69+
):
70+
angle.sort("field:Q", "min", "descending") # type: ignore[call-overload]
7071
angle.sort(field="field:Q", op="min", order="descending")
7172

72-
# NOTE: Doesn't raise `SchemaValidationError`
73-
# - `"ascending"` is silently ignored when positional
74-
# - Caught as invalid statically, but not at runtime
75-
bad = angle.sort("x", "ascending").to_dict() # type: ignore[call-overload]
76-
good = angle.sort(encoding="x", order="ascending").to_dict()
77-
assert isinstance(bad, dict)
78-
assert isinstance(good, dict)
7973
with pytest.raises(
80-
AssertionError, match=r"'x' == {'encoding': 'x', 'order': 'ascending'}"
74+
TypeError, match=r"sort\(\) accepts at most one positional argument"
8175
):
82-
assert bad["sort"] == good["sort"]
76+
angle.sort("x", "ascending") # type: ignore[call-overload]
77+
assert angle.sort(encoding="x", order="ascending").to_dict()

tools/schemapi/schemapi.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,6 +1660,19 @@ def __get__(self, obj, cls):
16601660
return self
16611661

16621662
def __call__(self, *args: Any, **kwargs: Any):
1663+
name = f"{type(self.obj).__name__}.{self.prop}"
1664+
if len(args) > 1:
1665+
msg = (
1666+
f"{name}() accepts at most one positional argument, "
1667+
f"but {len(args)} were given"
1668+
)
1669+
raise TypeError(msg)
1670+
if args and kwargs:
1671+
msg = (
1672+
f"{name}() cannot combine a positional argument with keyword arguments"
1673+
)
1674+
raise TypeError(msg)
1675+
16631676
obj = self.obj.copy()
16641677
# TODO: use schema to validate
16651678
obj[self.prop] = args[0] if args else kwargs

0 commit comments

Comments
 (0)