diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 51d3f2cac..d4868cf1b 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -105,6 +105,7 @@ AnyMark, BindCheckbox, Binding, + BindInput, BindRadioSelect, BindRange, BinParams, @@ -1751,10 +1752,49 @@ def selection_single(**kwargs: Any) -> Parameter: return _selection(type="point", **kwargs) -@utils.use_signature(core.Binding) -def binding(input: Any, **kwargs: Any) -> Binding: - """A generic binding.""" - return core.Binding(input=input, **kwargs) +def binding( + input: str, + *, + autocomplete: Optional[str] = Undefined, + debounce: Optional[float] = Undefined, + element: Optional[str] = Undefined, + name: Optional[str] = Undefined, + placeholder: Optional[str] = Undefined, +) -> BindInput: + """ + A generic binding. + + Parameters + ---------- + input : str + The type of input element to use. The valid values are ``"checkbox"``, ``"radio"``, + ``"range"``, ``"select"``, and any other legal `HTML form input type + `__. + autocomplete : str + A hint for form autofill. See the `HTML autocomplete attribute + `__ for + additional information. + debounce : float + If defined, delays event handling until the specified milliseconds have elapsed + since the last event was fired. + element : str + An optional CSS selector string indicating the parent element to which the input + element should be added. By default, all input elements are added within the parent + container of the Vega view. + name : str + By default, the signal name is used to label input elements. This ``name`` property + can be used instead to specify a custom label for the bound signal. + placeholder : str + Text that appears in the form control when it has no value set. + """ + return core.BindInput( + autocomplete=autocomplete, + debounce=debounce, + element=element, + input=input, + name=name, + placeholder=placeholder, + ) @utils.use_signature(core.BindCheckbox) diff --git a/tests/vegalite/v5/test_api.py b/tests/vegalite/v5/test_api.py index ac184f86c..f6cd4ee6d 100644 --- a/tests/vegalite/v5/test_api.py +++ b/tests/vegalite/v5/test_api.py @@ -24,10 +24,13 @@ from packaging.version import Version import altair as alt +from altair.utils.core import use_signature from altair.utils.schemapi import Optional, SchemaValidationError, Undefined from tests import skip_requires_vl_convert, slow if TYPE_CHECKING: + from typing import Any + from altair.vegalite.v5.api import _Conditional, _Conditions from altair.vegalite.v5.schema._typing import Map @@ -1654,3 +1657,37 @@ def test_ibis_with_vegafusion(monkeypatch: pytest.MonkeyPatch): {"a": 2, "b": "2020-01-02T00:00:00.000"}, {"a": 3, "b": "2020-01-03T00:00:00.000"}, ] + + +def test_binding() -> None: + @use_signature(alt.Binding) + def old_binding(input: Any, **kwargs: Any) -> alt.Binding: + """A generic binding.""" + return alt.Binding(input=input, **kwargs) + + # NOTE: `mypy` doesn't complain, but `pyright` does + old = old_binding(input="search", placeholder="Country", name="Search") # pyright: ignore[reportCallIssue] + old_positional = old_binding("search", placeholder="Country", name="Search") + + new = alt.binding(input="search", placeholder="Country", name="Search") + new_positional = alt.binding("search", placeholder="Country", name="Search") + + assert ( + old.to_dict() + == old_positional.to_dict() + == new.to_dict() + == new_positional.to_dict() + ) + assert all( + isinstance(x, alt.Binding) for x in (old, old_positional, new, new_positional) + ) + + MISSING_INPUT = r"missing 1 required positional argument: 'input" + + # NOTE: `mypy` doesn't complain, but `pyright` does (Again) + with pytest.raises(TypeError, match=MISSING_INPUT): + old_binding(placeholder="Country", name="Search") # pyright: ignore[reportCallIssue] + + # NOTE: Both type checkers can detect the issue on the new signature + with pytest.raises(TypeError, match=MISSING_INPUT): + alt.binding(placeholder="Country", name="Search") # type: ignore[call-arg]