From 613b6e48db7651dd8977bb8344fa1d029650234b Mon Sep 17 00:00:00 2001 From: Marco Edward Gorelli Date: Sun, 3 Nov 2024 16:05:06 +0000 Subject: [PATCH 1/2] chore: use more robust dtype comparisons, use Narwhals stable API in tests (#3670) --- altair/utils/core.py | 7 +++++-- tests/test_transformed_data.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/altair/utils/core.py b/altair/utils/core.py index acc7c2810..9143a1ec5 100644 --- a/altair/utils/core.py +++ b/altair/utils/core.py @@ -709,11 +709,14 @@ def infer_vegalite_type_for_narwhals( and not (categories := column.cat.get_categories()).is_empty() ): return "ordinal", categories.to_list() - if dtype in {nw.String, nw.Categorical, nw.Boolean}: + if dtype == nw.String or dtype == nw.Categorical or dtype == nw.Boolean: # noqa: PLR1714 return "nominal" elif dtype.is_numeric(): return "quantitative" - elif dtype in {nw.Datetime, nw.Date}: + elif dtype == nw.Datetime or dtype == nw.Date: # noqa: PLR1714 + # We use `== nw.Datetime` to check for any kind of Datetime, regardless of time + # unit and time zone. Prefer this over `dtype in {nw.Datetime, nw.Date}`, + # see https://narwhals-dev.github.io/narwhals/backcompat. return "temporal" else: msg = f"Unexpected DtypeKind: {dtype}" diff --git a/tests/test_transformed_data.py b/tests/test_transformed_data.py index ee689ff68..1196c7b07 100644 --- a/tests/test_transformed_data.py +++ b/tests/test_transformed_data.py @@ -7,7 +7,7 @@ import altair as alt from altair.utils.execeval import eval_block from tests import examples_methods_syntax, slow, ignore_DataFrameGroupBy -import narwhals as nw +import narwhals.stable.v1 as nw try: import vegafusion as vf From c28dbb9a7afeb7903d5c282cf7ac53b5524b2fd4 Mon Sep 17 00:00:00 2001 From: Dan Redding <125183946+dangotbanned@users.noreply.github.com> Date: Sun, 3 Nov 2024 16:20:52 +0000 Subject: [PATCH 2/2] fix: Resolve `alt.binding` signature/docstring issues (#3671) --- altair/vegalite/v5/api.py | 48 ++++++++++++++++++++++++++++++++--- tests/vegalite/v5/test_api.py | 37 +++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) 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]