From fde0696995091605b94e55ed77a612932388bab0 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sun, 28 Jul 2024 15:37:52 +0100 Subject: [PATCH 01/19] ci(ruff): Add `ANN` rules for `api.py` only To highlight all the missing annotations to fix and autofix `None` return --- pyproject.toml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fb29c32e8..6a0b61456 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -229,7 +229,9 @@ extend-safe-fixes=[ # escape-sequence-in-docstring "D301", # ends-in-period - "D400" + "D400", + # missing-return-type-special-method + "ANN204" ] # https://docs.astral.sh/ruff/preview/#using-rules-that-are-in-preview @@ -307,6 +309,7 @@ select = [ "D213", # numpy-specific-rules "NPY", + "ANN" ] ignore = [ # Whitespace before ':' @@ -351,6 +354,9 @@ ignore = [ "D413", # doc-line-too-long "W505", + "ANN002", # args + "ANN003", # kwargs + "ANN401" # Any ] # https://docs.astral.sh/ruff/settings/#lintpydocstyle pydocstyle={ convention="numpy" } @@ -361,6 +367,9 @@ mccabe={ max-complexity=18 } # https://docs.astral.sh/ruff/settings/#lint_flake8-tidy-imports_banned-api "typing.Optional".msg = "Use `Union[T, None]` instead.\n`typing.Optional` is likely to be confused with `altair.Optional`, which have a similar but different semantic meaning.\nSee https://github.com/vega/altair/pull/3449" +[tool.ruff.lint.per-file-ignores] +"!altair/vegalite/v5/api.py" = ["ANN"] + [tool.ruff.format] quote-style = "double" From cb1707e0246ca741b848cf273691631553bf066c Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:08:36 +0100 Subject: [PATCH 02/19] feat(typing): Complete annotations for most `api` functions Excluding `*args` on `ChartType` wrappers. They need to be defined in alignment in multiple places, which is more complex --- altair/vegalite/v5/api.py | 51 +++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index b0d3a6ffb..e132a6171 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -103,6 +103,11 @@ TopLevelSelectionParameter, SelectionParameter, InlineDataset, + NamedData, + InlineData, + BindCheckbox, + BindRadioSelect, + BindRange, ) from altair.expr.core import ( BinaryExpression, @@ -1087,7 +1092,7 @@ def when( # Top-Level Functions -def value(value, **kwargs) -> _Value: +def value(value: Any, **kwargs: Any) -> _Value: """Specify a value for use in an encoding.""" return _Value(value=value, **kwargs) # type: ignore[typeddict-item] @@ -1098,7 +1103,7 @@ def param( bind: Optional[Binding] = Undefined, empty: Optional[bool] = Undefined, expr: Optional[str | Expr | Expression] = Undefined, - **kwds, + **kwds: Any, ) -> Parameter: """ Create a named parameter, see https://altair-viz.github.io/user_guide/interactions.html for examples. @@ -1179,7 +1184,7 @@ def param( return parameter -def _selection(type: Optional[SelectionType_T] = Undefined, **kwds) -> Parameter: +def _selection(type: Optional[SelectionType_T] = Undefined, **kwds: Any) -> Parameter: # We separate out the parameter keywords from the selection keywords select_kwds = {"name", "bind", "value", "empty", "init", "views"} @@ -1209,7 +1214,7 @@ def _selection(type: Optional[SelectionType_T] = Undefined, **kwds) -> Parameter alternative="'selection_point()' or 'selection_interval()'", message="These functions also include more helpful docstrings.", ) -def selection(type: Optional[SelectionType_T] = Undefined, **kwds) -> Parameter: +def selection(type: Optional[SelectionType_T] = Undefined, **kwds: Any) -> Parameter: """ Users are recommended to use either 'selection_point' or 'selection_interval' instead, depending on the type of parameter they want to create. @@ -1244,7 +1249,7 @@ def selection_interval( mark: Optional[Mark] = Undefined, translate: Optional[str | bool] = Undefined, zoom: Optional[str | bool] = Undefined, - **kwds, + **kwds: Any, ) -> Parameter: """ Create an interval selection parameter. Selection parameters define data queries that are driven by direct manipulation from user input (e.g., mouse clicks or drags). Interval selection parameters are used to select a continuous range of data values on drag, whereas point selection parameters (`selection_point`) are used to select multiple discrete data values.). @@ -1357,7 +1362,7 @@ def selection_point( resolve: Optional[SelectionResolution_T] = Undefined, toggle: Optional[str | bool] = Undefined, nearest: Optional[bool] = Undefined, - **kwds, + **kwds: Any, ) -> Parameter: """ Create a point selection parameter. Selection parameters define data queries that are driven by direct manipulation from user input (e.g., mouse clicks or drags). Point selection parameters are used to select multiple discrete data values; the first value is selected on click and additional values toggled on shift-click. To select a continuous range of data values on drag interval selection parameters (`selection_interval`) can be used instead. @@ -1462,43 +1467,43 @@ def selection_point( @utils.deprecated(version="5.0.0", alternative="selection_point") -def selection_multi(**kwargs): +def selection_multi(**kwargs: Any) -> Parameter: """'selection_multi' is deprecated. Use 'selection_point'.""" return _selection(type="point", **kwargs) @utils.deprecated(version="5.0.0", alternative="selection_point") -def selection_single(**kwargs): +def selection_single(**kwargs: Any) -> Parameter: """'selection_single' is deprecated. Use 'selection_point'.""" return _selection(type="point", **kwargs) @utils.use_signature(core.Binding) -def binding(input, **kwargs): +def binding(input: Any, **kwargs: Any) -> Binding: """A generic binding.""" return core.Binding(input=input, **kwargs) @utils.use_signature(core.BindCheckbox) -def binding_checkbox(**kwargs): +def binding_checkbox(**kwargs: Any) -> BindCheckbox: """A checkbox binding.""" return core.BindCheckbox(input="checkbox", **kwargs) @utils.use_signature(core.BindRadioSelect) -def binding_radio(**kwargs): +def binding_radio(**kwargs: Any) -> BindRadioSelect: """A radio button binding.""" return core.BindRadioSelect(input="radio", **kwargs) @utils.use_signature(core.BindRadioSelect) -def binding_select(**kwargs): +def binding_select(**kwargs: Any) -> BindRadioSelect: """A select binding.""" return core.BindRadioSelect(input="select", **kwargs) @utils.use_signature(core.BindRange) -def binding_range(**kwargs): +def binding_range(**kwargs: Any) -> BindRange: """A range binding.""" return core.BindRange(input="range", **kwargs) @@ -1510,7 +1515,7 @@ def condition( if_false: _TSchemaBase, *, empty: Optional[bool] = ..., - **kwargs, + **kwargs: Any, ) -> _TSchemaBase: ... @overload def condition( @@ -1519,7 +1524,7 @@ def condition( if_false: Map | str, *, empty: Optional[bool] = ..., - **kwargs, + **kwargs: Any, ) -> dict[str, _ConditionType | Any]: ... @overload def condition( @@ -1528,11 +1533,11 @@ def condition( if_false: Map, *, empty: Optional[bool] = ..., - **kwargs, + **kwargs: Any, ) -> dict[str, _ConditionType | Any]: ... @overload def condition( - predicate: _PredicateType, if_true: str, if_false: str, **kwargs + predicate: _PredicateType, if_true: str, if_false: str, **kwargs: Any ) -> Never: ... # TODO: update the docstring def condition( @@ -1541,7 +1546,7 @@ def condition( if_false: _StatementType, *, empty: Optional[bool] = Undefined, - **kwargs, + **kwargs: Any, ) -> SchemaBase | dict[str, _ConditionType | Any]: """ A conditional attribute or encoding. @@ -4460,7 +4465,7 @@ def add_selection(self, *selections) -> Self: return self.add_params(*selections) -def topo_feature(url: str, feature: str, **kwargs) -> UrlData: +def topo_feature(url: str, feature: str, **kwargs: Any) -> UrlData: """ A convenience function for extracting features from a topojson url. @@ -4750,7 +4755,11 @@ def remove_prop(subchart, prop): @utils.use_signature(core.SequenceParams) def sequence( - start, stop=None, step=Undefined, as_=Undefined, **kwds + start: Optional[float], + stop: Optional[float | None] = None, + step: Optional[float] = Undefined, + as_: Optional[str] = Undefined, + **kwds: Any, ) -> SequenceGenerator: """Sequence generator.""" if stop is None: @@ -4760,7 +4769,7 @@ def sequence( @utils.use_signature(core.GraticuleParams) -def graticule(**kwds) -> GraticuleGenerator: +def graticule(**kwds: Any) -> GraticuleGenerator: """Graticule generator.""" # graticule: True indicates default parameters graticule: Any = core.GraticuleParams(**kwds) if kwds else True From a32c937a8c7a4830cf9c20f35d37aedefd8c52e9 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:23:19 +0100 Subject: [PATCH 03/19] feat(typing): Annotate more expr/params in `api` --- altair/expr/core.py | 9 ++++++++- altair/vegalite/v5/api.py | 23 ++++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/altair/expr/core.py b/altair/expr/core.py index cfaa9984c..52fabc1fe 100644 --- a/altair/expr/core.py +++ b/altair/expr/core.py @@ -1,5 +1,9 @@ from __future__ import annotations -from typing import Any + +from typing import Any, Union, Dict + +from typing_extensions import TypeAlias + from ..utils import SchemaBase @@ -232,3 +236,6 @@ def __init__(self, group, name) -> None: def __repr__(self) -> str: return f"{self.group}[{self.name!r}]" + + +IntoExpression: TypeAlias = Union[bool, None, str, OperatorMixin, Dict[str, Any]] diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index e132a6171..0c6ad9454 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -114,6 +114,7 @@ Expression, GetAttrExpression, GetItemExpression, + IntoExpression, ) from .schema._typing import ( ImputeMethod_T, @@ -368,13 +369,13 @@ def to_dict(self) -> dict[str, str | dict]: msg = f"Unrecognized parameter type: {self.param_type}" raise ValueError(msg) - def __invert__(self): + def __invert__(self) -> SelectionPredicateComposition | Any: if self.param_type == "selection": return SelectionPredicateComposition({"not": {"param": self.name}}) else: return _expr_core.OperatorMixin.__invert__(self) - def __and__(self, other): + def __and__(self, other: Any) -> SelectionPredicateComposition | Any: if self.param_type == "selection": if isinstance(other, Parameter): other = {"param": other.name} @@ -382,7 +383,7 @@ def __and__(self, other): else: return _expr_core.OperatorMixin.__and__(self, other) - def __or__(self, other): + def __or__(self, other: Any) -> SelectionPredicateComposition | Any: if self.param_type == "selection": if isinstance(other, Parameter): other = {"param": other.name} @@ -396,7 +397,7 @@ def __repr__(self) -> str: def _to_expr(self) -> str: return self.name - def _from_expr(self, expr) -> ParameterExpression: + def _from_expr(self, expr: IntoExpression) -> ParameterExpression: return ParameterExpression(expr=expr) def __getattr__(self, field_name: str) -> GetAttrExpression | SelectionExpression: @@ -417,18 +418,18 @@ def __getitem__(self, field_name: str) -> GetItemExpression: # Enables use of ~, &, | with compositions of selection objects. class SelectionPredicateComposition(core.PredicateComposition): - def __invert__(self): + def __invert__(self) -> SelectionPredicateComposition: return SelectionPredicateComposition({"not": self.to_dict()}) - def __and__(self, other): + def __and__(self, other: SchemaBase) -> SelectionPredicateComposition: return SelectionPredicateComposition({"and": [self.to_dict(), other.to_dict()]}) - def __or__(self, other): + def __or__(self, other: SchemaBase) -> SelectionPredicateComposition: return SelectionPredicateComposition({"or": [self.to_dict(), other.to_dict()]}) class ParameterExpression(_expr_core.OperatorMixin): - def __init__(self, expr) -> None: + def __init__(self, expr: IntoExpression) -> None: self.expr = expr def to_dict(self) -> dict[str, str]: @@ -437,12 +438,12 @@ def to_dict(self) -> dict[str, str]: def _to_expr(self) -> str: return repr(self.expr) - def _from_expr(self, expr) -> ParameterExpression: + def _from_expr(self, expr: IntoExpression) -> ParameterExpression: return ParameterExpression(expr=expr) class SelectionExpression(_expr_core.OperatorMixin): - def __init__(self, expr) -> None: + def __init__(self, expr: IntoExpression) -> None: self.expr = expr def to_dict(self) -> dict[str, str]: @@ -451,7 +452,7 @@ def to_dict(self) -> dict[str, str]: def _to_expr(self) -> str: return repr(self.expr) - def _from_expr(self, expr) -> SelectionExpression: + def _from_expr(self, expr: IntoExpression) -> SelectionExpression: return SelectionExpression(expr=expr) From de2fe9f84040c177cbc6d64f37b314851cf59e29 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:17:09 +0100 Subject: [PATCH 04/19] feat(typing): Various changes to enforce `dict[str, Any]` instead of `dict` Among these, many locations already assume `str` keys in the implementation - without checking --- altair/utils/_vegafusion_data.py | 2 +- altair/utils/compiler.py | 6 +++--- altair/vegalite/v5/api.py | 15 ++++++++++----- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/altair/utils/_vegafusion_data.py b/altair/utils/_vegafusion_data.py index 26f79d22e..1642760cb 100644 --- a/altair/utils/_vegafusion_data.py +++ b/altair/utils/_vegafusion_data.py @@ -213,7 +213,7 @@ def compile_to_vegafusion_chart_state( return chart_state -def compile_with_vegafusion(vegalite_spec: dict[str, Any]) -> dict: +def compile_with_vegafusion(vegalite_spec: dict[str, Any]) -> dict[str, Any]: """ Compile a Vega-Lite spec to Vega and pre-transform with VegaFusion. diff --git a/altair/utils/compiler.py b/altair/utils/compiler.py index cbe0dd62d..634260715 100644 --- a/altair/utils/compiler.py +++ b/altair/utils/compiler.py @@ -1,11 +1,11 @@ -from typing import Callable +from typing import Callable, Dict, Any from altair.utils import PluginRegistry # ============================================================================== # Vega-Lite to Vega compiler registry # ============================================================================== -VegaLiteCompilerType = Callable[[dict], dict] +VegaLiteCompilerType = Callable[[Dict[str, Any]], Dict[str, Any]] -class VegaLiteCompilerRegistry(PluginRegistry[VegaLiteCompilerType, dict]): +class VegaLiteCompilerRegistry(PluginRegistry[VegaLiteCompilerType, Dict[str, Any]]): pass diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 0c6ad9454..2608202bf 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -109,6 +109,7 @@ BindRadioSelect, BindRange, ) + from altair.utils.display import MimeBundleType from altair.expr.core import ( BinaryExpression, Expression, @@ -189,7 +190,7 @@ # ------------------------------------------------------------------------ # Data Utilities -def _dataset_name(values: dict | list | InlineDataset) -> str: +def _dataset_name(values: dict[str, Any] | list | InlineDataset) -> str: """ Generate a unique hash of the data. @@ -212,7 +213,9 @@ def _dataset_name(values: dict | list | InlineDataset) -> str: return "data-" + hsh -def _consolidate_data(data: Any, context: Any) -> Any: +def _consolidate_data( + data: ChartDataType | UrlData, context: dict[str, Any] +) -> ChartDataType | NamedData | InlineData | UrlData: """ If data is specified inline, then move it to context['datasets']. @@ -241,7 +244,9 @@ def _consolidate_data(data: Any, context: Any) -> Any: return data -def _prepare_data(data, context=None): +def _prepare_data( + data: ChartDataType, context: dict[str, Any] | None = None +) -> ChartDataType | NamedData | InlineData | UrlData | Any: """ Convert input data to data for use within schema. @@ -1610,7 +1615,7 @@ def to_dict( format: str = "vega-lite", ignore: list[str] | None = None, context: dict[str, Any] | None = None, - ) -> dict: + ) -> dict[str, Any]: """ Convert the chart to a dictionary suitable for JSON export. @@ -3276,7 +3281,7 @@ def transform_window( # Display-related methods - def _repr_mimebundle_(self, include=None, exclude=None): + def _repr_mimebundle_(self, *args, **kwds) -> MimeBundleType | None: # type:ignore[return] """Return a MIME bundle for display in Jupyter frontends.""" # Catch errors explicitly to get around issues in Jupyter frontend # see https://github.com/ipython/ipython/issues/11038 From 20f9d853f266153592423fe1691d8b76944b85dd Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:21:42 +0100 Subject: [PATCH 05/19] feat(typing): Misc minor method annotations --- altair/vegalite/v5/api.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 2608202bf..14d3f1689 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -292,7 +292,7 @@ def _prepare_data( class LookupData(core.LookupData): @utils.use_signature(core.LookupData) - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) def to_dict(self, *args, **kwargs) -> dict: @@ -306,7 +306,7 @@ class FacetMapping(core.FacetMapping): _class_is_valid_at_instantiation = False @utils.use_signature(core.FacetMapping) - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) def to_dict(self, *args, **kwargs) -> dict: @@ -1888,7 +1888,7 @@ def save( json_kwds: dict | None = None, webdriver: str | None = None, engine: str | None = None, - inline=False, + inline: bool = False, **kwargs, ) -> None: """ @@ -2534,7 +2534,7 @@ def transform_impute( groupby: Optional[list[str | FieldName]] = Undefined, keyvals: Optional[list[Any] | ImputeSequence] = Undefined, method: Optional[ImputeMethod_T | ImputeMethod] = Undefined, - value=Undefined, + value: Optional[Any] = Undefined, ) -> Self: """ Add an :class:`ImputeTransform` to the schema. @@ -3349,7 +3349,7 @@ def serve( open_browser=True, http_server=None, **kwargs, - ): + ) -> None: """ 'serve' is deprecated. Use 'show' instead. @@ -3410,7 +3410,7 @@ def show(self) -> None: display(self) @utils.use_signature(core.Resolve) - def _set_resolve(self, **kwargs): + def _set_resolve(self, **kwargs): # noqa: ANN202 """Copy the chart and update the resolve property with kwargs.""" if not hasattr(self, "resolve"): msg = f"{self.__class__} object has no attribute " "'resolve'" From 8cfcd5c5e3bb7cbdaf6873b9cebc03cc0582c8ee Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:28:00 +0100 Subject: [PATCH 06/19] feat(typing): Use `ChartType` in all `ChartType` dunder methods --- altair/vegalite/v5/api.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 14d3f1689..3f3e420f0 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -1979,27 +1979,27 @@ def __repr__(self) -> str: return f"alt.{self.__class__.__name__}(...)" # Layering and stacking - def __add__(self, other) -> LayerChart: - if not isinstance(other, TopLevelMixin): + def __add__(self, other: ChartType) -> LayerChart: + if not is_chart_type(other): msg = "Only Chart objects can be layered." raise ValueError(msg) - return layer(self, other) + return layer(t.cast("ChartType", self), other) - def __and__(self, other) -> VConcatChart: - if not isinstance(other, TopLevelMixin): + def __and__(self, other: ChartType) -> VConcatChart: + if not is_chart_type(other): msg = "Only Chart objects can be concatenated." raise ValueError(msg) # Too difficult to type check this - return vconcat(self, other) + return vconcat(t.cast("ChartType", self), other) - def __or__(self, other) -> HConcatChart | ConcatChart: - if not isinstance(other, TopLevelMixin): + def __or__(self, other: ChartType) -> HConcatChart | ConcatChart: + if not is_chart_type(other): msg = "Only Chart objects can be concatenated." raise ValueError(msg) elif isinstance(self, ConcatChart): return concat(self, other) else: - return hconcat(self, other) + return hconcat(t.cast("ChartType", self), other) def repeat( self, @@ -3988,14 +3988,14 @@ def __init__(self, data=Undefined, concat=(), columns=Undefined, **kwargs): self.data, self.concat = _combine_subchart_data(self.data, self.concat) self.params, self.concat = _combine_subchart_params(self.params, self.concat) - def __ior__(self, other) -> Self: + def __ior__(self, other: ChartType) -> Self: _check_if_valid_subspec(other, "ConcatChart") self.concat.append(other) self.data, self.concat = _combine_subchart_data(self.data, self.concat) self.params, self.concat = _combine_subchart_params(self.params, self.concat) return self - def __or__(self, other) -> Self: + def __or__(self, other: ChartType) -> Self: copy = self.copy(deep=["concat"]) copy |= other return copy @@ -4085,14 +4085,14 @@ def __init__(self, data=Undefined, hconcat=(), **kwargs): self.data, self.hconcat = _combine_subchart_data(self.data, self.hconcat) self.params, self.hconcat = _combine_subchart_params(self.params, self.hconcat) - def __ior__(self, other) -> Self: + def __ior__(self, other: ChartType) -> Self: _check_if_valid_subspec(other, "HConcatChart") self.hconcat.append(other) self.data, self.hconcat = _combine_subchart_data(self.data, self.hconcat) self.params, self.hconcat = _combine_subchart_params(self.params, self.hconcat) return self - def __or__(self, other) -> Self: + def __or__(self, other: ChartType) -> Self: copy = self.copy(deep=["hconcat"]) copy |= other return copy @@ -4182,14 +4182,14 @@ def __init__(self, data=Undefined, vconcat=(), **kwargs): self.data, self.vconcat = _combine_subchart_data(self.data, self.vconcat) self.params, self.vconcat = _combine_subchart_params(self.params, self.vconcat) - def __iand__(self, other) -> Self: + def __iand__(self, other: ChartType) -> Self: _check_if_valid_subspec(other, "VConcatChart") self.vconcat.append(other) self.data, self.vconcat = _combine_subchart_data(self.data, self.vconcat) self.params, self.vconcat = _combine_subchart_params(self.params, self.vconcat) return self - def __and__(self, other) -> Self: + def __and__(self, other: ChartType) -> Self: copy = self.copy(deep=["vconcat"]) copy &= other return copy @@ -4787,7 +4787,7 @@ def sphere() -> SphereGenerator: return core.SphereGenerator(sphere=True) -ChartType = Union[ +ChartType: TypeAlias = Union[ Chart, RepeatChart, ConcatChart, HConcatChart, VConcatChart, FacetChart, LayerChart ] From e20e426c1cf4ef16b56fd0e8894eaa66b942dd29 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:31:05 +0100 Subject: [PATCH 07/19] fix(ruff): Ignore some `ANN` rules that won't be fixed --- altair/vegalite/v5/api.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 3f3e420f0..3318425df 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -1589,7 +1589,9 @@ def condition( # Top-level objects -def _top_schema_base(obj: Any, /): # -> +def _top_schema_base( # noqa: ANN202 + obj: Any, / +): # -> """ Enforces an intersection type w/ `SchemaBase` & `TopLevelMixin` objects. @@ -3341,13 +3343,13 @@ def display( @utils.deprecated(version="4.1.0", alternative="show") def serve( self, - ip="127.0.0.1", - port=8888, - n_retries=50, - files=None, - jupyter_warning=True, - open_browser=True, - http_server=None, + ip="127.0.0.1", # noqa: ANN001 + port=8888, # noqa: ANN001 + n_retries=50, # noqa: ANN001 + files=None, # noqa: ANN001 + jupyter_warning=True, # noqa: ANN001 + open_browser=True, # noqa: ANN001 + http_server=None, # noqa: ANN001 **kwargs, ) -> None: """ From 0184c7f733fbc7dc7a6889806f091faa67799b6e Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:38:33 +0100 Subject: [PATCH 08/19] chore: add pyright ignore from #3492 --- altair/vegalite/v5/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 3318425df..73782f490 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -15,6 +15,7 @@ TYPE_CHECKING, TypeVar, Protocol, + Sequence, ) from typing_extensions import TypeAlias import typing as t @@ -907,7 +908,7 @@ def otherwise( if isinstance(current, list) and len(current) == 1: # This case is guaranteed to have come from `When` and not `ChainedWhen` # The `list` isn't needed if we complete the condition here - conditions = _Conditional(condition=current[0]) + conditions = _Conditional(condition=current[0]) # pyright: ignore[reportArgumentType] elif isinstance(current, dict): if not is_extra(statement): conditions = self.to_dict() From 3ef1ed619abbdf87aafe0280b2da4494fe624ea7 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:54:38 +0100 Subject: [PATCH 09/19] feat(typing): Improve `ChartType` constructor/factory annotations --- altair/utils/_transformed_data.py | 6 +- altair/vegalite/v5/api.py | 120 ++++++++++++++++++++++-------- 2 files changed, 93 insertions(+), 33 deletions(-) diff --git a/altair/utils/_transformed_data.py b/altair/utils/_transformed_data.py index 5847ad94f..ceae9fa34 100644 --- a/altair/utils/_transformed_data.py +++ b/altair/utils/_transformed_data.py @@ -30,6 +30,7 @@ if TYPE_CHECKING: from altair.utils.core import DataFrameLike + from altair.vegalite.v5.api import ChartType Scope: TypeAlias = Tuple[int, ...] FacetMapping: TypeAlias = Dict[Tuple[str, Scope], Tuple[str, Scope]] @@ -154,9 +155,7 @@ def transformed_data(chart, row_limit=None, exclude=None): # The same error appeared when trying it with Protocols for the concat and layer charts. # This function is only used internally and so we accept this inconsistency for now. def name_views( - chart: Chart | FacetChart | LayerChart | HConcatChart | VConcatChart | ConcatChart, - i: int = 0, - exclude: Iterable[str] | None = None, + chart: ChartType, i: int = 0, exclude: Iterable[str] | None = None ) -> list[str]: """ Name unnamed chart views. @@ -193,6 +192,7 @@ def name_views( else: return [] else: + subcharts: list[Any] if isinstance(chart, _chart_class_mapping[LayerChart]): subcharts = chart.layer elif isinstance(chart, _chart_class_mapping[HConcatChart]): diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 73782f490..22450d9c2 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -2062,7 +2062,9 @@ def repeat( else: repeat_arg = core.RepeatMapping(row=row, column=column) - return RepeatChart(spec=self, repeat=repeat_arg, columns=columns, **kwargs) + return RepeatChart( + spec=t.cast("ChartType", self), repeat=repeat_arg, columns=columns, **kwargs + ) def properties(self, **kwargs) -> Self: """ @@ -3583,10 +3585,10 @@ def __init__( height: Optional[int | str | dict | Step] = Undefined, **kwargs, ) -> None: - super().__init__( # Data type hints won't match with what TopLevelUnitSpec expects # as there is some data processing happening when converting to # a VL spec + super().__init__( data=data, # type: ignore[arg-type] encoding=encoding, mark=mark, @@ -3833,22 +3835,18 @@ def _get_any(spec: dict | SchemaBase, *attrs: str) -> bool: class RepeatChart(TopLevelMixin, core.TopLevelRepeatSpec): """A chart repeated across rows and columns with small changes.""" - # Because TopLevelRepeatSpec is defined as a union as of Vega-Lite schema 4.9, - # we set the arguments explicitly here. - # TODO: Should we instead use tools/schemapi/codegen.get_args? - @utils.use_signature(core.TopLevelRepeatSpec) def __init__( self, - repeat=Undefined, - spec=Undefined, + repeat: Optional[list[str] | LayerRepeatMapping | RepeatMapping] = Undefined, + spec: Optional[ChartType] = Undefined, align=Undefined, autosize=Undefined, background=Undefined, bounds=Undefined, center=Undefined, - columns=Undefined, + columns: Optional[int] = Undefined, config=Undefined, - data=Undefined, + data: Optional[ChartDataType] = Undefined, datasets=Undefined, description=Undefined, name=Undefined, @@ -3860,12 +3858,19 @@ def __init__( transform=Undefined, usermeta=Undefined, **kwds, - ): + ) -> None: + tp_name = type(self).__name__ + if utils.is_undefined(spec): + msg = f"{tp_name!r} requires a `spec`, but got: {spec!r}" + raise TypeError(msg) _check_if_valid_subspec(spec, "RepeatChart") _spec_as_list = [spec] params, _spec_as_list = _combine_subchart_params(params, _spec_as_list) spec = _spec_as_list[0] if isinstance(spec, (Chart, LayerChart)): + if utils.is_undefined(repeat): + msg = f"{tp_name!r} requires a `repeat`, but got: {repeat!r}" + raise TypeError(msg) params = _repeat_names(params, repeat, spec) super().__init__( repeat=repeat, @@ -3983,11 +3988,20 @@ class ConcatChart(TopLevelMixin, core.TopLevelConcatSpec): """A chart with horizontally-concatenated facets.""" @utils.use_signature(core.TopLevelConcatSpec) - def __init__(self, data=Undefined, concat=(), columns=Undefined, **kwargs): + def __init__( + self, + data: Optional[ChartDataType] = Undefined, + concat: Sequence[ConcatType] = (), + columns: Optional[float] = Undefined, + **kwargs, + ) -> None: # TODO: move common data to top level? for spec in concat: _check_if_valid_subspec(spec, "ConcatChart") - super().__init__(data=data, concat=list(concat), columns=columns, **kwargs) + super().__init__(data=data, concat=list(concat), columns=columns, **kwargs) # type: ignore[arg-type] + self.concat: list[ChartType] + self.params: Optional[Sequence[_Parameter]] + self.data: Optional[ChartDataType] self.data, self.concat = _combine_subchart_data(self.data, self.concat) self.params, self.concat = _combine_subchart_params(self.params, self.concat) @@ -4071,7 +4085,7 @@ def add_selection(self, *selections) -> Self: return self.add_params(*selections) -def concat(*charts, **kwargs) -> ConcatChart: +def concat(*charts: ConcatType, **kwargs: Any) -> ConcatChart: """Concatenate charts horizontally.""" return ConcatChart(concat=charts, **kwargs) # pyright: ignore @@ -4080,11 +4094,19 @@ class HConcatChart(TopLevelMixin, core.TopLevelHConcatSpec): """A chart with horizontally-concatenated facets.""" @utils.use_signature(core.TopLevelHConcatSpec) - def __init__(self, data=Undefined, hconcat=(), **kwargs): + def __init__( + self, + data: Optional[ChartDataType] = Undefined, + hconcat: Sequence[ConcatType] = (), + **kwargs, + ) -> None: # TODO: move common data to top level? for spec in hconcat: _check_if_valid_subspec(spec, "HConcatChart") - super().__init__(data=data, hconcat=list(hconcat), **kwargs) + super().__init__(data=data, hconcat=list(hconcat), **kwargs) # type: ignore[arg-type] + self.hconcat: list[ChartType] + self.params: Optional[Sequence[_Parameter]] + self.data: Optional[ChartDataType] self.data, self.hconcat = _combine_subchart_data(self.data, self.hconcat) self.params, self.hconcat = _combine_subchart_params(self.params, self.hconcat) @@ -4168,7 +4190,7 @@ def add_selection(self, *selections) -> Self: return self.add_params(*selections) -def hconcat(*charts, **kwargs) -> HConcatChart: +def hconcat(*charts: ConcatType, **kwargs: Any) -> HConcatChart: """Concatenate charts horizontally.""" return HConcatChart(hconcat=charts, **kwargs) # pyright: ignore @@ -4177,11 +4199,19 @@ class VConcatChart(TopLevelMixin, core.TopLevelVConcatSpec): """A chart with vertically-concatenated facets.""" @utils.use_signature(core.TopLevelVConcatSpec) - def __init__(self, data=Undefined, vconcat=(), **kwargs): + def __init__( + self, + data: Optional[ChartDataType] = Undefined, + vconcat: Sequence[ConcatType] = (), + **kwargs, + ) -> None: # TODO: move common data to top level? for spec in vconcat: _check_if_valid_subspec(spec, "VConcatChart") - super().__init__(data=data, vconcat=list(vconcat), **kwargs) + super().__init__(data=data, vconcat=list(vconcat), **kwargs) # type: ignore[arg-type] + self.vconcat: list[ChartType] + self.params: Optional[Sequence[_Parameter]] + self.data: Optional[ChartDataType] self.data, self.vconcat = _combine_subchart_data(self.data, self.vconcat) self.params, self.vconcat = _combine_subchart_params(self.params, self.vconcat) @@ -4267,7 +4297,7 @@ def add_selection(self, *selections) -> Self: return self.add_params(*selections) -def vconcat(*charts, **kwargs) -> VConcatChart: +def vconcat(*charts: ConcatType, **kwargs: Any) -> VConcatChart: """Concatenate charts vertically.""" return VConcatChart(vconcat=charts, **kwargs) # pyright: ignore @@ -4276,13 +4306,21 @@ class LayerChart(TopLevelMixin, _EncodingMixin, core.TopLevelLayerSpec): """A Chart with layers within a single panel.""" @utils.use_signature(core.TopLevelLayerSpec) - def __init__(self, data=Undefined, layer=(), **kwargs): + def __init__( + self, + data: Optional[ChartDataType] = Undefined, + layer: Sequence[LayerType] = (), + **kwargs, + ) -> None: # TODO: move common data to top level? # TODO: check for conflicting interaction for spec in layer: _check_if_valid_subspec(spec, "LayerChart") _check_if_can_be_layered(spec) - super().__init__(data=data, layer=list(layer), **kwargs) + super().__init__(data=data, layer=list(layer), **kwargs) # type: ignore[arg-type] + self.layer: list[ChartType] + self.params: Optional[Sequence[_Parameter]] + self.data: Optional[ChartDataType] self.data, self.layer = _combine_subchart_data(self.data, self.layer) # Currently (Vega-Lite 5.5) the same param can't occur on two layers self.layer = _remove_duplicate_params(self.layer) @@ -4386,7 +4424,7 @@ def add_selection(self, *selections) -> Self: return self.add_params(*selections) -def layer(*charts, **kwargs) -> LayerChart: +def layer(*charts: LayerType, **kwargs: Any) -> LayerChart: """Layer multiple charts.""" return LayerChart(layer=charts, **kwargs) # pyright: ignore @@ -4397,17 +4435,21 @@ class FacetChart(TopLevelMixin, core.TopLevelFacetSpec): @utils.use_signature(core.TopLevelFacetSpec) def __init__( self, - data=Undefined, - spec=Undefined, - facet=Undefined, - params=Undefined, + data: Optional[ChartDataType] = Undefined, + spec: Optional[ChartType] = Undefined, + facet: Optional[dict | SchemaBase] = Undefined, + params: Optional[Sequence[_Parameter]] = Undefined, **kwargs, - ): + ) -> None: + if utils.is_undefined(spec): + msg = f"{type(self).__name__!r} requires a `spec`, but got: {spec!r}" + raise TypeError(msg) _check_if_valid_subspec(spec, "FacetChart") _spec_as_list = [spec] params, _spec_as_list = _combine_subchart_params(params, _spec_as_list) spec = _spec_as_list[0] - super().__init__(data=data, spec=spec, facet=facet, params=params, **kwargs) + super().__init__(data=data, spec=spec, facet=facet, params=params, **kwargs) # type: ignore[arg-type] + self.data: Optional[ChartDataType] def transformed_data( self, row_limit: int | None = None, exclude: Iterable[str] | None = None @@ -4522,7 +4564,10 @@ def remove_data(subchart): return data, subcharts -def _viewless_dict(param: Parameter) -> dict: +_Parameter: TypeAlias = Union[ + core.VariableParameter, core.TopLevelSelectionParameter, core.SelectionParameter +] + d = param.to_dict() d.pop("views", None) return d @@ -4793,6 +4838,21 @@ def sphere() -> SphereGenerator: ChartType: TypeAlias = Union[ Chart, RepeatChart, ConcatChart, HConcatChart, VConcatChart, FacetChart, LayerChart ] +ConcatType: TypeAlias = Union[ + ChartType, + core.FacetSpec, + core.LayerSpec, + core.RepeatSpec, + core.FacetedUnitSpec, + core.LayerRepeatSpec, + core.NonNormalizedSpec, + core.NonLayerRepeatSpec, + core.ConcatSpecGenericSpec, + core.ConcatSpecGenericSpec, + core.HConcatSpecGenericSpec, + core.VConcatSpecGenericSpec, +] +LayerType: TypeAlias = Union[ChartType, core.UnitSpec, core.LayerSpec] def is_chart_type(obj: Any) -> TypeIs[ChartType]: From 591d322ff7a3be162d71feecbe5dc4df67a4ae28 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:05:27 +0100 Subject: [PATCH 10/19] feat(typing): Annotate remaining functions in `api` --- altair/vegalite/v5/api.py | 65 ++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 22450d9c2..2a26d9b06 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -3762,7 +3762,7 @@ def interactive( def _check_if_valid_subspec( - spec: Optional[SchemaBase | dict], + spec: ConcatType | LayerType | dict[str, Any], classname: Literal[ "ConcatChart", "FacetChart", @@ -3794,16 +3794,16 @@ def _check_if_valid_subspec( raise ValueError(err.format(attr, classname)) -def _check_if_can_be_layered(spec: dict | SchemaBase) -> None: +def _check_if_can_be_layered(spec: LayerType | dict[str, Any]) -> None: """Check if the spec can be layered.""" - def _get(spec, attr): + def _get(spec: LayerType | dict[str, Any], attr: str) -> Any: if isinstance(spec, core.SchemaBase): return spec._get(attr) else: return spec.get(attr, Undefined) - def _get_any(spec: dict | SchemaBase, *attrs: str) -> bool: + def _get_any(spec: LayerType | dict[str, Any], *attrs: str) -> bool: return any(_get(spec, attr) is not Undefined for attr in attrs) base_msg = "charts cannot be layered. Instead, layer the charts before" @@ -4539,8 +4539,10 @@ def topo_feature(url: str, feature: str, **kwargs: Any) -> UrlData: ) -def _combine_subchart_data(data, subcharts): - def remove_data(subchart): +def _combine_subchart_data( + data: Optional[ChartDataType], subcharts: list[ChartType] +) -> tuple[Optional[ChartDataType], list[ChartType]]: + def remove_data(subchart: _TSchemaBase) -> _TSchemaBase: if subchart.data is not Undefined: subchart = subchart.copy() subchart.data = Undefined @@ -4568,12 +4570,14 @@ def remove_data(subchart): core.VariableParameter, core.TopLevelSelectionParameter, core.SelectionParameter ] + +def _viewless_dict(param: _Parameter) -> dict[str, Any]: d = param.to_dict() d.pop("views", None) return d -def _needs_name(subchart): +def _needs_name(subchart: ChartType) -> bool: # Only `Chart` objects need a name if (subchart.name is not Undefined) or (not isinstance(subchart, Chart)): return False @@ -4583,7 +4587,7 @@ def _needs_name(subchart): # Convert SelectionParameters to TopLevelSelectionParameters with a views property. -def _prepare_to_lift(param: Any) -> Any: +def _prepare_to_lift(param: _Parameter) -> _Parameter: param = param.copy() if isinstance(param, core.VariableParameter): @@ -4598,15 +4602,15 @@ def _prepare_to_lift(param: Any) -> Any: return param -def _remove_duplicate_params(layer): +def _remove_duplicate_params(layer: list[ChartType]) -> list[ChartType]: subcharts = [subchart.copy() for subchart in layer] found_params = [] for subchart in subcharts: - if (not hasattr(subchart, "params")) or (subchart.params is Undefined): + if (not hasattr(subchart, "params")) or (utils.is_undefined(subchart.params)): continue - params = [] + params: list[_Parameter] = [] # Ensure the same selection parameter doesn't appear twice for param in subchart.params: @@ -4629,12 +4633,14 @@ def _remove_duplicate_params(layer): return subcharts -def _combine_subchart_params(params, subcharts): - if params is Undefined: +def _combine_subchart_params( + params: Optional[Sequence[_Parameter]], subcharts: list[ChartType] +) -> tuple[Optional[Sequence[_Parameter]], list[ChartType]]: + if utils.is_undefined(params): params = [] # List of triples related to params, (param, dictionary minus views, views) - param_info = [] + param_info: list[tuple[_Parameter, dict[str, Any], list[str]]] = [] # Put parameters already found into `param_info` list. for param in params: @@ -4650,7 +4656,7 @@ def _combine_subchart_params(params, subcharts): subcharts = [subchart.copy() for subchart in subcharts] for subchart in subcharts: - if (not hasattr(subchart, "params")) or (subchart.params is Undefined): + if (not hasattr(subchart, "params")) or (utils.is_undefined(subchart.params)): continue if _needs_name(subchart): @@ -4689,7 +4695,7 @@ def _combine_subchart_params(params, subcharts): if len(v) > 0: p.views = v - subparams = [p for p, _, _ in param_info] + subparams: Any = [p for p, _, _ in param_info] if len(subparams) == 0: subparams = Undefined @@ -4697,7 +4703,9 @@ def _combine_subchart_params(params, subcharts): return subparams, subcharts -def _get_repeat_strings(repeat): +def _get_repeat_strings( + repeat: list[str] | LayerRepeatMapping | RepeatMapping, +) -> list[str]: if isinstance(repeat, list): return repeat elif isinstance(repeat, core.LayerRepeatMapping): @@ -4709,7 +4717,7 @@ def _get_repeat_strings(repeat): return ["".join(s) for s in itertools.product(*rcstrings)] -def _extend_view_name(v, r, spec): +def _extend_view_name(v: str, r: str, spec: Chart | LayerChart) -> str: # prevent the same extension from happening more than once if isinstance(spec, Chart): if v.endswith("child__" + r): @@ -4721,14 +4729,21 @@ def _extend_view_name(v, r, spec): return v else: return f"child__{r}_{v}" + else: + msg = f"Expected 'Chart | LayerChart', but got: {type(spec).__name__!r}" + raise TypeError(msg) -def _repeat_names(params, repeat, spec): - if params is Undefined: +def _repeat_names( + params: Optional[Sequence[_Parameter]], + repeat: list[str] | LayerRepeatMapping | RepeatMapping, + spec: Chart | LayerChart, +) -> Optional[Sequence[_Parameter]]: + if utils.is_undefined(params): return params repeat = _get_repeat_strings(repeat) - params_named = [] + params_named: list[_Parameter] = [] for param in params: if not isinstance(param, core.TopLevelSelectionParameter): @@ -4755,8 +4770,10 @@ def _repeat_names(params, repeat, spec): return params_named -def _remove_layer_props(chart, subcharts, layer_props): - def remove_prop(subchart, prop): +def _remove_layer_props( + chart: LayerChart, subcharts: list[ChartType], layer_props: Iterable[str] +) -> tuple[dict[str, Any], list[ChartType]]: + def remove_prop(subchart: ChartType, prop: str) -> ChartType: # If subchart is a UnitSpec, then subchart["height"] raises a KeyError try: if subchart[prop] is not Undefined: @@ -4766,7 +4783,7 @@ def remove_prop(subchart, prop): pass return subchart - output_dict = {} + output_dict: dict[str, Any] = {} if not subcharts: # No subcharts = nothing to do. From 94ef3a5c7f248f05066a79490792b61c05bd6ea4 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:22:22 +0100 Subject: [PATCH 11/19] feat(typing): Complete `RepeatChart` annotations --- altair/vegalite/v5/api.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 2a26d9b06..cea449127 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -130,6 +130,9 @@ MultiTimeUnit_T, SingleTimeUnit_T, OneOrSeq, + AutosizeType_T, + ColorName_T, + LayoutAlign_T, ) __all__ = [ @@ -3839,24 +3842,26 @@ def __init__( self, repeat: Optional[list[str] | LayerRepeatMapping | RepeatMapping] = Undefined, spec: Optional[ChartType] = Undefined, - align=Undefined, - autosize=Undefined, - background=Undefined, - bounds=Undefined, - center=Undefined, + align: Optional[dict | SchemaBase | LayoutAlign_T] = Undefined, + autosize: Optional[dict | SchemaBase | AutosizeType_T] = Undefined, + background: Optional[ + str | dict | Parameter | SchemaBase | ColorName_T + ] = Undefined, + bounds: Optional[Literal["full", "flush"]] = Undefined, + center: Optional[bool | dict | SchemaBase] = Undefined, columns: Optional[int] = Undefined, - config=Undefined, + config: Optional[dict | SchemaBase] = Undefined, data: Optional[ChartDataType] = Undefined, - datasets=Undefined, - description=Undefined, - name=Undefined, - padding=Undefined, - params=Undefined, - resolve=Undefined, - spacing=Undefined, - title=Undefined, - transform=Undefined, - usermeta=Undefined, + datasets: Optional[dict | SchemaBase] = Undefined, + description: Optional[str] = Undefined, + name: Optional[str] = Undefined, + padding: Optional[dict | float | Parameter | SchemaBase] = Undefined, + params: Optional[Sequence[_Parameter]] = Undefined, + resolve: Optional[dict | SchemaBase] = Undefined, + spacing: Optional[dict | float | SchemaBase] = Undefined, + title: Optional[str | dict | SchemaBase | Sequence[str]] = Undefined, + transform: Optional[Sequence[dict | SchemaBase]] = Undefined, + usermeta: Optional[dict | SchemaBase] = Undefined, **kwds, ) -> None: tp_name = type(self).__name__ From 470a4909ae9d416130e06efabcf3d0a9ccafdaf0 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:29:40 +0100 Subject: [PATCH 12/19] fix(typing): Resolve Liskov violations ``` altair\vegalite\v5\api.py:4368: error: Argument 1 of "__iadd__" is incompatible with "__add__" of supertype "TopLevelMixin"; supertype defines the argument type as "Chart | RepeatChart | ConcatChart | HConcatChart | VConcatChart | FacetChart | LayerChart" [override] def __iadd__(self, other: LayerChart | Chart) -> Self: ^~~~~~~~~~~~~~~~~~~~~~~~~ altair\vegalite\v5\api.py:4368: note: This violates the Liskov substitution principle altair\vegalite\v5\api.py:4368: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides altair\vegalite\v5\api.py:4376: error: Argument 1 of "__add__" is incompatible with supertype "TopLevelMixin"; supertype defines the argument type as "Chart | RepeatChart | ConcatChart | HConcatChart | VConcatChart | FacetChart | LayerChart" [override] def __add__(self, other: LayerChart | Chart) -> Self: ^~~~~~~~~~~~~~~~~~~~~~~~~ altair\vegalite\v5\api.py:4376: note: This violates the Liskov substitution principle altair\vegalite\v5\api.py:4376: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides ``` --- altair/vegalite/v5/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index cea449127..8d7dfe4bd 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -4365,7 +4365,7 @@ def transformed_data( return transformed_data(self, row_limit=row_limit, exclude=exclude) - def __iadd__(self, other: LayerChart | Chart) -> Self: + def __iadd__(self, other: ChartType) -> Self: _check_if_valid_subspec(other, "LayerChart") _check_if_can_be_layered(other) self.layer.append(other) @@ -4373,7 +4373,7 @@ def __iadd__(self, other: LayerChart | Chart) -> Self: self.params, self.layer = _combine_subchart_params(self.params, self.layer) return self - def __add__(self, other: LayerChart | Chart) -> Self: + def __add__(self, other: ChartType) -> Self: copy = self.copy(deep=["layer"]) copy += other return copy From 8287f5eb6cfbff497966e45b5ab09350296b3bba Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:31:08 +0100 Subject: [PATCH 13/19] chore(typing): Update more `dict` -> `dict[str, Any]` --- altair/vegalite/v5/api.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 8d7dfe4bd..88eba1d61 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -299,7 +299,7 @@ class LookupData(core.LookupData): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - def to_dict(self, *args, **kwargs) -> dict: + def to_dict(self, *args, **kwargs) -> dict[str, Any]: """Convert the chart to a dictionary suitable for JSON export.""" copy = self.copy(deep=False) copy.data = _prepare_data(copy.data, kwargs.get("context")) @@ -313,7 +313,7 @@ class FacetMapping(core.FacetMapping): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - def to_dict(self, *args, **kwargs) -> dict: + def to_dict(self, *args, **kwargs) -> dict[str, Any]: copy = self.copy(deep=False) context = kwargs.get("context", {}) data = context.get("data", None) @@ -364,11 +364,11 @@ def __init__( self.param_type = param_type @utils.deprecated(version="5.0.0", alternative="to_dict") - def ref(self) -> dict: + def ref(self) -> dict[str, Any]: """'ref' is deprecated. No need to call '.ref()' anymore.""" return self.to_dict() - def to_dict(self) -> dict[str, str | dict]: + def to_dict(self) -> dict[str, str | dict[str, Any]]: if self.param_type == "variable": return {"expr": self.name} elif self.param_type == "selection": @@ -3649,7 +3649,7 @@ def to_dict( format: str = "vega-lite", ignore: list[str] | None = None, context: dict[str, Any] | None = None, - ) -> dict: + ) -> dict[str, Any]: """ Convert the chart to a dictionary suitable for JSON export. From 00985dcd4793aa544517dff57b07299f641d1c29 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:32:11 +0100 Subject: [PATCH 14/19] style(ruff): fix whitespace --- altair/vegalite/v5/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 88eba1d61..4d2bff2cd 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -3588,9 +3588,9 @@ def __init__( height: Optional[int | str | dict | Step] = Undefined, **kwargs, ) -> None: - # Data type hints won't match with what TopLevelUnitSpec expects - # as there is some data processing happening when converting to - # a VL spec + # Data type hints won't match with what TopLevelUnitSpec expects + # as there is some data processing happening when converting to + # a VL spec super().__init__( data=data, # type: ignore[arg-type] encoding=encoding, From 831d653d297c000b766aa68eace8e85dbbd34a1b Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:05:54 +0100 Subject: [PATCH 15/19] chore: Remove TODO fixed in #3480 --- altair/vegalite/v5/api.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 4d2bff2cd..43354aee0 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -4000,7 +4000,6 @@ def __init__( columns: Optional[float] = Undefined, **kwargs, ) -> None: - # TODO: move common data to top level? for spec in concat: _check_if_valid_subspec(spec, "ConcatChart") super().__init__(data=data, concat=list(concat), columns=columns, **kwargs) # type: ignore[arg-type] @@ -4105,7 +4104,6 @@ def __init__( hconcat: Sequence[ConcatType] = (), **kwargs, ) -> None: - # TODO: move common data to top level? for spec in hconcat: _check_if_valid_subspec(spec, "HConcatChart") super().__init__(data=data, hconcat=list(hconcat), **kwargs) # type: ignore[arg-type] @@ -4210,7 +4208,6 @@ def __init__( vconcat: Sequence[ConcatType] = (), **kwargs, ) -> None: - # TODO: move common data to top level? for spec in vconcat: _check_if_valid_subspec(spec, "VConcatChart") super().__init__(data=data, vconcat=list(vconcat), **kwargs) # type: ignore[arg-type] @@ -4317,7 +4314,6 @@ def __init__( layer: Sequence[LayerType] = (), **kwargs, ) -> None: - # TODO: move common data to top level? # TODO: check for conflicting interaction for spec in layer: _check_if_valid_subspec(spec, "LayerChart") From 60281f54829494e5c37e7e3205d2bb09cc4ac0db Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:09:50 +0100 Subject: [PATCH 16/19] fix(typing): Enable `ANN003` and fix all in `api` --- altair/vegalite/v5/api.py | 58 +++++++++++++++++++-------------------- pyproject.toml | 1 - 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 43354aee0..103fd6441 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -296,10 +296,10 @@ def _prepare_data( class LookupData(core.LookupData): @utils.use_signature(core.LookupData) - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *args, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - def to_dict(self, *args, **kwargs) -> dict[str, Any]: + def to_dict(self, *args, **kwargs: Any) -> dict[str, Any]: """Convert the chart to a dictionary suitable for JSON export.""" copy = self.copy(deep=False) copy.data = _prepare_data(copy.data, kwargs.get("context")) @@ -310,10 +310,10 @@ class FacetMapping(core.FacetMapping): _class_is_valid_at_instantiation = False @utils.use_signature(core.FacetMapping) - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *args, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - def to_dict(self, *args, **kwargs) -> dict[str, Any]: + def to_dict(self, *args, **kwargs: Any) -> dict[str, Any]: copy = self.copy(deep=False) context = kwargs.get("context", {}) data = context.get("data", None) @@ -988,7 +988,7 @@ def when( ) raise NotImplementedError(msg) - def to_dict(self, *args, **kwds) -> _Conditional[_C]: # type: ignore[override] + def to_dict(self, *args, **kwds: Any) -> _Conditional[_C]: # type: ignore[override] m = super().to_dict(*args, **kwds) return _Conditional(condition=m["condition"]) @@ -1748,7 +1748,7 @@ def to_json( format: str = "vega-lite", ignore: list[str] | None = None, context: dict[str, Any] | None = None, - **kwargs, + **kwargs: Any, ) -> str: """ Convert a chart to a JSON string. @@ -1793,7 +1793,7 @@ def to_html( fullhtml: bool = True, requirejs: bool = False, inline: bool = False, - **kwargs, + **kwargs: Any, ) -> str: """ Embed a Vega/Vega-Lite spec into an HTML page. @@ -1895,7 +1895,7 @@ def save( webdriver: str | None = None, engine: str | None = None, inline: bool = False, - **kwargs, + **kwargs: Any, ) -> None: """ Save a chart to file in a variety of formats. @@ -2014,7 +2014,7 @@ def repeat( column: Optional[list[str]] = Undefined, layer: Optional[list[str]] = Undefined, columns: Optional[int] = Undefined, - **kwargs, + **kwargs: Any, ) -> RepeatChart: """ Return a RepeatChart built from the chart. @@ -2069,7 +2069,7 @@ def repeat( spec=t.cast("ChartType", self), repeat=repeat_arg, columns=columns, **kwargs ) - def properties(self, **kwargs) -> Self: + def properties(self, **kwargs: Any) -> Self: """ Set top-level properties of the Chart. @@ -2117,7 +2117,7 @@ def project( translate: Optional[ list[float] | Vector2number | ExprRef | Parameter ] = Undefined, - **kwds, + **kwds: Any, ) -> Self: """ Add a geographic projection to the chart. @@ -2332,7 +2332,7 @@ def transform_bin( as_: Optional[str | FieldName | list[str | FieldName]] = Undefined, field: Optional[str | FieldName] = Undefined, bin: Literal[True] | BinParams = True, - **kwargs, + **kwargs: Any, ) -> Self: """ Add a :class:`BinTransform` to the schema. @@ -2692,7 +2692,7 @@ def transform_filter( | Parameter | PredicateComposition | dict[str, Predicate | str | list | bool], - **kwargs, + **kwargs: Any, ) -> Self: """ Add a :class:`FilterTransform` to the schema. @@ -2831,7 +2831,7 @@ def transform_lookup( from_: Optional[LookupData | LookupSelection] = Undefined, as_: Optional[str | FieldName | list[str | FieldName]] = Undefined, default: Optional[str] = Undefined, - **kwargs, + **kwargs: Any, ) -> Self: """ Add a :class:`DataLookupTransform` or :class:`SelectionLookupTransform` to the chart. @@ -3289,7 +3289,7 @@ def transform_window( # Display-related methods - def _repr_mimebundle_(self, *args, **kwds) -> MimeBundleType | None: # type:ignore[return] + def _repr_mimebundle_(self, *args, **kwds) -> MimeBundleType | None: # type:ignore[return] # noqa: ANN003 """Return a MIME bundle for display in Jupyter frontends.""" # Catch errors explicitly to get around issues in Jupyter frontend # see https://github.com/ipython/ipython/issues/11038 @@ -3307,7 +3307,7 @@ def display( renderer: Optional[Literal["canvas", "svg"]] = Undefined, theme: Optional[str] = Undefined, actions: Optional[bool | dict] = Undefined, - **kwargs, + **kwargs: Any, ) -> None: """ Display chart in Jupyter notebook or JupyterLab. @@ -3356,7 +3356,7 @@ def serve( jupyter_warning=True, # noqa: ANN001 open_browser=True, # noqa: ANN001 http_server=None, # noqa: ANN001 - **kwargs, + **kwargs, # noqa: ANN003 ) -> None: """ 'serve' is deprecated. Use 'show' instead. @@ -3418,7 +3418,7 @@ def show(self) -> None: display(self) @utils.use_signature(core.Resolve) - def _set_resolve(self, **kwargs): # noqa: ANN202 + def _set_resolve(self, **kwargs: Any): # noqa: ANN202 """Copy the chart and update the resolve property with kwargs.""" if not hasattr(self, "resolve"): msg = f"{self.__class__} object has no attribute " "'resolve'" @@ -3431,19 +3431,19 @@ def _set_resolve(self, **kwargs): # noqa: ANN202 return copy @utils.use_signature(core.AxisResolveMap) - def resolve_axis(self, *args, **kwargs) -> Self: + def resolve_axis(self, *args, **kwargs: Any) -> Self: check = _top_schema_base(self) r = check._set_resolve(axis=core.AxisResolveMap(*args, **kwargs)) return t.cast("Self", r) @utils.use_signature(core.LegendResolveMap) - def resolve_legend(self, *args, **kwargs) -> Self: + def resolve_legend(self, *args, **kwargs: Any) -> Self: check = _top_schema_base(self) r = check._set_resolve(legend=core.LegendResolveMap(*args, **kwargs)) return t.cast("Self", r) @utils.use_signature(core.ScaleResolveMap) - def resolve_scale(self, *args, **kwargs) -> Self: + def resolve_scale(self, *args, **kwargs: Any) -> Self: check = _top_schema_base(self) r = check._set_resolve(scale=core.ScaleResolveMap(*args, **kwargs)) return t.cast("Self", r) @@ -3459,7 +3459,7 @@ def facet( column: Optional[str | FacetFieldDef | Column] = Undefined, data: Optional[ChartDataType] = Undefined, columns: Optional[int] = Undefined, - **kwargs, + **kwargs: Any, ) -> FacetChart: """ Create a facet chart from the current chart. @@ -3586,7 +3586,7 @@ def __init__( mark: Optional[str | AnyMark] = Undefined, width: Optional[int | str | dict | Step] = Undefined, height: Optional[int | str | dict | Step] = Undefined, - **kwargs, + **kwargs: Any, ) -> None: # Data type hints won't match with what TopLevelUnitSpec expects # as there is some data processing happening when converting to @@ -3862,7 +3862,7 @@ def __init__( title: Optional[str | dict | SchemaBase | Sequence[str]] = Undefined, transform: Optional[Sequence[dict | SchemaBase]] = Undefined, usermeta: Optional[dict | SchemaBase] = Undefined, - **kwds, + **kwds: Any, ) -> None: tp_name = type(self).__name__ if utils.is_undefined(spec): @@ -3998,7 +3998,7 @@ def __init__( data: Optional[ChartDataType] = Undefined, concat: Sequence[ConcatType] = (), columns: Optional[float] = Undefined, - **kwargs, + **kwargs: Any, ) -> None: for spec in concat: _check_if_valid_subspec(spec, "ConcatChart") @@ -4102,7 +4102,7 @@ def __init__( self, data: Optional[ChartDataType] = Undefined, hconcat: Sequence[ConcatType] = (), - **kwargs, + **kwargs: Any, ) -> None: for spec in hconcat: _check_if_valid_subspec(spec, "HConcatChart") @@ -4206,7 +4206,7 @@ def __init__( self, data: Optional[ChartDataType] = Undefined, vconcat: Sequence[ConcatType] = (), - **kwargs, + **kwargs: Any, ) -> None: for spec in vconcat: _check_if_valid_subspec(spec, "VConcatChart") @@ -4312,7 +4312,7 @@ def __init__( self, data: Optional[ChartDataType] = Undefined, layer: Sequence[LayerType] = (), - **kwargs, + **kwargs: Any, ) -> None: # TODO: check for conflicting interaction for spec in layer: @@ -4440,7 +4440,7 @@ def __init__( spec: Optional[ChartType] = Undefined, facet: Optional[dict | SchemaBase] = Undefined, params: Optional[Sequence[_Parameter]] = Undefined, - **kwargs, + **kwargs: Any, ) -> None: if utils.is_undefined(spec): msg = f"{type(self).__name__!r} requires a `spec`, but got: {spec!r}" diff --git a/pyproject.toml b/pyproject.toml index 6a0b61456..aeac2ac11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -355,7 +355,6 @@ ignore = [ # doc-line-too-long "W505", "ANN002", # args - "ANN003", # kwargs "ANN401" # Any ] # https://docs.astral.sh/ruff/settings/#lintpydocstyle From 3d3fd1e43c40b3084cb3ec1f245225247124f3bf Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:14:18 +0100 Subject: [PATCH 17/19] fix(typing): Enable `ANN002` and fix all in `api` --- altair/vegalite/v5/api.py | 32 ++++++++++++++++---------------- pyproject.toml | 7 ++++--- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 103fd6441..67e35c213 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -296,10 +296,10 @@ def _prepare_data( class LookupData(core.LookupData): @utils.use_signature(core.LookupData) - def __init__(self, *args, **kwargs: Any) -> None: + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - def to_dict(self, *args, **kwargs: Any) -> dict[str, Any]: + def to_dict(self, *args: Any, **kwargs: Any) -> dict[str, Any]: """Convert the chart to a dictionary suitable for JSON export.""" copy = self.copy(deep=False) copy.data = _prepare_data(copy.data, kwargs.get("context")) @@ -310,10 +310,10 @@ class FacetMapping(core.FacetMapping): _class_is_valid_at_instantiation = False @utils.use_signature(core.FacetMapping) - def __init__(self, *args, **kwargs: Any) -> None: + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - def to_dict(self, *args, **kwargs: Any) -> dict[str, Any]: + def to_dict(self, *args: Any, **kwargs: Any) -> dict[str, Any]: copy = self.copy(deep=False) context = kwargs.get("context", {}) data = context.get("data", None) @@ -988,7 +988,7 @@ def when( ) raise NotImplementedError(msg) - def to_dict(self, *args, **kwds: Any) -> _Conditional[_C]: # type: ignore[override] + def to_dict(self, *args: Any, **kwds: Any) -> _Conditional[_C]: # type: ignore[override] m = super().to_dict(*args, **kwds) return _Conditional(condition=m["condition"]) @@ -3289,7 +3289,7 @@ def transform_window( # Display-related methods - def _repr_mimebundle_(self, *args, **kwds) -> MimeBundleType | None: # type:ignore[return] # noqa: ANN003 + def _repr_mimebundle_(self, *args, **kwds) -> MimeBundleType | None: # type:ignore[return] # noqa: ANN002, ANN003 """Return a MIME bundle for display in Jupyter frontends.""" # Catch errors explicitly to get around issues in Jupyter frontend # see https://github.com/ipython/ipython/issues/11038 @@ -3431,19 +3431,19 @@ def _set_resolve(self, **kwargs: Any): # noqa: ANN202 return copy @utils.use_signature(core.AxisResolveMap) - def resolve_axis(self, *args, **kwargs: Any) -> Self: + def resolve_axis(self, *args: Any, **kwargs: Any) -> Self: check = _top_schema_base(self) r = check._set_resolve(axis=core.AxisResolveMap(*args, **kwargs)) return t.cast("Self", r) @utils.use_signature(core.LegendResolveMap) - def resolve_legend(self, *args, **kwargs: Any) -> Self: + def resolve_legend(self, *args: Any, **kwargs: Any) -> Self: check = _top_schema_base(self) r = check._set_resolve(legend=core.LegendResolveMap(*args, **kwargs)) return t.cast("Self", r) @utils.use_signature(core.ScaleResolveMap) - def resolve_scale(self, *args, **kwargs: Any) -> Self: + def resolve_scale(self, *args: Any, **kwargs: Any) -> Self: check = _top_schema_base(self) r = check._set_resolve(scale=core.ScaleResolveMap(*args, **kwargs)) return t.cast("Self", r) @@ -3730,7 +3730,7 @@ def add_params(self, *params: Parameter) -> Self: return copy @utils.deprecated(version="5.0.0", alternative="add_params") - def add_selection(self, *params) -> Self: + def add_selection(self, *params) -> Self: # noqa: ANN002 """'add_selection' is deprecated. Use 'add_params' instead.""" return self.add_params(*params) @@ -3960,7 +3960,7 @@ def add_params(self, *params: Parameter) -> Self: return copy.copy() @utils.deprecated(version="5.0.0", alternative="add_params") - def add_selection(self, *selections) -> Self: + def add_selection(self, *selections) -> Self: # noqa: ANN002 """'add_selection' is deprecated. Use 'add_params' instead.""" return self.add_params(*selections) @@ -4084,7 +4084,7 @@ def add_params(self, *params: Parameter) -> Self: return copy @utils.deprecated(version="5.0.0", alternative="add_params") - def add_selection(self, *selections) -> Self: + def add_selection(self, *selections) -> Self: # noqa: ANN002 """'add_selection' is deprecated. Use 'add_params' instead.""" return self.add_params(*selections) @@ -4188,7 +4188,7 @@ def add_params(self, *params: Parameter) -> Self: return copy @utils.deprecated(version="5.0.0", alternative="add_params") - def add_selection(self, *selections) -> Self: + def add_selection(self, *selections) -> Self: # noqa: ANN002 """'add_selection' is deprecated. Use 'add_params' instead.""" return self.add_params(*selections) @@ -4294,7 +4294,7 @@ def add_params(self, *params: Parameter) -> Self: return copy @utils.deprecated(version="5.0.0", alternative="add_params") - def add_selection(self, *selections) -> Self: + def add_selection(self, *selections) -> Self: # noqa: ANN002 """'add_selection' is deprecated. Use 'add_params' instead.""" return self.add_params(*selections) @@ -4420,7 +4420,7 @@ def add_params(self, *params: Parameter) -> Self: return copy.copy() @utils.deprecated(version="5.0.0", alternative="add_params") - def add_selection(self, *selections) -> Self: + def add_selection(self, *selections) -> Self: # noqa: ANN002 """'add_selection' is deprecated. Use 'add_params' instead.""" return self.add_params(*selections) @@ -4512,7 +4512,7 @@ def add_params(self, *params: Parameter) -> Self: return copy.copy() @utils.deprecated(version="5.0.0", alternative="add_params") - def add_selection(self, *selections) -> Self: + def add_selection(self, *selections) -> Self: # noqa: ANN002 """'add_selection' is deprecated. Use 'add_params' instead.""" return self.add_params(*selections) diff --git a/pyproject.toml b/pyproject.toml index aeac2ac11..51b09b681 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -309,7 +309,8 @@ select = [ "D213", # numpy-specific-rules "NPY", - "ANN" + # flake8-annotations + "ANN", ] ignore = [ # Whitespace before ':' @@ -354,8 +355,8 @@ ignore = [ "D413", # doc-line-too-long "W505", - "ANN002", # args - "ANN401" # Any + # Any as annotation + "ANN401" ] # https://docs.astral.sh/ruff/settings/#lintpydocstyle pydocstyle={ convention="numpy" } From 010d4fb142ee5b1cc7fe82a85bdb8b342a84e841 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:16:43 +0100 Subject: [PATCH 18/19] chore(ruff): Add note on `ANN` This could later be extended to other modules, but for now `api` is complete. --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 51b09b681..c184861b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -368,6 +368,7 @@ mccabe={ max-complexity=18 } "typing.Optional".msg = "Use `Union[T, None]` instead.\n`typing.Optional` is likely to be confused with `altair.Optional`, which have a similar but different semantic meaning.\nSee https://github.com/vega/altair/pull/3449" [tool.ruff.lint.per-file-ignores] +# Only enforce type annotation rules on public api "!altair/vegalite/v5/api.py" = ["ANN"] From c5ab6f0799515130f78191f8bdb9082753d4d575 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:54:06 +0100 Subject: [PATCH 19/19] fix(typing): Add missing `FacetChart` annotations To align with the other `ChartType`s --- altair/vegalite/v5/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 6e04daa53..9d1de4ab0 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -4528,6 +4528,8 @@ def __init__( spec = _spec_as_list[0] super().__init__(data=data, spec=spec, facet=facet, params=params, **kwargs) # type: ignore[arg-type] self.data: Optional[ChartDataType] + self.spec: ChartType + self.params: Optional[Sequence[_Parameter]] def transformed_data( self, row_limit: int | None = None, exclude: Iterable[str] | None = None