From 680b69efc9fa56bde2df456ab47623f89445b2b7 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 3 Aug 2024 12:53:05 +0100 Subject: [PATCH 01/32] feat(typing): Create `altair.typing` https://github.com/vega/altair/issues/3514 --- altair/__init__.py | 2 ++ altair/typing.py | 0 2 files changed, 2 insertions(+) create mode 100644 altair/typing.py diff --git a/altair/__init__.py b/altair/__init__.py index d6c03f48a..a1809ec5a 100644 --- a/altair/__init__.py +++ b/altair/__init__.py @@ -634,6 +634,7 @@ "to_json", "to_values", "topo_feature", + "typing", "utils", "v5", "value", @@ -654,6 +655,7 @@ def __dir__(): from altair.jupyter import JupyterChart from altair.expr import expr from altair.utils import AltairDeprecationWarning, parse_shorthand, Optional, Undefined +from altair import typing def load_ipython_extension(ipython): diff --git a/altair/typing.py b/altair/typing.py new file mode 100644 index 000000000..e69de29bb From 785f89c408fa237d428fec69499c738c23be83be Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 3 Aug 2024 13:06:01 +0100 Subject: [PATCH 02/32] chore: Add comment on `tooltip` annotation Trying to trigger some places to add github comments - for actual threads --- tools/generate_schema_wrapper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index df854b78a..e1022d463 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -888,6 +888,9 @@ def _create_encode_signature( # We could be more specific about what types are accepted in the list # but then the signatures would get rather long and less useful # to a user when it shows up in their IDE. + + # NOTE: Currently triggered only for `detail`, `order`, `tooltip` + # I think the `tooltip` one especially we should be more specific union_types.append("list") docstring_union_types.append("List") From 5423748d8b5fa6ae978210bd22ecfff07483eee3 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 3 Aug 2024 13:14:45 +0100 Subject: [PATCH 03/32] feat(typing): Add reference impl `EncodeKwds` from comment --- altair/typing.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/altair/typing.py b/altair/typing.py index e69de29bb..33bb38b0b 100644 --- a/altair/typing.py +++ b/altair/typing.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import sys +from typing import Any, Mapping, Union +from typing_extensions import TypedDict + +if sys.version_info >= (3, 10): + from typing import TypeAlias +else: + from typing_extensions import TypeAlias + +ChannelType: TypeAlias = Union[str, Mapping[str, Any], Any] + + +class EncodeKwds(TypedDict, total=False): + """ + Reference implementation from [comment code block](https://github.com/pola-rs/polars/pull/17995#issuecomment-2263609625). + + Aiming to define more specific `ChannelType`, without being exact. + + This would be generated alongside `tools.generate_schema_wrapper._create_encode_signature` + """ + + angle: ChannelType + color: ChannelType + column: ChannelType + description: ChannelType + detail: ChannelType | list[Any] + facet: ChannelType + fill: ChannelType + fillOpacity: ChannelType + href: ChannelType + key: ChannelType + latitude: ChannelType + latitude2: ChannelType + longitude: ChannelType + longitude2: ChannelType + opacity: ChannelType + order: ChannelType | list[Any] + radius: ChannelType + radius2: ChannelType + row: ChannelType + shape: ChannelType + size: ChannelType + stroke: ChannelType + strokeDash: ChannelType + strokeOpacity: ChannelType + strokeWidth: ChannelType + text: ChannelType + theta: ChannelType + theta2: ChannelType + tooltip: ChannelType | list[Any] + url: ChannelType + x: ChannelType + x2: ChannelType + xError: ChannelType + xError2: ChannelType + xOffset: ChannelType + y: ChannelType + y2: ChannelType + yError: ChannelType + yError2: ChannelType + yOffset: ChannelType From da6473836d09b86d99a4f08389846cb13ef88f98 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:02:02 +0100 Subject: [PATCH 04/32] feat(typing): Use `OneOrSeq[tps]` instead of `Union[*tps, list]` in `.encode()` --- tools/generate_schema_wrapper.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index e1022d463..b89f9f640 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -884,25 +884,21 @@ def _create_encode_signature( # for more background. union_types = ["str", field_class_name, "Map"] docstring_union_types = ["str", rst_syntax_for_class(field_class_name), "Dict"] + tp_inner: str = " | ".join(chain(union_types, datum_and_value_class_names)) if info.supports_arrays: # We could be more specific about what types are accepted in the list # but then the signatures would get rather long and less useful # to a user when it shows up in their IDE. + docstring_union_types.append("List") # NOTE: Currently triggered only for `detail`, `order`, `tooltip` # I think the `tooltip` one especially we should be more specific - union_types.append("list") - docstring_union_types.append("List") + tp_inner = f"OneOrSeq[{tp_inner}]" - union_types = union_types + datum_and_value_class_names + signature_args.append(f"{channel}: Optional[{tp_inner}] = Undefined") docstring_union_types = docstring_union_types + [ rst_syntax_for_class(c) for c in datum_and_value_class_names ] - - signature_args.append( - f"{channel}: Optional[Union[{', '.join(union_types)}]] = Undefined" - ) - docstring_parameters.extend( ( f"{channel} : {', '.join(docstring_union_types)}", From 5877b38fdccd589338c61a35d0e06e21945fe1f4 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:03:24 +0100 Subject: [PATCH 05/32] build: run `generate-schema-wrapper` --- altair/vegalite/v5/schema/channels.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/altair/vegalite/v5/schema/channels.py b/altair/vegalite/v5/schema/channels.py index d41a2c96d..21dc29578 100644 --- a/altair/vegalite/v5/schema/channels.py +++ b/altair/vegalite/v5/schema/channels.py @@ -30905,7 +30905,7 @@ def encode( color: Optional[str | Color | Map | ColorDatum | ColorValue] = Undefined, column: Optional[str | Column | Map] = Undefined, description: Optional[str | Description | Map | DescriptionValue] = Undefined, - detail: Optional[str | Detail | Map | list] = Undefined, + detail: Optional[OneOrSeq[str | Detail | Map]] = Undefined, facet: Optional[str | Facet | Map] = Undefined, fill: Optional[str | Fill | Map | FillDatum | FillValue] = Undefined, fillOpacity: Optional[ @@ -30924,7 +30924,7 @@ def encode( opacity: Optional[ str | Opacity | Map | OpacityDatum | OpacityValue ] = Undefined, - order: Optional[str | Order | Map | list | OrderValue] = Undefined, + order: Optional[OneOrSeq[str | Order | Map | OrderValue]] = Undefined, radius: Optional[str | Radius | Map | RadiusDatum | RadiusValue] = Undefined, radius2: Optional[ str | Radius2 | Map | Radius2Datum | Radius2Value @@ -30945,7 +30945,7 @@ def encode( text: Optional[str | Text | Map | TextDatum | TextValue] = Undefined, theta: Optional[str | Theta | Map | ThetaDatum | ThetaValue] = Undefined, theta2: Optional[str | Theta2 | Map | Theta2Datum | Theta2Value] = Undefined, - tooltip: Optional[str | Tooltip | Map | list | TooltipValue] = Undefined, + tooltip: Optional[OneOrSeq[str | Tooltip | Map | TooltipValue]] = Undefined, url: Optional[str | Url | Map | UrlValue] = Undefined, x: Optional[str | X | Map | XDatum | XValue] = Undefined, x2: Optional[str | X2 | Map | X2Datum | X2Value] = Undefined, From 4fe5f3a1f86e7d2d444ec30ea2fcf779653ebe0b Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:20:15 +0100 Subject: [PATCH 06/32] wip: generate `typed_dict_args` --- tools/generate_schema_wrapper.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index b89f9f640..576367d77 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -866,6 +866,11 @@ def _create_encode_signature( ) -> str: signature_args: list[str] = ["self", "*args: Any"] docstring_parameters: list[str] = ["", "Parameters", "----------"] + + # TODO: refactor so extracting the `TypedDict` here makes sense + # - Want to avoid simply returning tuple[str, str] + typed_dict_args: list[str] = [] + for channel, info in channel_infos.items(): field_class_name = info.field_class_name assert ( @@ -896,6 +901,7 @@ def _create_encode_signature( tp_inner = f"OneOrSeq[{tp_inner}]" signature_args.append(f"{channel}: Optional[{tp_inner}] = Undefined") + typed_dict_args.append(f"{channel}: {tp_inner}") docstring_union_types = docstring_union_types + [ rst_syntax_for_class(c) for c in datum_and_value_class_names ] From 0014cc8e5527f8115c4cd2f4cef9e3e382cd4379 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:00:41 +0100 Subject: [PATCH 07/32] refactor: Simplify `tools`, remove redundant code Pushing this mid-refactor of `_create_encode_signature`. Currently 0 changes to generated code --- tools/generate_schema_wrapper.py | 103 +++++++++++++------------------ tools/schemapi/codegen.py | 15 +---- tools/schemapi/utils.py | 85 ++++++++++++------------- 3 files changed, 86 insertions(+), 117 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 576367d77..58d2ee35e 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -562,18 +562,29 @@ def _type_checking_only_imports(*imports: str) -> str: class ChannelInfo: supports_arrays: bool deep_description: str - field_class_name: str | None = None + field_class_name: str datum_class_name: str | None = None value_class_name: str | None = None + @property + def is_field_only(self) -> bool: + return not (self.datum_class_name or self.value_class_name) + @property def all_names(self) -> Iterator[str]: - if self.field_class_name: - yield self.field_class_name - if self.datum_class_name: - yield self.datum_class_name - if self.value_class_name: - yield self.value_class_name + """All channels are expected to have a field class.""" + yield self.field_class_name + yield from self.non_field_names + + @property + def non_field_names(self) -> Iterator[str]: + if self.is_field_only: + yield from () + else: + if self.datum_class_name: + yield self.datum_class_name + if self.value_class_name: + yield self.value_class_name def generate_vegalite_channel_wrappers( @@ -595,50 +606,38 @@ def generate_vegalite_channel_wrappers( supports_arrays = any( schema_info.is_array() for schema_info in propschema.anyOf ) + classname: str = prop[0].upper() + prop[1:] channel_info = ChannelInfo( supports_arrays=supports_arrays, deep_description=propschema.deep_description, + field_class_name=classname, ) for encoding_spec, definition in def_dict.items(): - classname = prop[0].upper() + prop[1:] basename = definition.rsplit("/", maxsplit=1)[-1] basename = get_valid_identifier(basename) + gen: SchemaGenerator defschema = {"$ref": definition} - - Generator: ( - type[FieldSchemaGenerator] - | type[DatumSchemaGenerator] - | type[ValueSchemaGenerator] - ) + kwds = { + "basename": basename, + "schema": defschema, + "rootschema": schema, + "encodingname": prop, + "haspropsetters": True, + "altair_classes_prefix": "core", + } if encoding_spec == "field": - Generator = FieldSchemaGenerator - nodefault = [] - channel_info.field_class_name = classname - + gen = FieldSchemaGenerator(classname, nodefault=[], **kwds) elif encoding_spec == "datum": - Generator = DatumSchemaGenerator - classname += "Datum" - nodefault = ["datum"] - channel_info.datum_class_name = classname - + temp_name = f"{classname}Datum" + channel_info.datum_class_name = temp_name + gen = DatumSchemaGenerator(temp_name, nodefault=["datum"], **kwds) elif encoding_spec == "value": - Generator = ValueSchemaGenerator - classname += "Value" - nodefault = ["value"] - channel_info.value_class_name = classname - - gen = Generator( - classname=classname, - basename=basename, - schema=defschema, - rootschema=schema, - encodingname=prop, - nodefault=nodefault, - haspropsetters=True, - altair_classes_prefix="core", - ) + temp_name = f"{classname}Value" + channel_info.value_class_name = temp_name + gen = ValueSchemaGenerator(temp_name, nodefault=["value"], **kwds) + class_defs.append(gen.schema_class()) channel_infos[prop] = channel_info @@ -872,42 +871,26 @@ def _create_encode_signature( typed_dict_args: list[str] = [] for channel, info in channel_infos.items(): - field_class_name = info.field_class_name - assert ( - field_class_name is not None - ), "All channels are expected to have a field class" - datum_and_value_class_names = [] - if info.datum_class_name is not None: - datum_and_value_class_names.append(info.datum_class_name) - - if info.value_class_name is not None: - datum_and_value_class_names.append(info.value_class_name) - - # dict stands for the return types of alt.datum, alt.value as well as + # `Map` stands for the return types of alt.datum, alt.value as well as # the dictionary representation of an encoding channel class. See # discussions in https://github.com/vega/altair/pull/3208 # for more background. - union_types = ["str", field_class_name, "Map"] - docstring_union_types = ["str", rst_syntax_for_class(field_class_name), "Dict"] - tp_inner: str = " | ".join(chain(union_types, datum_and_value_class_names)) + union_types = ["str", info.field_class_name, "Map"] + it_rst_names = (rst_syntax_for_class(c) for c in info.all_names) + docstring_union_types = ["str", next(it_rst_names), "Dict"] + tp_inner: str = " | ".join(chain(union_types, info.non_field_names)) if info.supports_arrays: # We could be more specific about what types are accepted in the list # but then the signatures would get rather long and less useful # to a user when it shows up in their IDE. docstring_union_types.append("List") - - # NOTE: Currently triggered only for `detail`, `order`, `tooltip` - # I think the `tooltip` one especially we should be more specific tp_inner = f"OneOrSeq[{tp_inner}]" signature_args.append(f"{channel}: Optional[{tp_inner}] = Undefined") typed_dict_args.append(f"{channel}: {tp_inner}") - docstring_union_types = docstring_union_types + [ - rst_syntax_for_class(c) for c in datum_and_value_class_names - ] docstring_parameters.extend( ( - f"{channel} : {', '.join(docstring_union_types)}", + f"{channel} : {', '.join(chain(docstring_union_types, it_rst_names))}", f" {process_description(info.deep_description)}", ) ) diff --git a/tools/schemapi/codegen.py b/tools/schemapi/codegen.py index 3d232240d..8217c6a6a 100644 --- a/tools/schemapi/codegen.py +++ b/tools/schemapi/codegen.py @@ -226,12 +226,7 @@ def docstring(self, indent: int = 0) -> str: ): propinfo = info.properties[prop] doc += [ - "{} : {}".format( - prop, - propinfo.get_python_type_representation( - altair_classes_prefix=self.altair_classes_prefix, - ), - ), + f"{prop} : {propinfo.get_python_type_representation()}", f" {self._process_description(propinfo.deep_description)}", ] if len(doc) > 1: @@ -279,9 +274,7 @@ def init_args( [ *additional_types, *info.properties[p].get_python_type_representation( - for_type_hints=True, - altair_classes_prefix=self.altair_classes_prefix, - return_as_str=False, + for_type_hints=True, return_as_str=False ), ] ) @@ -315,9 +308,7 @@ def get_args(self, si: SchemaInfo) -> list[str]: [ f"{p}: " + info.get_python_type_representation( - for_type_hints=True, - altair_classes_prefix=self.altair_classes_prefix, - additional_type_hints=["UndefinedType"], + for_type_hints=True, additional_type_hints=["UndefinedType"] ) + " = Undefined" for p, info in prop_infos.items() diff --git a/tools/schemapi/utils.py b/tools/schemapi/utils.py index 9ff3953f0..5e27ac8d5 100644 --- a/tools/schemapi/utils.py +++ b/tools/schemapi/utils.py @@ -10,7 +10,16 @@ from html import unescape from itertools import chain from operator import itemgetter -from typing import TYPE_CHECKING, Any, Final, Iterable, Iterator, Sequence +from typing import ( + TYPE_CHECKING, + Any, + Final, + Iterable, + Iterator, + Literal, + Sequence, + overload, +) import mistune from mistune.renderers.rst import RSTRenderer as _RSTRenderer @@ -363,21 +372,30 @@ def title(self) -> str: else: return "" + @overload + def get_python_type_representation( + self, + for_type_hints: bool = ..., + return_as_str: Literal[True] = ..., + additional_type_hints: list[str] | None = ..., + ) -> str: ... + @overload + def get_python_type_representation( + self, + for_type_hints: bool = ..., + return_as_str: Literal[False] = ..., + additional_type_hints: list[str] | None = ..., + ) -> list[str]: ... def get_python_type_representation( self, for_type_hints: bool = False, - altair_classes_prefix: str | None = None, return_as_str: bool = True, additional_type_hints: list[str] | None = None, ) -> str | list[str]: - # This is a list of all types which can be used for the current SchemaInfo. - # This includes Altair classes, standard Python types, etc. type_representations: list[str] = [] - TP_CHECK_ONLY = {"Parameter", "SchemaBase"} - """Most common annotations are include in `TYPE_CHECKING` block. - They do not require `core.` prefix, and this saves many lines of code. - - Eventually a more robust solution would apply to more types from `core`. + """ + All types which can be used for the current `SchemaInfo`. + Including `altair` classes, standard `python` types, etc. """ if self.title: @@ -385,7 +403,8 @@ def get_python_type_representation( # To keep type hints simple, we only use the SchemaBase class # as the type hint for all classes which inherit from it. class_names = ["SchemaBase"] - if self.title == "ExprRef": + if self.title in {"ExprRef", "ParameterExtent"}: + class_names.append("Parameter") # In these cases, a value parameter is also always accepted. # It would be quite complex to further differentiate # between a value and a selection parameter based on @@ -393,23 +412,7 @@ def get_python_type_representation( # try to check for the type of the Parameter.param attribute # but then we would need to write some overload signatures for # api.param). - class_names.append("Parameter") - if self.title == "ParameterExtent": - class_names.append("Parameter") - prefix = ( - "" if not altair_classes_prefix else altair_classes_prefix + "." - ) - # If there is no prefix, it might be that the class is defined - # in the same script and potentially after this line -> We use - # deferred type annotations using quotation marks. - if not prefix: - class_names = [f'"{n}"' for n in class_names] - else: - class_names = ( - n if n in TP_CHECK_ONLY else f"{prefix}{n}" for n in class_names - ) - # class_names = [f"{prefix}{n}" for n in class_names] type_representations.extend(class_names) else: # use RST syntax for generated sphinx docs @@ -427,26 +430,22 @@ def get_python_type_representation( tp_str = TypeAliasTracer.add_literal(self, spell_literal(it), replace=True) type_representations.append(tp_str) elif self.is_anyOf(): - type_representations.extend( - [ - s.get_python_type_representation( - for_type_hints=for_type_hints, - altair_classes_prefix=altair_classes_prefix, - return_as_str=False, - ) - for s in self.anyOf - ] + it = ( + s.get_python_type_representation( + for_type_hints=for_type_hints, return_as_str=False + ) + for s in self.anyOf ) + type_representations.extend(it) elif isinstance(self.type, list): options = [] subschema = SchemaInfo(dict(**self.schema)) for typ_ in self.type: subschema.schema["type"] = typ_ + # We always use title if possible for nested objects options.append( subschema.get_python_type_representation( - # We always use title if possible for nested objects - for_type_hints=for_type_hints, - altair_classes_prefix=altair_classes_prefix, + for_type_hints=for_type_hints ) ) type_representations.extend(options) @@ -469,14 +468,10 @@ def get_python_type_representation( # method. However, it is not entirely accurate as some sequences # such as e.g. a range are not supported by SchemaBase.to_dict but # this tradeoff seems worth it. - type_representations.append( - "Sequence[{}]".format( - self.child(self.items).get_python_type_representation( - for_type_hints=for_type_hints, - altair_classes_prefix=altair_classes_prefix, - ) - ) + s = self.child(self.items).get_python_type_representation( + for_type_hints=for_type_hints ) + type_representations.append(f"Sequence[{s}]") elif self.type in jsonschema_to_python_types: type_representations.append(jsonschema_to_python_types[self.type]) else: From 226038d5de7151d69157d500cc00417ca5bc8718 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:20:35 +0100 Subject: [PATCH 08/32] refactor: finish removing `altair_classes_prefix` --- tools/generate_schema_wrapper.py | 1 - tools/schemapi/codegen.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 58d2ee35e..cd8ca40ea 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -625,7 +625,6 @@ def generate_vegalite_channel_wrappers( "rootschema": schema, "encodingname": prop, "haspropsetters": True, - "altair_classes_prefix": "core", } if encoding_spec == "field": gen = FieldSchemaGenerator(classname, nodefault=[], **kwds) diff --git a/tools/schemapi/codegen.py b/tools/schemapi/codegen.py index 8217c6a6a..5681a1a37 100644 --- a/tools/schemapi/codegen.py +++ b/tools/schemapi/codegen.py @@ -105,7 +105,6 @@ class SchemaGenerator: rootschemarepr : CodeSnippet or object, optional An object whose repr will be used in the place of the explicit root schema. - altair_classes_prefix : string, optional **kwargs : dict Additional keywords for derived classes. """ @@ -141,7 +140,6 @@ def __init__( rootschemarepr: object | None = None, nodefault: list[str] | None = None, haspropsetters: bool = False, - altair_classes_prefix: str | None = None, **kwargs, ) -> None: self.classname = classname @@ -153,7 +151,6 @@ def __init__( self.nodefault = nodefault or () self.haspropsetters = haspropsetters self.kwargs = kwargs - self.altair_classes_prefix = altair_classes_prefix def subclasses(self) -> list[str]: """Return a list of subclass names, if any.""" From 77b101a7828ad3e89f90b68c2908c8baae871601 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 3 Aug 2024 21:40:48 +0100 Subject: [PATCH 09/32] feat: `_create_encode_signature()` -> `EncodingArtifacts` Refactored, moved comments into docs, parameterised globals --- tools/generate_schema_wrapper.py | 112 ++++++++++++++++++------------- 1 file changed, 66 insertions(+), 46 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index cd8ca40ea..3df0e3c9a 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -209,7 +209,7 @@ def configure_{prop}(self, *args, **kwargs) -> Self: ENCODE_METHOD: Final = ''' class _EncodingMixin: - def encode({encode_method_args}) -> Self: + def encode({method_args}) -> Self: """Map properties of the data to visual properties of the chart (see :class:`FacetedEncoding`) {docstring}""" # Compat prep for `infer_encoding_types` signature @@ -233,6 +233,13 @@ def encode({encode_method_args}) -> Self: return copy ''' +ENCODE_TYPED_DICT: Final = ''' +class _EncodeKwds(TypedDict, total=False): + """{docstring}""" + {channels} + +''' + # NOTE: Not yet reasonable to generalize `TypeAliasType`, `TypeVar` # Revisit if this starts to become more common TYPING_EXTRA: Final = ''' @@ -654,7 +661,7 @@ def generate_vegalite_channel_wrappers( imports = imports or [ "from __future__ import annotations\n", - "from typing import Any, overload, Sequence, List, Literal, Union, TYPE_CHECKING", + "from typing import Any, overload, Sequence, List, Literal, Union, TYPE_CHECKING, TypedDict", "from narwhals.dependencies import is_pandas_dataframe as _is_pandas_dataframe", "from altair.utils.schemapi import Undefined, with_property_setters", "from altair.utils import infer_encoding_types as _infer_encoding_types", @@ -674,11 +681,8 @@ def generate_vegalite_channel_wrappers( "\n" f"__all__ = {sorted(all_)}\n", CHANNEL_MIXINS, *class_defs, + *EncodingArtifacts(channel_infos, ENCODE_METHOD, ENCODE_TYPED_DICT), ] - - # Generate the type signature for the encode method - encode_signature = _create_encode_signature(channel_infos) - contents.append(encode_signature) return "\n".join(contents) @@ -859,48 +863,64 @@ def vegalite_main(skip_download: bool = False) -> None: ruff_write_lint_format_str(fp, contents) -def _create_encode_signature( - channel_infos: dict[str, ChannelInfo], -) -> str: - signature_args: list[str] = ["self", "*args: Any"] - docstring_parameters: list[str] = ["", "Parameters", "----------"] - - # TODO: refactor so extracting the `TypedDict` here makes sense - # - Want to avoid simply returning tuple[str, str] - typed_dict_args: list[str] = [] - - for channel, info in channel_infos.items(): - # `Map` stands for the return types of alt.datum, alt.value as well as - # the dictionary representation of an encoding channel class. See - # discussions in https://github.com/vega/altair/pull/3208 - # for more background. - union_types = ["str", info.field_class_name, "Map"] - it_rst_names = (rst_syntax_for_class(c) for c in info.all_names) - docstring_union_types = ["str", next(it_rst_names), "Dict"] - tp_inner: str = " | ".join(chain(union_types, info.non_field_names)) - if info.supports_arrays: - # We could be more specific about what types are accepted in the list - # but then the signatures would get rather long and less useful - # to a user when it shows up in their IDE. - docstring_union_types.append("List") - tp_inner = f"OneOrSeq[{tp_inner}]" - - signature_args.append(f"{channel}: Optional[{tp_inner}] = Undefined") - typed_dict_args.append(f"{channel}: {tp_inner}") - docstring_parameters.extend( - ( - f"{channel} : {', '.join(chain(docstring_union_types, it_rst_names))}", - f" {process_description(info.deep_description)}", +@dataclass +class EncodingArtifacts: + """ + Wrapper for what was previously `_create_encode_signature()`. + + Now also creates a `TypedDict` as part of https://github.com/pola-rs/polars/pull/17995 + """ + + channel_infos: dict[str, ChannelInfo] + fmt_method: str + fmt_typed_dict: str + + def __iter__(self) -> Iterator[str]: + """After construction, this allows for unpacking (`*`).""" + yield from self._gen_artifacts() + + def _gen_artifacts(self) -> None: + """ + Generate `.encode()` related things. + + Notes + ----- + - `Map`/`Dict` stands for the return types of `alt.(datum|value)`, and any encoding channel class. + - See discussions in https://github.com/vega/altair/pull/3208 + - We could be more specific about what types are accepted in the `List` + - but this translates poorly to an IDE + - `info.supports_arrays` + """ + signature_args: list[str] = ["self", "*args: Any"] + docstring_parameters: list[str] = ["", "Parameters", "----------"] + typed_dict_args: list[str] = [] + for channel, info in self.channel_infos.items(): + it = info.all_names + it_rst_names = (rst_syntax_for_class(c) for c in info.all_names) + + docstring_union_types = ["str", next(it_rst_names), "Dict"] + tp_inner: str = " | ".join(chain(("str", next(it), "Map"), it)) + if info.supports_arrays: + docstring_union_types.append("List") + tp_inner = f"OneOrSeq[{tp_inner}]" + signature_args.append(f"{channel}: Optional[{tp_inner}] = Undefined") + typed_dict_args.append(f"{channel}: {tp_inner}") + docstring_parameters.extend( + ( + f"{channel} : {', '.join(chain(docstring_union_types, it_rst_names))}", + f" {process_description(info.deep_description)}", + ) ) - ) - if len(docstring_parameters) > 1: docstring_parameters += [""] - docstring = indent_docstring( - docstring_parameters, indent_level=8, width=100, lstrip=False - ) - return ENCODE_METHOD.format( - encode_method_args=", ".join(signature_args), docstring=docstring - ) + doc = indent_docstring( + docstring_parameters, indent_level=8, width=100, lstrip=False + ) + yield self.fmt_method.format( + method_args=", ".join(signature_args), docstring=doc + ) + yield self.fmt_typed_dict.format( + channels="\n ".join(typed_dict_args), docstring="Placeholder (FIXME)" + ) def main() -> None: From 675bc4e9f54a7cfdd3b1b52600ca0aac38c2b164 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 3 Aug 2024 21:41:19 +0100 Subject: [PATCH 10/32] build: run `generate-schema-wrapper` --- altair/vegalite/v5/schema/channels.py | 47 ++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/altair/vegalite/v5/schema/channels.py b/altair/vegalite/v5/schema/channels.py index 21dc29578..4049e7f18 100644 --- a/altair/vegalite/v5/schema/channels.py +++ b/altair/vegalite/v5/schema/channels.py @@ -11,7 +11,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Literal, Sequence, overload +from typing import TYPE_CHECKING, Any, Literal, Sequence, TypedDict, overload from narwhals.dependencies import is_pandas_dataframe as _is_pandas_dataframe @@ -31189,3 +31189,48 @@ def encode( encoding.update(kwargs) copy.encoding = core.FacetedEncoding(**encoding) return copy + + +class _EncodeKwds(TypedDict, total=False): + """Placeholder (FIXME).""" + + angle: str | Angle | Map | AngleDatum | AngleValue + color: str | Color | Map | ColorDatum | ColorValue + column: str | Column | Map + description: str | Description | Map | DescriptionValue + detail: OneOrSeq[str | Detail | Map] + facet: str | Facet | Map + fill: str | Fill | Map | FillDatum | FillValue + fillOpacity: str | FillOpacity | Map | FillOpacityDatum | FillOpacityValue + href: str | Href | Map | HrefValue + key: str | Key | Map + latitude: str | Latitude | Map | LatitudeDatum + latitude2: str | Latitude2 | Map | Latitude2Datum | Latitude2Value + longitude: str | Longitude | Map | LongitudeDatum + longitude2: str | Longitude2 | Map | Longitude2Datum | Longitude2Value + opacity: str | Opacity | Map | OpacityDatum | OpacityValue + order: OneOrSeq[str | Order | Map | OrderValue] + radius: str | Radius | Map | RadiusDatum | RadiusValue + radius2: str | Radius2 | Map | Radius2Datum | Radius2Value + row: str | Row | Map + shape: str | Shape | Map | ShapeDatum | ShapeValue + size: str | Size | Map | SizeDatum | SizeValue + stroke: str | Stroke | Map | StrokeDatum | StrokeValue + strokeDash: str | StrokeDash | Map | StrokeDashDatum | StrokeDashValue + strokeOpacity: str | StrokeOpacity | Map | StrokeOpacityDatum | StrokeOpacityValue + strokeWidth: str | StrokeWidth | Map | StrokeWidthDatum | StrokeWidthValue + text: str | Text | Map | TextDatum | TextValue + theta: str | Theta | Map | ThetaDatum | ThetaValue + theta2: str | Theta2 | Map | Theta2Datum | Theta2Value + tooltip: OneOrSeq[str | Tooltip | Map | TooltipValue] + url: str | Url | Map | UrlValue + x: str | X | Map | XDatum | XValue + x2: str | X2 | Map | X2Datum | X2Value + xError: str | XError | Map | XErrorValue + xError2: str | XError2 | Map | XError2Value + xOffset: str | XOffset | Map | XOffsetDatum | XOffsetValue + y: str | Y | Map | YDatum | YValue + y2: str | Y2 | Map | Y2Datum | Y2Value + yError: str | YError | Map | YErrorValue + yError2: str | YError2 | Map | YError2Value + yOffset: str | YOffset | Map | YOffsetDatum | YOffsetValue From 51a84a5db3e991bbe41ff3673f4a1b8da0ca868d Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 3 Aug 2024 21:46:49 +0100 Subject: [PATCH 11/32] feat(typing): Provide a public export for `_EncodeKwds` --- altair/typing.py | 62 ++---------------------------------------------- 1 file changed, 2 insertions(+), 60 deletions(-) diff --git a/altair/typing.py b/altair/typing.py index 33bb38b0b..61eb32761 100644 --- a/altair/typing.py +++ b/altair/typing.py @@ -1,63 +1,5 @@ from __future__ import annotations -import sys -from typing import Any, Mapping, Union -from typing_extensions import TypedDict +__all__ = ["EncodeKwds"] -if sys.version_info >= (3, 10): - from typing import TypeAlias -else: - from typing_extensions import TypeAlias - -ChannelType: TypeAlias = Union[str, Mapping[str, Any], Any] - - -class EncodeKwds(TypedDict, total=False): - """ - Reference implementation from [comment code block](https://github.com/pola-rs/polars/pull/17995#issuecomment-2263609625). - - Aiming to define more specific `ChannelType`, without being exact. - - This would be generated alongside `tools.generate_schema_wrapper._create_encode_signature` - """ - - angle: ChannelType - color: ChannelType - column: ChannelType - description: ChannelType - detail: ChannelType | list[Any] - facet: ChannelType - fill: ChannelType - fillOpacity: ChannelType - href: ChannelType - key: ChannelType - latitude: ChannelType - latitude2: ChannelType - longitude: ChannelType - longitude2: ChannelType - opacity: ChannelType - order: ChannelType | list[Any] - radius: ChannelType - radius2: ChannelType - row: ChannelType - shape: ChannelType - size: ChannelType - stroke: ChannelType - strokeDash: ChannelType - strokeOpacity: ChannelType - strokeWidth: ChannelType - text: ChannelType - theta: ChannelType - theta2: ChannelType - tooltip: ChannelType | list[Any] - url: ChannelType - x: ChannelType - x2: ChannelType - xError: ChannelType - xError2: ChannelType - xOffset: ChannelType - y: ChannelType - y2: ChannelType - yError: ChannelType - yError2: ChannelType - yOffset: ChannelType +from altair.vegalite.v5.schema.channels import _EncodeKwds as EncodeKwds From 2b9ad2cd74cf7de66655304be57214acd9e83758 Mon Sep 17 00:00:00 2001 From: Stefan Binder Date: Sun, 4 Aug 2024 15:40:46 +0200 Subject: [PATCH 12/32] Add docstring to _EncodeKwds --- altair/vegalite/v5/schema/channels.py | 209 +++++++++++++++++++++++++- tools/generate_schema_wrapper.py | 28 +++- 2 files changed, 228 insertions(+), 9 deletions(-) diff --git a/altair/vegalite/v5/schema/channels.py b/altair/vegalite/v5/schema/channels.py index 4049e7f18..1b82ccb15 100644 --- a/altair/vegalite/v5/schema/channels.py +++ b/altair/vegalite/v5/schema/channels.py @@ -31192,7 +31192,214 @@ def encode( class _EncodeKwds(TypedDict, total=False): - """Placeholder (FIXME).""" + """ + Encoding channels map properties of the data to visual properties of the chart. + + Parameters + ---------- + angle : + Rotation angle of point and text marks. + color : + Color of the marks - either fill or stroke color based on the ``filled`` + property of mark definition. By default, ``color`` represents fill color for + ``"area"``, ``"bar"``, ``"tick"``, ``"text"``, ``"trail"``, ``"circle"``, + and ``"square"`` / stroke color for ``"line"`` and ``"point"``. + + **Default value:** If undefined, the default color depends on `mark config + `__'s + ``color`` property. + + *Note:* 1) For fine-grained control over both fill and stroke colors of the + marks, please use the ``fill`` and ``stroke`` channels. The ``fill`` or + ``stroke`` encodings have higher precedence than ``color``, thus may + override the ``color`` encoding if conflicting encodings are specified. 2) + See the scale documentation for more information about customizing `color + scheme `__. + column : + A field definition for the horizontal facet of trellis plots. + description : + A text description of this mark for ARIA accessibility (SVG output only). + For SVG output the ``"aria-label"`` attribute will be set to this + description. + detail : + Additional levels of detail for grouping data in aggregate views and in + line, trail, and area marks without mapping data to a specific visual + channel. + facet : + A field definition for the (flexible) facet of trellis plots. + + If either ``row`` or ``column`` is specified, this channel will be ignored. + fill : + Fill color of the marks. **Default value:** If undefined, the default color + depends on `mark config + `__'s + ``color`` property. + + *Note:* The ``fill`` encoding has higher precedence than ``color``, thus may + override the ``color`` encoding if conflicting encodings are specified. + fillOpacity : + Fill opacity of the marks. + + **Default value:** If undefined, the default opacity depends on `mark config + `__'s + ``fillOpacity`` property. + href : + A URL to load upon mouse click. + key : + A data field to use as a unique key for data binding. When a visualization's + data is updated, the key value will be used to match data elements to + existing mark instances. Use a key channel to enable object constancy for + transitions over dynamic data. + latitude : + Latitude position of geographically projected marks. + latitude2 : + Latitude-2 position for geographically projected ranged ``"area"``, + ``"bar"``, ``"rect"``, and ``"rule"``. + longitude : + Longitude position of geographically projected marks. + longitude2 : + Longitude-2 position for geographically projected ranged ``"area"``, + ``"bar"``, ``"rect"``, and ``"rule"``. + opacity : + Opacity of the marks. + + **Default value:** If undefined, the default opacity depends on `mark config + `__'s + ``opacity`` property. + order : + Order of the marks. + + * For stacked marks, this ``order`` channel encodes `stack order + `__. + * For line and trail marks, this ``order`` channel encodes order of data + points in the lines. This can be useful for creating `a connected + scatterplot + `__. + Setting ``order`` to ``{"value": null}`` makes the line marks use the + original order in the data sources. + * Otherwise, this ``order`` channel encodes layer order of the marks. + + **Note**: In aggregate plots, ``order`` field should be ``aggregate``d to + avoid creating additional aggregation grouping. + radius : + The outer radius in pixels of arc marks. + radius2 : + The inner radius in pixels of arc marks. + row : + A field definition for the vertical facet of trellis plots. + shape : + Shape of the mark. + + 1. For ``point`` marks the supported values include: - plotting shapes: + ``"circle"``, ``"square"``, ``"cross"``, ``"diamond"``, ``"triangle-up"``, + ``"triangle-down"``, ``"triangle-right"``, or ``"triangle-left"``. - the + line symbol ``"stroke"`` - centered directional shapes ``"arrow"``, + ``"wedge"``, or ``"triangle"`` - a custom `SVG path string + `__ (For + correct sizing, custom shape paths should be defined within a square + bounding box with coordinates ranging from -1 to 1 along both the x and y + dimensions.) + + 2. For ``geoshape`` marks it should be a field definition of the geojson + data + + **Default value:** If undefined, the default shape depends on `mark config + `__'s + ``shape`` property. (``"circle"`` if unset.) + size : + Size of the mark. + + * For ``"point"``, ``"square"`` and ``"circle"``, - the symbol size, or + pixel area of the mark. + * For ``"bar"`` and ``"tick"`` - the bar and tick's size. + * For ``"text"`` - the text's font size. + * Size is unsupported for ``"line"``, ``"area"``, and ``"rect"``. (Use + ``"trail"`` instead of line with varying size) + stroke : + Stroke color of the marks. **Default value:** If undefined, the default + color depends on `mark config + `__'s + ``color`` property. + + *Note:* The ``stroke`` encoding has higher precedence than ``color``, thus + may override the ``color`` encoding if conflicting encodings are specified. + strokeDash : + Stroke dash of the marks. + + **Default value:** ``[1,0]`` (No dash). + strokeOpacity : + Stroke opacity of the marks. + + **Default value:** If undefined, the default opacity depends on `mark config + `__'s + ``strokeOpacity`` property. + strokeWidth : + Stroke width of the marks. + + **Default value:** If undefined, the default stroke width depends on `mark + config `__'s + ``strokeWidth`` property. + text : + Text of the ``text`` mark. + theta : + * For arc marks, the arc length in radians if theta2 is not specified, + otherwise the start arc angle. (A value of 0 indicates up or “north”, + increasing values proceed clockwise.) + + * For text marks, polar coordinate angle in radians. + theta2 : + The end angle of arc marks in radians. A value of 0 indicates up or “north”, + increasing values proceed clockwise. + tooltip : + The tooltip text to show upon mouse hover. Specifying ``tooltip`` encoding + overrides `the tooltip property in the mark definition + `__. + + See the `tooltip `__ + documentation for a detailed discussion about tooltip in Vega-Lite. + url : + The URL of an image mark. + x : + X coordinates of the marks, or width of horizontal ``"bar"`` and ``"area"`` + without specified ``x2`` or ``width``. + + The ``value`` of this channel can be a number or a string ``"width"`` for + the width of the plot. + x2 : + X2 coordinates for ranged ``"area"``, ``"bar"``, ``"rect"``, and + ``"rule"``. + + The ``value`` of this channel can be a number or a string ``"width"`` for + the width of the plot. + xError : + Error value of x coordinates for error specified ``"errorbar"`` and + ``"errorband"``. + xError2 : + Secondary error value of x coordinates for error specified ``"errorbar"`` + and ``"errorband"``. + xOffset : + Offset of x-position of the marks + y : + Y coordinates of the marks, or height of vertical ``"bar"`` and ``"area"`` + without specified ``y2`` or ``height``. + + The ``value`` of this channel can be a number or a string ``"height"`` for + the height of the plot. + y2 : + Y2 coordinates for ranged ``"area"``, ``"bar"``, ``"rect"``, and + ``"rule"``. + + The ``value`` of this channel can be a number or a string ``"height"`` for + the height of the plot. + yError : + Error value of y coordinates for error specified ``"errorbar"`` and + ``"errorband"``. + yError2 : + Secondary error value of y coordinates for error specified ``"errorbar"`` + and ``"errorband"``. + yOffset : + Offset of y-position of the marks + """ angle: str | Angle | Map | AngleDatum | AngleValue color: str | Color | Map | ColorDatum | ColorValue diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 3df0e3c9a..9123c942a 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -235,7 +235,8 @@ def encode({method_args}) -> Self: ENCODE_TYPED_DICT: Final = ''' class _EncodeKwds(TypedDict, total=False): - """{docstring}""" + """Encoding channels map properties of the data to visual properties of the chart. + {docstring}""" {channels} ''' @@ -892,7 +893,8 @@ def _gen_artifacts(self) -> None: - `info.supports_arrays` """ signature_args: list[str] = ["self", "*args: Any"] - docstring_parameters: list[str] = ["", "Parameters", "----------"] + signature_docstring_parameters: list[str] = ["", "Parameters", "----------"] + typed_dict_docstring_parameters: list[str] = ["", "Parameters", "----------"] typed_dict_args: list[str] = [] for channel, info in self.channel_infos.items(): it = info.all_names @@ -905,21 +907,31 @@ def _gen_artifacts(self) -> None: tp_inner = f"OneOrSeq[{tp_inner}]" signature_args.append(f"{channel}: Optional[{tp_inner}] = Undefined") typed_dict_args.append(f"{channel}: {tp_inner}") - docstring_parameters.extend( + signature_docstring_parameters.extend( ( f"{channel} : {', '.join(chain(docstring_union_types, it_rst_names))}", f" {process_description(info.deep_description)}", ) ) - docstring_parameters += [""] - doc = indent_docstring( - docstring_parameters, indent_level=8, width=100, lstrip=False + typed_dict_docstring_parameters.extend( + ( + f"{channel} :", + f" {process_description(info.deep_description)}", + ) + ) + signature_docstring_parameters += [""] + typed_dict_docstring_parameters += [""] + signature_doc = indent_docstring( + signature_docstring_parameters, indent_level=8, width=100, lstrip=False + ) + typed_dict_doc = indent_docstring( + typed_dict_docstring_parameters, indent_level=8, width=100, lstrip=False ) yield self.fmt_method.format( - method_args=", ".join(signature_args), docstring=doc + method_args=", ".join(signature_args), docstring=signature_doc ) yield self.fmt_typed_dict.format( - channels="\n ".join(typed_dict_args), docstring="Placeholder (FIXME)" + channels="\n ".join(typed_dict_args), docstring=typed_dict_doc ) From 79f317dcaa8b74fff6891530dab7b185fec489b5 Mon Sep 17 00:00:00 2001 From: Stefan Binder Date: Sun, 4 Aug 2024 15:47:27 +0200 Subject: [PATCH 13/32] Rewrite EncodeArtifacts dataclass as a function --- tools/generate_schema_wrapper.py | 124 +++++++++++++++---------------- 1 file changed, 58 insertions(+), 66 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 9123c942a..37d01d224 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -290,7 +290,9 @@ def process_description(description: str) -> str: # Some entries in the Vega-Lite schema miss the second occurence of '__' description = description.replace("__Default value: ", "__Default value:__ ") # Fixing ambiguous unicode, RUF001 produces RUF002 in docs - description = description.replace("’", "'") # noqa: RUF001 [RIGHT SINGLE QUOTATION MARK] + description = description.replace( + "’", "'" + ) # noqa: RUF001 [RIGHT SINGLE QUOTATION MARK] description = description.replace("–", "-") # noqa: RUF001 [EN DASH] description = description.replace(" ", " ") # noqa: RUF001 [NO-BREAK SPACE] return description.strip() @@ -682,7 +684,7 @@ def generate_vegalite_channel_wrappers( "\n" f"__all__ = {sorted(all_)}\n", CHANNEL_MIXINS, *class_defs, - *EncodingArtifacts(channel_infos, ENCODE_METHOD, ENCODE_TYPED_DICT), + *generate_encoding_artifacts(channel_infos, ENCODE_METHOD, ENCODE_TYPED_DICT), ] return "\n".join(contents) @@ -864,75 +866,65 @@ def vegalite_main(skip_download: bool = False) -> None: ruff_write_lint_format_str(fp, contents) -@dataclass -class EncodingArtifacts: +def generate_encoding_artifacts( + channel_infos: dict[str, ChannelInfo], fmt_method: str, fmt_typed_dict: str +) -> tuple[str, str]: """ - Wrapper for what was previously `_create_encode_signature()`. - - Now also creates a `TypedDict` as part of https://github.com/pola-rs/polars/pull/17995 + Generate `.encode()` related things. + + This was previously `_create_encode_signature()`. Now also creates a `TypedDict` as + part of https://github.com/pola-rs/polars/pull/17995. + + Notes + ----- + - `Map`/`Dict` stands for the return types of `alt.(datum|value)`, and any encoding channel class. + - See discussions in https://github.com/vega/altair/pull/3208 + - We could be more specific about what types are accepted in the `List` + - but this translates poorly to an IDE + - `info.supports_arrays` """ - - channel_infos: dict[str, ChannelInfo] - fmt_method: str - fmt_typed_dict: str - - def __iter__(self) -> Iterator[str]: - """After construction, this allows for unpacking (`*`).""" - yield from self._gen_artifacts() - - def _gen_artifacts(self) -> None: - """ - Generate `.encode()` related things. - - Notes - ----- - - `Map`/`Dict` stands for the return types of `alt.(datum|value)`, and any encoding channel class. - - See discussions in https://github.com/vega/altair/pull/3208 - - We could be more specific about what types are accepted in the `List` - - but this translates poorly to an IDE - - `info.supports_arrays` - """ - signature_args: list[str] = ["self", "*args: Any"] - signature_docstring_parameters: list[str] = ["", "Parameters", "----------"] - typed_dict_docstring_parameters: list[str] = ["", "Parameters", "----------"] - typed_dict_args: list[str] = [] - for channel, info in self.channel_infos.items(): - it = info.all_names - it_rst_names = (rst_syntax_for_class(c) for c in info.all_names) - - docstring_union_types = ["str", next(it_rst_names), "Dict"] - tp_inner: str = " | ".join(chain(("str", next(it), "Map"), it)) - if info.supports_arrays: - docstring_union_types.append("List") - tp_inner = f"OneOrSeq[{tp_inner}]" - signature_args.append(f"{channel}: Optional[{tp_inner}] = Undefined") - typed_dict_args.append(f"{channel}: {tp_inner}") - signature_docstring_parameters.extend( - ( - f"{channel} : {', '.join(chain(docstring_union_types, it_rst_names))}", - f" {process_description(info.deep_description)}", - ) + signature_args: list[str] = ["self", "*args: Any"] + signature_docstring_parameters: list[str] = ["", "Parameters", "----------"] + typed_dict_docstring_parameters: list[str] = ["", "Parameters", "----------"] + typed_dict_args: list[str] = [] + for channel, info in channel_infos.items(): + it = info.all_names + it_rst_names = (rst_syntax_for_class(c) for c in info.all_names) + + docstring_union_types = ["str", next(it_rst_names), "Dict"] + tp_inner: str = " | ".join(chain(("str", next(it), "Map"), it)) + if info.supports_arrays: + docstring_union_types.append("List") + tp_inner = f"OneOrSeq[{tp_inner}]" + signature_args.append(f"{channel}: Optional[{tp_inner}] = Undefined") + typed_dict_args.append(f"{channel}: {tp_inner}") + signature_docstring_parameters.extend( + ( + f"{channel} : {', '.join(chain(docstring_union_types, it_rst_names))}", + f" {process_description(info.deep_description)}", ) - typed_dict_docstring_parameters.extend( - ( - f"{channel} :", - f" {process_description(info.deep_description)}", - ) - ) - signature_docstring_parameters += [""] - typed_dict_docstring_parameters += [""] - signature_doc = indent_docstring( - signature_docstring_parameters, indent_level=8, width=100, lstrip=False - ) - typed_dict_doc = indent_docstring( - typed_dict_docstring_parameters, indent_level=8, width=100, lstrip=False ) - yield self.fmt_method.format( - method_args=", ".join(signature_args), docstring=signature_doc - ) - yield self.fmt_typed_dict.format( - channels="\n ".join(typed_dict_args), docstring=typed_dict_doc + typed_dict_docstring_parameters.extend( + ( + f"{channel} :", + f" {process_description(info.deep_description)}", + ) ) + signature_docstring_parameters += [""] + typed_dict_docstring_parameters += [""] + signature_doc = indent_docstring( + signature_docstring_parameters, indent_level=8, width=100, lstrip=False + ) + typed_dict_doc = indent_docstring( + typed_dict_docstring_parameters, indent_level=8, width=100, lstrip=False + ) + encode_method = fmt_method.format( + method_args=", ".join(signature_args), docstring=signature_doc + ) + encode_typed_dict = fmt_typed_dict.format( + channels="\n ".join(typed_dict_args), docstring=typed_dict_doc + ) + return encode_method, encode_typed_dict def main() -> None: From 1eb466d3de82fa06db42f59dca2f74f706174aac Mon Sep 17 00:00:00 2001 From: Stefan Binder Date: Sun, 4 Aug 2024 15:52:41 +0200 Subject: [PATCH 14/32] Fix ruff issue due to old local ruff version --- tools/generate_schema_wrapper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 37d01d224..9063ced53 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -291,8 +291,9 @@ def process_description(description: str) -> str: description = description.replace("__Default value: ", "__Default value:__ ") # Fixing ambiguous unicode, RUF001 produces RUF002 in docs description = description.replace( - "’", "'" - ) # noqa: RUF001 [RIGHT SINGLE QUOTATION MARK] + "’", # noqa: RUF001 [RIGHT SINGLE QUOTATION MARK] + "'", + ) description = description.replace("–", "-") # noqa: RUF001 [EN DASH] description = description.replace(" ", " ") # noqa: RUF001 [NO-BREAK SPACE] return description.strip() From 0287eba43a18b8a4b4a4c2963e50c92f23726bc5 Mon Sep 17 00:00:00 2001 From: Stefan Binder Date: Sun, 4 Aug 2024 16:05:37 +0200 Subject: [PATCH 15/32] Change generate_encoding_artifacts to an iterator --- tools/generate_schema_wrapper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 9063ced53..3949225d5 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -869,7 +869,7 @@ def vegalite_main(skip_download: bool = False) -> None: def generate_encoding_artifacts( channel_infos: dict[str, ChannelInfo], fmt_method: str, fmt_typed_dict: str -) -> tuple[str, str]: +) -> Iterator[str]: """ Generate `.encode()` related things. @@ -925,7 +925,8 @@ def generate_encoding_artifacts( encode_typed_dict = fmt_typed_dict.format( channels="\n ".join(typed_dict_args), docstring=typed_dict_doc ) - return encode_method, encode_typed_dict + artifacts = [encode_method, encode_typed_dict] + yield from artifacts def main() -> None: From bac1f67c97760c00d13d2b3100a468c484a36946 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sun, 4 Aug 2024 16:55:57 +0100 Subject: [PATCH 16/32] docs: run `generate-schema-wrapper` with `indent_level=4` I prefer the original, but this is to clarify https://github.com/vega/altair/pull/3515#discussion_r1703215491 --- altair/vegalite/v5/schema/channels.py | 180 ++++++++++++-------------- tools/generate_schema_wrapper.py | 2 +- 2 files changed, 86 insertions(+), 96 deletions(-) diff --git a/altair/vegalite/v5/schema/channels.py b/altair/vegalite/v5/schema/channels.py index 1b82ccb15..4416941ba 100644 --- a/altair/vegalite/v5/schema/channels.py +++ b/altair/vegalite/v5/schema/channels.py @@ -31200,39 +31200,36 @@ class _EncodeKwds(TypedDict, total=False): angle : Rotation angle of point and text marks. color : - Color of the marks - either fill or stroke color based on the ``filled`` - property of mark definition. By default, ``color`` represents fill color for - ``"area"``, ``"bar"``, ``"tick"``, ``"text"``, ``"trail"``, ``"circle"``, - and ``"square"`` / stroke color for ``"line"`` and ``"point"``. + Color of the marks - either fill or stroke color based on the ``filled`` property + of mark definition. By default, ``color`` represents fill color for ``"area"``, + ``"bar"``, ``"tick"``, ``"text"``, ``"trail"``, ``"circle"``, and ``"square"`` / + stroke color for ``"line"`` and ``"point"``. **Default value:** If undefined, the default color depends on `mark config - `__'s - ``color`` property. - - *Note:* 1) For fine-grained control over both fill and stroke colors of the - marks, please use the ``fill`` and ``stroke`` channels. The ``fill`` or - ``stroke`` encodings have higher precedence than ``color``, thus may - override the ``color`` encoding if conflicting encodings are specified. 2) - See the scale documentation for more information about customizing `color - scheme `__. + `__'s ``color`` + property. + + *Note:* 1) For fine-grained control over both fill and stroke colors of the marks, + please use the ``fill`` and ``stroke`` channels. The ``fill`` or ``stroke`` + encodings have higher precedence than ``color``, thus may override the ``color`` + encoding if conflicting encodings are specified. 2) See the scale documentation for + more information about customizing `color scheme + `__. column : A field definition for the horizontal facet of trellis plots. description : - A text description of this mark for ARIA accessibility (SVG output only). - For SVG output the ``"aria-label"`` attribute will be set to this - description. + A text description of this mark for ARIA accessibility (SVG output only). For SVG + output the ``"aria-label"`` attribute will be set to this description. detail : - Additional levels of detail for grouping data in aggregate views and in - line, trail, and area marks without mapping data to a specific visual - channel. + Additional levels of detail for grouping data in aggregate views and in line, trail, + and area marks without mapping data to a specific visual channel. facet : A field definition for the (flexible) facet of trellis plots. If either ``row`` or ``column`` is specified, this channel will be ignored. fill : - Fill color of the marks. **Default value:** If undefined, the default color - depends on `mark config - `__'s + Fill color of the marks. **Default value:** If undefined, the default color depends + on `mark config `__'s ``color`` property. *Note:* The ``fill`` encoding has higher precedence than ``color``, thus may @@ -31241,46 +31238,45 @@ class _EncodeKwds(TypedDict, total=False): Fill opacity of the marks. **Default value:** If undefined, the default opacity depends on `mark config - `__'s - ``fillOpacity`` property. + `__'s ``fillOpacity`` + property. href : A URL to load upon mouse click. key : - A data field to use as a unique key for data binding. When a visualization's - data is updated, the key value will be used to match data elements to - existing mark instances. Use a key channel to enable object constancy for - transitions over dynamic data. + A data field to use as a unique key for data binding. When a visualization's data is + updated, the key value will be used to match data elements to existing mark + instances. Use a key channel to enable object constancy for transitions over dynamic + data. latitude : Latitude position of geographically projected marks. latitude2 : - Latitude-2 position for geographically projected ranged ``"area"``, - ``"bar"``, ``"rect"``, and ``"rule"``. + Latitude-2 position for geographically projected ranged ``"area"``, ``"bar"``, + ``"rect"``, and ``"rule"``. longitude : Longitude position of geographically projected marks. longitude2 : - Longitude-2 position for geographically projected ranged ``"area"``, - ``"bar"``, ``"rect"``, and ``"rule"``. + Longitude-2 position for geographically projected ranged ``"area"``, ``"bar"``, + ``"rect"``, and ``"rule"``. opacity : Opacity of the marks. **Default value:** If undefined, the default opacity depends on `mark config - `__'s - ``opacity`` property. + `__'s ``opacity`` + property. order : Order of the marks. * For stacked marks, this ``order`` channel encodes `stack order `__. - * For line and trail marks, this ``order`` channel encodes order of data - points in the lines. This can be useful for creating `a connected - scatterplot - `__. - Setting ``order`` to ``{"value": null}`` makes the line marks use the - original order in the data sources. + * For line and trail marks, this ``order`` channel encodes order of data points in + the lines. This can be useful for creating `a connected scatterplot + `__. Setting + ``order`` to ``{"value": null}`` makes the line marks use the original order in + the data sources. * Otherwise, this ``order`` channel encodes layer order of the marks. - **Note**: In aggregate plots, ``order`` field should be ``aggregate``d to - avoid creating additional aggregation grouping. + **Note**: In aggregate plots, ``order`` field should be ``aggregate``d to avoid + creating additional aggregation grouping. radius : The outer radius in pixels of arc marks. radius2 : @@ -31292,37 +31288,35 @@ class _EncodeKwds(TypedDict, total=False): 1. For ``point`` marks the supported values include: - plotting shapes: ``"circle"``, ``"square"``, ``"cross"``, ``"diamond"``, ``"triangle-up"``, - ``"triangle-down"``, ``"triangle-right"``, or ``"triangle-left"``. - the - line symbol ``"stroke"`` - centered directional shapes ``"arrow"``, - ``"wedge"``, or ``"triangle"`` - a custom `SVG path string - `__ (For - correct sizing, custom shape paths should be defined within a square - bounding box with coordinates ranging from -1 to 1 along both the x and y - dimensions.) + ``"triangle-down"``, ``"triangle-right"``, or ``"triangle-left"``. - the line + symbol ``"stroke"`` - centered directional shapes ``"arrow"``, ``"wedge"``, or + ``"triangle"`` - a custom `SVG path string + `__ (For correct + sizing, custom shape paths should be defined within a square bounding box with + coordinates ranging from -1 to 1 along both the x and y dimensions.) - 2. For ``geoshape`` marks it should be a field definition of the geojson - data + 2. For ``geoshape`` marks it should be a field definition of the geojson data **Default value:** If undefined, the default shape depends on `mark config - `__'s - ``shape`` property. (``"circle"`` if unset.) + `__'s ``shape`` + property. (``"circle"`` if unset.) size : Size of the mark. - * For ``"point"``, ``"square"`` and ``"circle"``, - the symbol size, or - pixel area of the mark. + * For ``"point"``, ``"square"`` and ``"circle"``, - the symbol size, or pixel area + of the mark. * For ``"bar"`` and ``"tick"`` - the bar and tick's size. * For ``"text"`` - the text's font size. - * Size is unsupported for ``"line"``, ``"area"``, and ``"rect"``. (Use - ``"trail"`` instead of line with varying size) + * Size is unsupported for ``"line"``, ``"area"``, and ``"rect"``. (Use ``"trail"`` + instead of line with varying size) stroke : - Stroke color of the marks. **Default value:** If undefined, the default - color depends on `mark config - `__'s - ``color`` property. + Stroke color of the marks. **Default value:** If undefined, the default color + depends on `mark config + `__'s ``color`` + property. - *Note:* The ``stroke`` encoding has higher precedence than ``color``, thus - may override the ``color`` encoding if conflicting encodings are specified. + *Note:* The ``stroke`` encoding has higher precedence than ``color``, thus may + override the ``color`` encoding if conflicting encodings are specified. strokeDash : Stroke dash of the marks. @@ -31336,23 +31330,23 @@ class _EncodeKwds(TypedDict, total=False): strokeWidth : Stroke width of the marks. - **Default value:** If undefined, the default stroke width depends on `mark - config `__'s - ``strokeWidth`` property. + **Default value:** If undefined, the default stroke width depends on `mark config + `__'s ``strokeWidth`` + property. text : Text of the ``text`` mark. theta : - * For arc marks, the arc length in radians if theta2 is not specified, - otherwise the start arc angle. (A value of 0 indicates up or “north”, - increasing values proceed clockwise.) + * For arc marks, the arc length in radians if theta2 is not specified, otherwise the + start arc angle. (A value of 0 indicates up or “north”, increasing values proceed + clockwise.) * For text marks, polar coordinate angle in radians. theta2 : The end angle of arc marks in radians. A value of 0 indicates up or “north”, increasing values proceed clockwise. tooltip : - The tooltip text to show upon mouse hover. Specifying ``tooltip`` encoding - overrides `the tooltip property in the mark definition + The tooltip text to show upon mouse hover. Specifying ``tooltip`` encoding overrides + `the tooltip property in the mark definition `__. See the `tooltip `__ @@ -31360,43 +31354,39 @@ class _EncodeKwds(TypedDict, total=False): url : The URL of an image mark. x : - X coordinates of the marks, or width of horizontal ``"bar"`` and ``"area"`` - without specified ``x2`` or ``width``. + X coordinates of the marks, or width of horizontal ``"bar"`` and ``"area"`` without + specified ``x2`` or ``width``. - The ``value`` of this channel can be a number or a string ``"width"`` for - the width of the plot. + The ``value`` of this channel can be a number or a string ``"width"`` for the width + of the plot. x2 : - X2 coordinates for ranged ``"area"``, ``"bar"``, ``"rect"``, and - ``"rule"``. + X2 coordinates for ranged ``"area"``, ``"bar"``, ``"rect"``, and ``"rule"``. - The ``value`` of this channel can be a number or a string ``"width"`` for - the width of the plot. + The ``value`` of this channel can be a number or a string ``"width"`` for the width + of the plot. xError : - Error value of x coordinates for error specified ``"errorbar"`` and - ``"errorband"``. + Error value of x coordinates for error specified ``"errorbar"`` and ``"errorband"``. xError2 : - Secondary error value of x coordinates for error specified ``"errorbar"`` - and ``"errorband"``. + Secondary error value of x coordinates for error specified ``"errorbar"`` and + ``"errorband"``. xOffset : Offset of x-position of the marks y : - Y coordinates of the marks, or height of vertical ``"bar"`` and ``"area"`` - without specified ``y2`` or ``height``. + Y coordinates of the marks, or height of vertical ``"bar"`` and ``"area"`` without + specified ``y2`` or ``height``. - The ``value`` of this channel can be a number or a string ``"height"`` for - the height of the plot. + The ``value`` of this channel can be a number or a string ``"height"`` for the + height of the plot. y2 : - Y2 coordinates for ranged ``"area"``, ``"bar"``, ``"rect"``, and - ``"rule"``. + Y2 coordinates for ranged ``"area"``, ``"bar"``, ``"rect"``, and ``"rule"``. - The ``value`` of this channel can be a number or a string ``"height"`` for - the height of the plot. + The ``value`` of this channel can be a number or a string ``"height"`` for the + height of the plot. yError : - Error value of y coordinates for error specified ``"errorbar"`` and - ``"errorband"``. + Error value of y coordinates for error specified ``"errorbar"`` and ``"errorband"``. yError2 : - Secondary error value of y coordinates for error specified ``"errorbar"`` - and ``"errorband"``. + Secondary error value of y coordinates for error specified ``"errorbar"`` and + ``"errorband"``. yOffset : Offset of y-position of the marks """ diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 3949225d5..b1679602f 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -917,7 +917,7 @@ def generate_encoding_artifacts( signature_docstring_parameters, indent_level=8, width=100, lstrip=False ) typed_dict_doc = indent_docstring( - typed_dict_docstring_parameters, indent_level=8, width=100, lstrip=False + typed_dict_docstring_parameters, indent_level=4, width=100, lstrip=False ) encode_method = fmt_method.format( method_args=", ".join(signature_args), docstring=signature_doc From 341925067e846dd1522bdbed307508cf9d533390 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:25:44 +0100 Subject: [PATCH 17/32] feat(typing): Move `ChartType`, `is_chart_type` to `alt.typing` These aren't introduced until `5.4.0`, so this is keeps the top-level from growing +2 https://github.com/vega/altair/pull/3515#discussion_r1703165544 --- altair/__init__.py | 2 -- altair/typing.py | 5 ++++- altair/utils/_transformed_data.py | 2 +- altair/utils/schemapi.py | 2 +- altair/vegalite/v5/api.py | 2 -- tools/schemapi/schemapi.py | 2 +- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/altair/__init__.py b/altair/__init__.py index a1809ec5a..a659b2ef1 100644 --- a/altair/__init__.py +++ b/altair/__init__.py @@ -50,7 +50,6 @@ "ChainedWhen", "Chart", "ChartDataType", - "ChartType", "Color", "ColorDatum", "ColorDef", @@ -611,7 +610,6 @@ "expr", "graticule", "hconcat", - "is_chart_type", "jupyter", "layer", "limit_rows", diff --git a/altair/typing.py b/altair/typing.py index 61eb32761..74d6b1fd6 100644 --- a/altair/typing.py +++ b/altair/typing.py @@ -1,5 +1,8 @@ +"""Public types to ease integrating with `altair`.""" + from __future__ import annotations -__all__ = ["EncodeKwds"] +__all__ = ["ChartType", "EncodeKwds", "is_chart_type"] +from altair.vegalite.v5.api import ChartType, is_chart_type from altair.vegalite.v5.schema.channels import _EncodeKwds as EncodeKwds diff --git a/altair/utils/_transformed_data.py b/altair/utils/_transformed_data.py index 12fe11bf5..0bf33c9f7 100644 --- a/altair/utils/_transformed_data.py +++ b/altair/utils/_transformed_data.py @@ -31,8 +31,8 @@ from altair.utils.schemapi import Undefined if TYPE_CHECKING: + from altair.typing import ChartType 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]] diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index de196025b..0df55f5b3 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -44,7 +44,7 @@ from referencing import Registry - from altair import ChartType + from altair.typing import ChartType if sys.version_info >= (3, 13): from typing import TypeIs diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 5a754a9ad..0b3776541 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -145,7 +145,6 @@ "ChainedWhen", "Chart", "ChartDataType", - "ChartType", "ConcatChart", "DataType", "FacetChart", @@ -174,7 +173,6 @@ "condition", "graticule", "hconcat", - "is_chart_type", "layer", "mixins", "param", diff --git a/tools/schemapi/schemapi.py b/tools/schemapi/schemapi.py index 0ef56ccf7..4ac2860ef 100644 --- a/tools/schemapi/schemapi.py +++ b/tools/schemapi/schemapi.py @@ -42,7 +42,7 @@ from referencing import Registry - from altair import ChartType + from altair.typing import ChartType if sys.version_info >= (3, 13): from typing import TypeIs From d16ec34ffaebbf6d62a0569259711a426598f05e Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:06:22 +0100 Subject: [PATCH 18/32] revert(ruff): Restore original ('RUF001`) line 1eb466d3de82fa06db42f59dca2f74f706174aac --- tools/generate_schema_wrapper.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index b1679602f..3863ef8f9 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -290,10 +290,7 @@ def process_description(description: str) -> str: # Some entries in the Vega-Lite schema miss the second occurence of '__' description = description.replace("__Default value: ", "__Default value:__ ") # Fixing ambiguous unicode, RUF001 produces RUF002 in docs - description = description.replace( - "’", # noqa: RUF001 [RIGHT SINGLE QUOTATION MARK] - "'", - ) + description = description.replace("’", "'") # noqa: RUF001 [RIGHT SINGLE QUOTATION MARK] description = description.replace("–", "-") # noqa: RUF001 [EN DASH] description = description.replace(" ", " ") # noqa: RUF001 [NO-BREAK SPACE] return description.strip() From e9035282a96e0f5e9a58aa59114933331ee740b4 Mon Sep 17 00:00:00 2001 From: Stefan Binder Date: Mon, 5 Aug 2024 20:07:05 +0200 Subject: [PATCH 19/32] Add type aliases for each channel --- altair/vegalite/v5/schema/channels.py | 236 +++++++++++++++----------- tools/generate_schema_wrapper.py | 15 +- 2 files changed, 145 insertions(+), 106 deletions(-) diff --git a/altair/vegalite/v5/schema/channels.py b/altair/vegalite/v5/schema/channels.py index 4416941ba..496ef81b5 100644 --- a/altair/vegalite/v5/schema/channels.py +++ b/altair/vegalite/v5/schema/channels.py @@ -12,6 +12,7 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Literal, Sequence, TypedDict, overload +from typing_extensions import TypeAlias from narwhals.dependencies import is_pandas_dataframe as _is_pandas_dataframe @@ -20,6 +21,7 @@ from altair.utils.schemapi import Undefined, with_property_setters from . import core +from ._typing import * # noqa: F403 # ruff: noqa: F405 if TYPE_CHECKING: @@ -28,8 +30,6 @@ from altair import Parameter, SchemaBase from altair.utils.schemapi import Optional - from ._typing import * # noqa: F403 - __all__ = [ "X2", @@ -30897,70 +30897,102 @@ def __init__(self, value, **kwds): super().__init__(value=value, **kwds) +ChannelAngle: TypeAlias = str | Angle | Map | AngleDatum | AngleValue +ChannelColor: TypeAlias = str | Color | Map | ColorDatum | ColorValue +ChannelColumn: TypeAlias = str | Column | Map +ChannelDescription: TypeAlias = str | Description | Map | DescriptionValue +ChannelDetail: TypeAlias = OneOrSeq[str | Detail | Map] +ChannelFacet: TypeAlias = str | Facet | Map +ChannelFill: TypeAlias = str | Fill | Map | FillDatum | FillValue +ChannelFillOpacity: TypeAlias = ( + str | FillOpacity | Map | FillOpacityDatum | FillOpacityValue +) +ChannelHref: TypeAlias = str | Href | Map | HrefValue +ChannelKey: TypeAlias = str | Key | Map +ChannelLatitude: TypeAlias = str | Latitude | Map | LatitudeDatum +ChannelLatitude2: TypeAlias = str | Latitude2 | Map | Latitude2Datum | Latitude2Value +ChannelLongitude: TypeAlias = str | Longitude | Map | LongitudeDatum +ChannelLongitude2: TypeAlias = ( + str | Longitude2 | Map | Longitude2Datum | Longitude2Value +) +ChannelOpacity: TypeAlias = str | Opacity | Map | OpacityDatum | OpacityValue +ChannelOrder: TypeAlias = OneOrSeq[str | Order | Map | OrderValue] +ChannelRadius: TypeAlias = str | Radius | Map | RadiusDatum | RadiusValue +ChannelRadius2: TypeAlias = str | Radius2 | Map | Radius2Datum | Radius2Value +ChannelRow: TypeAlias = str | Row | Map +ChannelShape: TypeAlias = str | Shape | Map | ShapeDatum | ShapeValue +ChannelSize: TypeAlias = str | Size | Map | SizeDatum | SizeValue +ChannelStroke: TypeAlias = str | Stroke | Map | StrokeDatum | StrokeValue +ChannelStrokeDash: TypeAlias = ( + str | StrokeDash | Map | StrokeDashDatum | StrokeDashValue +) +ChannelStrokeOpacity: TypeAlias = ( + str | StrokeOpacity | Map | StrokeOpacityDatum | StrokeOpacityValue +) +ChannelStrokeWidth: TypeAlias = ( + str | StrokeWidth | Map | StrokeWidthDatum | StrokeWidthValue +) +ChannelText: TypeAlias = str | Text | Map | TextDatum | TextValue +ChannelTheta: TypeAlias = str | Theta | Map | ThetaDatum | ThetaValue +ChannelTheta2: TypeAlias = str | Theta2 | Map | Theta2Datum | Theta2Value +ChannelTooltip: TypeAlias = OneOrSeq[str | Tooltip | Map | TooltipValue] +ChannelUrl: TypeAlias = str | Url | Map | UrlValue +ChannelX: TypeAlias = str | X | Map | XDatum | XValue +ChannelX2: TypeAlias = str | X2 | Map | X2Datum | X2Value +ChannelXError: TypeAlias = str | XError | Map | XErrorValue +ChannelXError2: TypeAlias = str | XError2 | Map | XError2Value +ChannelXOffset: TypeAlias = str | XOffset | Map | XOffsetDatum | XOffsetValue +ChannelY: TypeAlias = str | Y | Map | YDatum | YValue +ChannelY2: TypeAlias = str | Y2 | Map | Y2Datum | Y2Value +ChannelYError: TypeAlias = str | YError | Map | YErrorValue +ChannelYError2: TypeAlias = str | YError2 | Map | YError2Value +ChannelYOffset: TypeAlias = str | YOffset | Map | YOffsetDatum | YOffsetValue + + class _EncodingMixin: def encode( self, *args: Any, - angle: Optional[str | Angle | Map | AngleDatum | AngleValue] = Undefined, - color: Optional[str | Color | Map | ColorDatum | ColorValue] = Undefined, - column: Optional[str | Column | Map] = Undefined, - description: Optional[str | Description | Map | DescriptionValue] = Undefined, - detail: Optional[OneOrSeq[str | Detail | Map]] = Undefined, - facet: Optional[str | Facet | Map] = Undefined, - fill: Optional[str | Fill | Map | FillDatum | FillValue] = Undefined, - fillOpacity: Optional[ - str | FillOpacity | Map | FillOpacityDatum | FillOpacityValue - ] = Undefined, - href: Optional[str | Href | Map | HrefValue] = Undefined, - key: Optional[str | Key | Map] = Undefined, - latitude: Optional[str | Latitude | Map | LatitudeDatum] = Undefined, - latitude2: Optional[ - str | Latitude2 | Map | Latitude2Datum | Latitude2Value - ] = Undefined, - longitude: Optional[str | Longitude | Map | LongitudeDatum] = Undefined, - longitude2: Optional[ - str | Longitude2 | Map | Longitude2Datum | Longitude2Value - ] = Undefined, - opacity: Optional[ - str | Opacity | Map | OpacityDatum | OpacityValue - ] = Undefined, - order: Optional[OneOrSeq[str | Order | Map | OrderValue]] = Undefined, - radius: Optional[str | Radius | Map | RadiusDatum | RadiusValue] = Undefined, - radius2: Optional[ - str | Radius2 | Map | Radius2Datum | Radius2Value - ] = Undefined, - row: Optional[str | Row | Map] = Undefined, - shape: Optional[str | Shape | Map | ShapeDatum | ShapeValue] = Undefined, - size: Optional[str | Size | Map | SizeDatum | SizeValue] = Undefined, - stroke: Optional[str | Stroke | Map | StrokeDatum | StrokeValue] = Undefined, - strokeDash: Optional[ - str | StrokeDash | Map | StrokeDashDatum | StrokeDashValue - ] = Undefined, - strokeOpacity: Optional[ - str | StrokeOpacity | Map | StrokeOpacityDatum | StrokeOpacityValue - ] = Undefined, - strokeWidth: Optional[ - str | StrokeWidth | Map | StrokeWidthDatum | StrokeWidthValue - ] = Undefined, - text: Optional[str | Text | Map | TextDatum | TextValue] = Undefined, - theta: Optional[str | Theta | Map | ThetaDatum | ThetaValue] = Undefined, - theta2: Optional[str | Theta2 | Map | Theta2Datum | Theta2Value] = Undefined, - tooltip: Optional[OneOrSeq[str | Tooltip | Map | TooltipValue]] = Undefined, - url: Optional[str | Url | Map | UrlValue] = Undefined, - x: Optional[str | X | Map | XDatum | XValue] = Undefined, - x2: Optional[str | X2 | Map | X2Datum | X2Value] = Undefined, - xError: Optional[str | XError | Map | XErrorValue] = Undefined, - xError2: Optional[str | XError2 | Map | XError2Value] = Undefined, - xOffset: Optional[ - str | XOffset | Map | XOffsetDatum | XOffsetValue - ] = Undefined, - y: Optional[str | Y | Map | YDatum | YValue] = Undefined, - y2: Optional[str | Y2 | Map | Y2Datum | Y2Value] = Undefined, - yError: Optional[str | YError | Map | YErrorValue] = Undefined, - yError2: Optional[str | YError2 | Map | YError2Value] = Undefined, - yOffset: Optional[ - str | YOffset | Map | YOffsetDatum | YOffsetValue - ] = Undefined, + angle: Optional[ChannelAngle] = Undefined, + color: Optional[ChannelColor] = Undefined, + column: Optional[ChannelColumn] = Undefined, + description: Optional[ChannelDescription] = Undefined, + detail: Optional[ChannelDetail] = Undefined, + facet: Optional[ChannelFacet] = Undefined, + fill: Optional[ChannelFill] = Undefined, + fillOpacity: Optional[ChannelFillOpacity] = Undefined, + href: Optional[ChannelHref] = Undefined, + key: Optional[ChannelKey] = Undefined, + latitude: Optional[ChannelLatitude] = Undefined, + latitude2: Optional[ChannelLatitude2] = Undefined, + longitude: Optional[ChannelLongitude] = Undefined, + longitude2: Optional[ChannelLongitude2] = Undefined, + opacity: Optional[ChannelOpacity] = Undefined, + order: Optional[ChannelOrder] = Undefined, + radius: Optional[ChannelRadius] = Undefined, + radius2: Optional[ChannelRadius2] = Undefined, + row: Optional[ChannelRow] = Undefined, + shape: Optional[ChannelShape] = Undefined, + size: Optional[ChannelSize] = Undefined, + stroke: Optional[ChannelStroke] = Undefined, + strokeDash: Optional[ChannelStrokeDash] = Undefined, + strokeOpacity: Optional[ChannelStrokeOpacity] = Undefined, + strokeWidth: Optional[ChannelStrokeWidth] = Undefined, + text: Optional[ChannelText] = Undefined, + theta: Optional[ChannelTheta] = Undefined, + theta2: Optional[ChannelTheta2] = Undefined, + tooltip: Optional[ChannelTooltip] = Undefined, + url: Optional[ChannelUrl] = Undefined, + x: Optional[ChannelX] = Undefined, + x2: Optional[ChannelX2] = Undefined, + xError: Optional[ChannelXError] = Undefined, + xError2: Optional[ChannelXError2] = Undefined, + xOffset: Optional[ChannelXOffset] = Undefined, + y: Optional[ChannelY] = Undefined, + y2: Optional[ChannelY2] = Undefined, + yError: Optional[ChannelYError] = Undefined, + yError2: Optional[ChannelYError2] = Undefined, + yOffset: Optional[ChannelYOffset] = Undefined, ) -> Self: """ Map properties of the data to visual properties of the chart (see :class:`FacetedEncoding`). @@ -31391,43 +31423,43 @@ class _EncodeKwds(TypedDict, total=False): Offset of y-position of the marks """ - angle: str | Angle | Map | AngleDatum | AngleValue - color: str | Color | Map | ColorDatum | ColorValue - column: str | Column | Map - description: str | Description | Map | DescriptionValue - detail: OneOrSeq[str | Detail | Map] - facet: str | Facet | Map - fill: str | Fill | Map | FillDatum | FillValue - fillOpacity: str | FillOpacity | Map | FillOpacityDatum | FillOpacityValue - href: str | Href | Map | HrefValue - key: str | Key | Map - latitude: str | Latitude | Map | LatitudeDatum - latitude2: str | Latitude2 | Map | Latitude2Datum | Latitude2Value - longitude: str | Longitude | Map | LongitudeDatum - longitude2: str | Longitude2 | Map | Longitude2Datum | Longitude2Value - opacity: str | Opacity | Map | OpacityDatum | OpacityValue - order: OneOrSeq[str | Order | Map | OrderValue] - radius: str | Radius | Map | RadiusDatum | RadiusValue - radius2: str | Radius2 | Map | Radius2Datum | Radius2Value - row: str | Row | Map - shape: str | Shape | Map | ShapeDatum | ShapeValue - size: str | Size | Map | SizeDatum | SizeValue - stroke: str | Stroke | Map | StrokeDatum | StrokeValue - strokeDash: str | StrokeDash | Map | StrokeDashDatum | StrokeDashValue - strokeOpacity: str | StrokeOpacity | Map | StrokeOpacityDatum | StrokeOpacityValue - strokeWidth: str | StrokeWidth | Map | StrokeWidthDatum | StrokeWidthValue - text: str | Text | Map | TextDatum | TextValue - theta: str | Theta | Map | ThetaDatum | ThetaValue - theta2: str | Theta2 | Map | Theta2Datum | Theta2Value - tooltip: OneOrSeq[str | Tooltip | Map | TooltipValue] - url: str | Url | Map | UrlValue - x: str | X | Map | XDatum | XValue - x2: str | X2 | Map | X2Datum | X2Value - xError: str | XError | Map | XErrorValue - xError2: str | XError2 | Map | XError2Value - xOffset: str | XOffset | Map | XOffsetDatum | XOffsetValue - y: str | Y | Map | YDatum | YValue - y2: str | Y2 | Map | Y2Datum | Y2Value - yError: str | YError | Map | YErrorValue - yError2: str | YError2 | Map | YError2Value - yOffset: str | YOffset | Map | YOffsetDatum | YOffsetValue + angle: ChannelAngle + color: ChannelColor + column: ChannelColumn + description: ChannelDescription + detail: ChannelDetail + facet: ChannelFacet + fill: ChannelFill + fillOpacity: ChannelFillOpacity + href: ChannelHref + key: ChannelKey + latitude: ChannelLatitude + latitude2: ChannelLatitude2 + longitude: ChannelLongitude + longitude2: ChannelLongitude2 + opacity: ChannelOpacity + order: ChannelOrder + radius: ChannelRadius + radius2: ChannelRadius2 + row: ChannelRow + shape: ChannelShape + size: ChannelSize + stroke: ChannelStroke + strokeDash: ChannelStrokeDash + strokeOpacity: ChannelStrokeOpacity + strokeWidth: ChannelStrokeWidth + text: ChannelText + theta: ChannelTheta + theta2: ChannelTheta2 + tooltip: ChannelTooltip + url: ChannelUrl + x: ChannelX + x2: ChannelX2 + xError: ChannelXError + xError2: ChannelXError2 + xOffset: ChannelXOffset + y: ChannelY + y2: ChannelY2 + yError: ChannelYError + yError2: ChannelYError2 + yOffset: ChannelYOffset diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 3863ef8f9..566649db1 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -663,11 +663,13 @@ def generate_vegalite_channel_wrappers( imports = imports or [ "from __future__ import annotations\n", "from typing import Any, overload, Sequence, List, Literal, Union, TYPE_CHECKING, TypedDict", + "from typing_extensions import TypeAlias", "from narwhals.dependencies import is_pandas_dataframe as _is_pandas_dataframe", "from altair.utils.schemapi import Undefined, with_property_setters", "from altair.utils import infer_encoding_types as _infer_encoding_types", "from altair.utils import parse_shorthand", "from . import core", + "from ._typing import * # noqa: F403", ] contents = [ HEADER, @@ -676,7 +678,6 @@ def generate_vegalite_channel_wrappers( _type_checking_only_imports( "from altair import Parameter, SchemaBase", "from altair.utils.schemapi import Optional", - "from ._typing import * # noqa: F403", "from typing_extensions import Self", ), "\n" f"__all__ = {sorted(all_)}\n", @@ -883,6 +884,7 @@ def generate_encoding_artifacts( """ signature_args: list[str] = ["self", "*args: Any"] signature_docstring_parameters: list[str] = ["", "Parameters", "----------"] + channel_type_aliases: list[str] = [] typed_dict_docstring_parameters: list[str] = ["", "Parameters", "----------"] typed_dict_args: list[str] = [] for channel, info in channel_infos.items(): @@ -894,8 +896,13 @@ def generate_encoding_artifacts( if info.supports_arrays: docstring_union_types.append("List") tp_inner = f"OneOrSeq[{tp_inner}]" - signature_args.append(f"{channel}: Optional[{tp_inner}] = Undefined") - typed_dict_args.append(f"{channel}: {tp_inner}") + + alias_name = f"Channel{channel[0].upper()}{channel[1:]}" + channel_type_aliases.append(f"{alias_name}: TypeAlias = {tp_inner}") + typed_dict_args.append(f"{channel}: {alias_name}") + + signature_args.append(f"{channel}: Optional[{alias_name}] = Undefined") + signature_docstring_parameters.extend( ( f"{channel} : {', '.join(chain(docstring_union_types, it_rst_names))}", @@ -922,7 +929,7 @@ def generate_encoding_artifacts( encode_typed_dict = fmt_typed_dict.format( channels="\n ".join(typed_dict_args), docstring=typed_dict_doc ) - artifacts = [encode_method, encode_typed_dict] + artifacts = [*channel_type_aliases, encode_method, encode_typed_dict] yield from artifacts From 6662fc9b6011e33b0ff8981c1e75dbfc3d6a654f Mon Sep 17 00:00:00 2001 From: Stefan Binder Date: Mon, 5 Aug 2024 20:10:48 +0200 Subject: [PATCH 20/32] Format --- tools/generate_schema_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 566649db1..2b7aea22d 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -902,7 +902,7 @@ def generate_encoding_artifacts( typed_dict_args.append(f"{channel}: {alias_name}") signature_args.append(f"{channel}: Optional[{alias_name}] = Undefined") - + signature_docstring_parameters.extend( ( f"{channel} : {', '.join(chain(docstring_union_types, it_rst_names))}", From 28de27b1f3f431e04c8f689dc73f4c65d98f8bb1 Mon Sep 17 00:00:00 2001 From: Stefan Binder Date: Mon, 5 Aug 2024 20:14:59 +0200 Subject: [PATCH 21/32] Use Union instead of | for compatibility with Py <3.10 --- altair/vegalite/v5/schema/channels.py | 102 +++++++++++++------------- tools/generate_schema_wrapper.py | 2 +- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/altair/vegalite/v5/schema/channels.py b/altair/vegalite/v5/schema/channels.py index 496ef81b5..8b5bfb4ac 100644 --- a/altair/vegalite/v5/schema/channels.py +++ b/altair/vegalite/v5/schema/channels.py @@ -11,7 +11,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Literal, Sequence, TypedDict, overload +from typing import TYPE_CHECKING, Any, Literal, Sequence, TypedDict, Union, overload from typing_extensions import TypeAlias from narwhals.dependencies import is_pandas_dataframe as _is_pandas_dataframe @@ -30897,56 +30897,56 @@ def __init__(self, value, **kwds): super().__init__(value=value, **kwds) -ChannelAngle: TypeAlias = str | Angle | Map | AngleDatum | AngleValue -ChannelColor: TypeAlias = str | Color | Map | ColorDatum | ColorValue -ChannelColumn: TypeAlias = str | Column | Map -ChannelDescription: TypeAlias = str | Description | Map | DescriptionValue -ChannelDetail: TypeAlias = OneOrSeq[str | Detail | Map] -ChannelFacet: TypeAlias = str | Facet | Map -ChannelFill: TypeAlias = str | Fill | Map | FillDatum | FillValue -ChannelFillOpacity: TypeAlias = ( - str | FillOpacity | Map | FillOpacityDatum | FillOpacityValue -) -ChannelHref: TypeAlias = str | Href | Map | HrefValue -ChannelKey: TypeAlias = str | Key | Map -ChannelLatitude: TypeAlias = str | Latitude | Map | LatitudeDatum -ChannelLatitude2: TypeAlias = str | Latitude2 | Map | Latitude2Datum | Latitude2Value -ChannelLongitude: TypeAlias = str | Longitude | Map | LongitudeDatum -ChannelLongitude2: TypeAlias = ( - str | Longitude2 | Map | Longitude2Datum | Longitude2Value -) -ChannelOpacity: TypeAlias = str | Opacity | Map | OpacityDatum | OpacityValue -ChannelOrder: TypeAlias = OneOrSeq[str | Order | Map | OrderValue] -ChannelRadius: TypeAlias = str | Radius | Map | RadiusDatum | RadiusValue -ChannelRadius2: TypeAlias = str | Radius2 | Map | Radius2Datum | Radius2Value -ChannelRow: TypeAlias = str | Row | Map -ChannelShape: TypeAlias = str | Shape | Map | ShapeDatum | ShapeValue -ChannelSize: TypeAlias = str | Size | Map | SizeDatum | SizeValue -ChannelStroke: TypeAlias = str | Stroke | Map | StrokeDatum | StrokeValue -ChannelStrokeDash: TypeAlias = ( - str | StrokeDash | Map | StrokeDashDatum | StrokeDashValue -) -ChannelStrokeOpacity: TypeAlias = ( - str | StrokeOpacity | Map | StrokeOpacityDatum | StrokeOpacityValue -) -ChannelStrokeWidth: TypeAlias = ( - str | StrokeWidth | Map | StrokeWidthDatum | StrokeWidthValue -) -ChannelText: TypeAlias = str | Text | Map | TextDatum | TextValue -ChannelTheta: TypeAlias = str | Theta | Map | ThetaDatum | ThetaValue -ChannelTheta2: TypeAlias = str | Theta2 | Map | Theta2Datum | Theta2Value -ChannelTooltip: TypeAlias = OneOrSeq[str | Tooltip | Map | TooltipValue] -ChannelUrl: TypeAlias = str | Url | Map | UrlValue -ChannelX: TypeAlias = str | X | Map | XDatum | XValue -ChannelX2: TypeAlias = str | X2 | Map | X2Datum | X2Value -ChannelXError: TypeAlias = str | XError | Map | XErrorValue -ChannelXError2: TypeAlias = str | XError2 | Map | XError2Value -ChannelXOffset: TypeAlias = str | XOffset | Map | XOffsetDatum | XOffsetValue -ChannelY: TypeAlias = str | Y | Map | YDatum | YValue -ChannelY2: TypeAlias = str | Y2 | Map | Y2Datum | Y2Value -ChannelYError: TypeAlias = str | YError | Map | YErrorValue -ChannelYError2: TypeAlias = str | YError2 | Map | YError2Value -ChannelYOffset: TypeAlias = str | YOffset | Map | YOffsetDatum | YOffsetValue +ChannelAngle: TypeAlias = Union[str, Angle, Map, AngleDatum, AngleValue] +ChannelColor: TypeAlias = Union[str, Color, Map, ColorDatum, ColorValue] +ChannelColumn: TypeAlias = Union[str, Column, Map] +ChannelDescription: TypeAlias = Union[str, Description, Map, DescriptionValue] +ChannelDetail: TypeAlias = OneOrSeq[Union[str, Detail, Map]] +ChannelFacet: TypeAlias = Union[str, Facet, Map] +ChannelFill: TypeAlias = Union[str, Fill, Map, FillDatum, FillValue] +ChannelFillOpacity: TypeAlias = Union[ + str, FillOpacity, Map, FillOpacityDatum, FillOpacityValue +] +ChannelHref: TypeAlias = Union[str, Href, Map, HrefValue] +ChannelKey: TypeAlias = Union[str, Key, Map] +ChannelLatitude: TypeAlias = Union[str, Latitude, Map, LatitudeDatum] +ChannelLatitude2: TypeAlias = Union[str, Latitude2, Map, Latitude2Datum, Latitude2Value] +ChannelLongitude: TypeAlias = Union[str, Longitude, Map, LongitudeDatum] +ChannelLongitude2: TypeAlias = Union[ + str, Longitude2, Map, Longitude2Datum, Longitude2Value +] +ChannelOpacity: TypeAlias = Union[str, Opacity, Map, OpacityDatum, OpacityValue] +ChannelOrder: TypeAlias = OneOrSeq[Union[str, Order, Map, OrderValue]] +ChannelRadius: TypeAlias = Union[str, Radius, Map, RadiusDatum, RadiusValue] +ChannelRadius2: TypeAlias = Union[str, Radius2, Map, Radius2Datum, Radius2Value] +ChannelRow: TypeAlias = Union[str, Row, Map] +ChannelShape: TypeAlias = Union[str, Shape, Map, ShapeDatum, ShapeValue] +ChannelSize: TypeAlias = Union[str, Size, Map, SizeDatum, SizeValue] +ChannelStroke: TypeAlias = Union[str, Stroke, Map, StrokeDatum, StrokeValue] +ChannelStrokeDash: TypeAlias = Union[ + str, StrokeDash, Map, StrokeDashDatum, StrokeDashValue +] +ChannelStrokeOpacity: TypeAlias = Union[ + str, StrokeOpacity, Map, StrokeOpacityDatum, StrokeOpacityValue +] +ChannelStrokeWidth: TypeAlias = Union[ + str, StrokeWidth, Map, StrokeWidthDatum, StrokeWidthValue +] +ChannelText: TypeAlias = Union[str, Text, Map, TextDatum, TextValue] +ChannelTheta: TypeAlias = Union[str, Theta, Map, ThetaDatum, ThetaValue] +ChannelTheta2: TypeAlias = Union[str, Theta2, Map, Theta2Datum, Theta2Value] +ChannelTooltip: TypeAlias = OneOrSeq[Union[str, Tooltip, Map, TooltipValue]] +ChannelUrl: TypeAlias = Union[str, Url, Map, UrlValue] +ChannelX: TypeAlias = Union[str, X, Map, XDatum, XValue] +ChannelX2: TypeAlias = Union[str, X2, Map, X2Datum, X2Value] +ChannelXError: TypeAlias = Union[str, XError, Map, XErrorValue] +ChannelXError2: TypeAlias = Union[str, XError2, Map, XError2Value] +ChannelXOffset: TypeAlias = Union[str, XOffset, Map, XOffsetDatum, XOffsetValue] +ChannelY: TypeAlias = Union[str, Y, Map, YDatum, YValue] +ChannelY2: TypeAlias = Union[str, Y2, Map, Y2Datum, Y2Value] +ChannelYError: TypeAlias = Union[str, YError, Map, YErrorValue] +ChannelYError2: TypeAlias = Union[str, YError2, Map, YError2Value] +ChannelYOffset: TypeAlias = Union[str, YOffset, Map, YOffsetDatum, YOffsetValue] class _EncodingMixin: diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 2b7aea22d..e5bad5c36 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -892,7 +892,7 @@ def generate_encoding_artifacts( it_rst_names = (rst_syntax_for_class(c) for c in info.all_names) docstring_union_types = ["str", next(it_rst_names), "Dict"] - tp_inner: str = " | ".join(chain(("str", next(it), "Map"), it)) + tp_inner: str = "Union[" + ", ".join(chain(("str", next(it), "Map"), it)) + "]" if info.supports_arrays: docstring_union_types.append("List") tp_inner = f"OneOrSeq[{tp_inner}]" From b3fbe9cd244c416169cdcd9fb7d35d5792833d41 Mon Sep 17 00:00:00 2001 From: Stefan Binder Date: Tue, 6 Aug 2024 06:33:59 +0200 Subject: [PATCH 22/32] Add channel type aliases to typing module. Add 'Type hints' section to API references in docs. Remove is_chart_type from 'API' docs section as it's now in typing. Rename '_EncodeKwds' to 'EncodeKwds' --- altair/typing.py | 92 ++++++++++++++++++++++++++- altair/vegalite/v5/schema/channels.py | 2 +- doc/user_guide/api.rst | 54 +++++++++++++++- tools/generate_api_docs.py | 19 +++++- tools/generate_schema_wrapper.py | 6 +- 5 files changed, 166 insertions(+), 7 deletions(-) diff --git a/altair/typing.py b/altair/typing.py index 74d6b1fd6..71e453dfe 100644 --- a/altair/typing.py +++ b/altair/typing.py @@ -1,8 +1,96 @@ """Public types to ease integrating with `altair`.""" +# ruff: noqa: F401 + from __future__ import annotations -__all__ = ["ChartType", "EncodeKwds", "is_chart_type"] +__all__ = [ + "ChannelAngle", + "ChannelColor", + "ChannelColumn", + "ChannelDescription", + "ChannelDetail", + "ChannelFacet", + "ChannelFill", + "ChannelFillOpacity", + "ChannelHref", + "ChannelKey", + "ChannelLatitude", + "ChannelLatitude2", + "ChannelLongitude", + "ChannelLongitude2", + "ChannelOpacity", + "ChannelOrder", + "ChannelRadius", + "ChannelRadius2", + "ChannelRow", + "ChannelShape", + "ChannelSize", + "ChannelStroke", + "ChannelStrokeDash", + "ChannelStrokeOpacity", + "ChannelStrokeWidth", + "ChannelText", + "ChannelTheta", + "ChannelTheta2", + "ChannelTooltip", + "ChannelUrl", + "ChannelX", + "ChannelX2", + "ChannelXError", + "ChannelXError2", + "ChannelXOffset", + "ChannelY", + "ChannelY2", + "ChannelYError", + "ChannelYError2", + "ChannelYOffset", + "ChartType", + "EncodeKwds", + "is_chart_type", +] from altair.vegalite.v5.api import ChartType, is_chart_type -from altair.vegalite.v5.schema.channels import _EncodeKwds as EncodeKwds +from altair.vegalite.v5.schema.channels import ( + ChannelAngle, + ChannelColor, + ChannelColumn, + ChannelDescription, + ChannelDetail, + ChannelFacet, + ChannelFill, + ChannelFillOpacity, + ChannelHref, + ChannelKey, + ChannelLatitude, + ChannelLatitude2, + ChannelLongitude, + ChannelLongitude2, + ChannelOpacity, + ChannelOrder, + ChannelRadius, + ChannelRadius2, + ChannelRow, + ChannelShape, + ChannelSize, + ChannelStroke, + ChannelStrokeDash, + ChannelStrokeOpacity, + ChannelStrokeWidth, + ChannelText, + ChannelTheta, + ChannelTheta2, + ChannelTooltip, + ChannelUrl, + ChannelX, + ChannelX2, + ChannelXError, + ChannelXError2, + ChannelXOffset, + ChannelY, + ChannelY2, + ChannelYError, + ChannelYError2, + ChannelYOffset, + EncodeKwds, +) diff --git a/altair/vegalite/v5/schema/channels.py b/altair/vegalite/v5/schema/channels.py index 8b5bfb4ac..40e23dec2 100644 --- a/altair/vegalite/v5/schema/channels.py +++ b/altair/vegalite/v5/schema/channels.py @@ -31223,7 +31223,7 @@ def encode( return copy -class _EncodeKwds(TypedDict, total=False): +class EncodeKwds(TypedDict, total=False): """ Encoding channels map properties of the data to visual properties of the chart. diff --git a/doc/user_guide/api.rst b/doc/user_guide/api.rst index f6dac987e..20c42dbd5 100644 --- a/doc/user_guide/api.rst +++ b/doc/user_guide/api.rst @@ -152,7 +152,6 @@ API Functions condition graticule hconcat - is_chart_type layer param repeat @@ -638,3 +637,56 @@ API Utility Classes When Then ChainedWhen + +Type Hints +---------- +.. currentmodule:: altair.typing + +.. autosummary:: + :toctree: generated/typing/ + :nosignatures: + + ChannelAngle + ChannelColor + ChannelColumn + ChannelDescription + ChannelDetail + ChannelFacet + ChannelFill + ChannelFillOpacity + ChannelHref + ChannelKey + ChannelLatitude + ChannelLatitude2 + ChannelLongitude + ChannelLongitude2 + ChannelOpacity + ChannelOrder + ChannelRadius + ChannelRadius2 + ChannelRow + ChannelShape + ChannelSize + ChannelStroke + ChannelStrokeDash + ChannelStrokeOpacity + ChannelStrokeWidth + ChannelText + ChannelTheta + ChannelTheta2 + ChannelTooltip + ChannelUrl + ChannelX + ChannelX2 + ChannelXError + ChannelXError2 + ChannelXOffset + ChannelY + ChannelY2 + ChannelYError + ChannelYError2 + ChannelYOffset + ChartType + EncodeKwds + is_chart_type + diff --git a/tools/generate_api_docs.py b/tools/generate_api_docs.py index ed655ab69..2f5a79c74 100644 --- a/tools/generate_api_docs.py +++ b/tools/generate_api_docs.py @@ -72,6 +72,17 @@ :nosignatures: {api_classes} + +Type Hints +---------- +.. currentmodule:: altair.typing + +.. autosummary:: + :toctree: generated/typing/ + :nosignatures: + + {typing_objects} + """ @@ -109,7 +120,8 @@ def api_functions() -> list[str]: altair_api_functions = [ obj_name for obj_name in iter_objects(alt.api, restrict_to_type=types.FunctionType) # type: ignore[attr-defined] - if obj_name not in {"cast", "overload", "NamedTuple", "TypedDict"} + if obj_name + not in {"cast", "overload", "NamedTuple", "TypedDict", "is_chart_type"} ] return sorted(altair_api_functions) @@ -119,6 +131,10 @@ def api_classes() -> list[str]: return ["expr", "When", "Then", "ChainedWhen"] +def type_hints() -> list[str]: + return [s for s in sorted(iter_objects(alt.typing)) if s != "annotations"] + + def lowlevel_wrappers() -> list[str]: objects = sorted(iter_objects(alt.schema.core, restrict_to_subclass=alt.SchemaBase)) # type: ignore[attr-defined] # The names of these two classes are also used for classes in alt.channels. Due to @@ -140,6 +156,7 @@ def write_api_file() -> None: encoding_wrappers=sep.join(encoding_wrappers()), lowlevel_wrappers=sep.join(lowlevel_wrappers()), api_classes=sep.join(api_classes()), + typing_objects=sep.join(type_hints()), ), encoding="utf-8", ) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index e5bad5c36..468f4faf4 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -234,7 +234,7 @@ def encode({method_args}) -> Self: ''' ENCODE_TYPED_DICT: Final = ''' -class _EncodeKwds(TypedDict, total=False): +class EncodeKwds(TypedDict, total=False): """Encoding channels map properties of the data to visual properties of the chart. {docstring}""" {channels} @@ -898,7 +898,9 @@ def generate_encoding_artifacts( tp_inner = f"OneOrSeq[{tp_inner}]" alias_name = f"Channel{channel[0].upper()}{channel[1:]}" - channel_type_aliases.append(f"{alias_name}: TypeAlias = {tp_inner}") + channel_type_aliases.append( + f"{alias_name}: TypeAlias = {tp_inner}", + ) typed_dict_args.append(f"{channel}: {alias_name}") signature_args.append(f"{channel}: Optional[{alias_name}] = Undefined") From 5ba8a8d409245cd54507ea2813f6a6453390fb9b Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:54:20 +0100 Subject: [PATCH 23/32] chore(ruff): Remove unused `F401` ignore Would have only been needed before adding an `__all__` --- altair/typing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/altair/typing.py b/altair/typing.py index 71e453dfe..73f524378 100644 --- a/altair/typing.py +++ b/altair/typing.py @@ -1,7 +1,5 @@ """Public types to ease integrating with `altair`.""" -# ruff: noqa: F401 - from __future__ import annotations __all__ = [ From 49122b10057e06ed70cbfe041b691a32a6df1d1a Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 6 Aug 2024 13:11:48 +0100 Subject: [PATCH 24/32] feat(typing): Move `Optional` export to `typing` Updates all references, docs, `ruff` rule --- altair/__init__.py | 3 +-- altair/typing.py | 2 ++ altair/utils/schemapi.py | 8 +++++--- altair/vegalite/v5/schema/channels.py | 2 +- altair/vegalite/v5/schema/core.py | 2 +- altair/vegalite/v5/schema/mixins.py | 2 +- doc/user_guide/api.rst | 1 + pyproject.toml | 7 ++++++- tests/vegalite/v5/test_api.py | 6 +++--- tools/generate_schema_wrapper.py | 6 +++--- tools/schemapi/schemapi.py | 8 +++++--- 11 files changed, 29 insertions(+), 18 deletions(-) diff --git a/altair/__init__.py b/altair/__init__.py index a659b2ef1..9a6496d5e 100644 --- a/altair/__init__.py +++ b/altair/__init__.py @@ -300,7 +300,6 @@ "Opacity", "OpacityDatum", "OpacityValue", - "Optional", "Order", "OrderFieldDef", "OrderOnlyDef", @@ -652,7 +651,7 @@ def __dir__(): from altair.vegalite.v5.schema.core import Dict from altair.jupyter import JupyterChart from altair.expr import expr -from altair.utils import AltairDeprecationWarning, parse_shorthand, Optional, Undefined +from altair.utils import AltairDeprecationWarning, parse_shorthand, Undefined from altair import typing diff --git a/altair/typing.py b/altair/typing.py index 73f524378..cd8cb1489 100644 --- a/altair/typing.py +++ b/altair/typing.py @@ -45,9 +45,11 @@ "ChannelYOffset", "ChartType", "EncodeKwds", + "Optional", "is_chart_type", ] +from altair.utils.schemapi import Optional from altair.vegalite.v5.api import ChartType, is_chart_type from altair.vegalite.v5.schema.channels import ( ChannelAngle, diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index 0df55f5b3..e8884ccd2 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -777,7 +777,7 @@ def __repr__(self) -> str: The parameters ``short``, ``long`` accept the same range of types:: # ruff: noqa: UP006, UP007 - from altair import Optional + from altair.typing import Optional def func_1( short: Optional[str | bool | float | dict[str, Any] | SchemaBase] = Undefined, @@ -786,10 +786,12 @@ def func_1( ] = Undefined, ): ... -This is distinct from `typing.Optional `__ as ``altair.Optional`` treats ``None`` like any other type:: +This is distinct from `typing.Optional `__. + +``altair.typing.Optional`` treats ``None`` like any other type:: # ruff: noqa: UP006, UP007 - from altair import Optional + from altair.typing import Optional def func_2( short: Optional[str | float | dict[str, Any] | None | SchemaBase] = Undefined, diff --git a/altair/vegalite/v5/schema/channels.py b/altair/vegalite/v5/schema/channels.py index 40e23dec2..47f89d5fd 100644 --- a/altair/vegalite/v5/schema/channels.py +++ b/altair/vegalite/v5/schema/channels.py @@ -28,7 +28,7 @@ from typing_extensions import Self from altair import Parameter, SchemaBase - from altair.utils.schemapi import Optional + from altair.typing import Optional __all__ = [ diff --git a/altair/vegalite/v5/schema/core.py b/altair/vegalite/v5/schema/core.py index 1a1db541d..0e24cd59f 100644 --- a/altair/vegalite/v5/schema/core.py +++ b/altair/vegalite/v5/schema/core.py @@ -17,7 +17,7 @@ # ruff: noqa: F405 if TYPE_CHECKING: from altair import Parameter - from altair.utils.schemapi import Optional + from altair.typing import Optional from ._typing import * # noqa: F403 diff --git a/altair/vegalite/v5/schema/mixins.py b/altair/vegalite/v5/schema/mixins.py index bc081e7df..940164158 100644 --- a/altair/vegalite/v5/schema/mixins.py +++ b/altair/vegalite/v5/schema/mixins.py @@ -22,7 +22,7 @@ # ruff: noqa: F405 if TYPE_CHECKING: - from altair.utils.schemapi import Optional + from altair.typing import Optional from ._typing import * # noqa: F403 diff --git a/doc/user_guide/api.rst b/doc/user_guide/api.rst index 20c42dbd5..aed5d5312 100644 --- a/doc/user_guide/api.rst +++ b/doc/user_guide/api.rst @@ -688,5 +688,6 @@ Type Hints ChannelYOffset ChartType EncodeKwds + Optional is_chart_type diff --git a/pyproject.toml b/pyproject.toml index a645220a4..1a6529280 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -376,7 +376,12 @@ split-on-trailing-comma = false [tool.ruff.lint.flake8-tidy-imports.banned-api] # 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" +"typing.Optional".msg = """ +Use `Union[T, None]` instead. +`typing.Optional` is likely to be confused with `altair.typing.Optional`, \ +which have a similar but different semantic meaning. +See https://github.com/vega/altair/pull/3449 +""" [tool.ruff.lint.per-file-ignores] # Only enforce type annotation rules on public api diff --git a/tests/vegalite/v5/test_api.py b/tests/vegalite/v5/test_api.py index 5d3f73c95..ecfcbcace 100644 --- a/tests/vegalite/v5/test_api.py +++ b/tests/vegalite/v5/test_api.py @@ -22,7 +22,7 @@ from packaging.version import Version import altair as alt -from altair.utils.schemapi import Undefined +from altair.utils.schemapi import Optional, Undefined try: import vl_convert as vlc @@ -667,9 +667,9 @@ def test_when_multiple_fields(): alt.selection_point(fields=["Horsepower"]), ], ) -@pytest.mark.parametrize("empty", [alt.Undefined, True, False]) +@pytest.mark.parametrize("empty", [Undefined, True, False]) def test_when_condition_parity( - cars, channel: str, when, empty: alt.Optional[bool], then, otherwise + cars, channel: str, when, empty: Optional[bool], then, otherwise ): params = [when] if isinstance(when, alt.Parameter) else () kwds = {"x": "Cylinders:N", "y": "Origin:N"} diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 468f4faf4..72c3ef4b3 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -537,7 +537,7 @@ def generate_vegalite_schema_wrapper(schema_file: Path) -> str: "from altair.utils.schemapi import SchemaBase, Undefined, UndefinedType, _subclasses # noqa: F401\n", _type_checking_only_imports( "from altair import Parameter", - "from altair.utils.schemapi import Optional", + "from altair.typing import Optional", "from ._typing import * # noqa: F403", ), "\n" f"__all__ = {all_}\n", @@ -677,7 +677,7 @@ def generate_vegalite_channel_wrappers( *imports, _type_checking_only_imports( "from altair import Parameter, SchemaBase", - "from altair.utils.schemapi import Optional", + "from altair.typing import Optional", "from typing_extensions import Self", ), "\n" f"__all__ = {sorted(all_)}\n", @@ -836,7 +836,7 @@ def vegalite_main(skip_download: bool = False) -> None: "\n\n", _type_checking_only_imports( "from altair import Parameter, SchemaBase", - "from altair.utils.schemapi import Optional", + "from altair.typing import Optional", "from ._typing import * # noqa: F403", ), "\n\n\n", diff --git a/tools/schemapi/schemapi.py b/tools/schemapi/schemapi.py index 4ac2860ef..bd1827a89 100644 --- a/tools/schemapi/schemapi.py +++ b/tools/schemapi/schemapi.py @@ -775,7 +775,7 @@ def __repr__(self) -> str: The parameters ``short``, ``long`` accept the same range of types:: # ruff: noqa: UP006, UP007 - from altair import Optional + from altair.typing import Optional def func_1( short: Optional[str | bool | float | dict[str, Any] | SchemaBase] = Undefined, @@ -784,10 +784,12 @@ def func_1( ] = Undefined, ): ... -This is distinct from `typing.Optional `__ as ``altair.Optional`` treats ``None`` like any other type:: +This is distinct from `typing.Optional `__. + +``altair.typing.Optional`` treats ``None`` like any other type:: # ruff: noqa: UP006, UP007 - from altair import Optional + from altair.typing import Optional def func_2( short: Optional[str | float | dict[str, Any] | None | SchemaBase] = Undefined, From fe22c80b1c6491dcd05153b36e82f382826922ce Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:51:52 +0100 Subject: [PATCH 25/32] refactor: Move blank line append to `indent_docstring` If this is needed for every call, it belongs in the function itself --- tools/generate_schema_wrapper.py | 2 -- tools/schemapi/codegen.py | 2 -- tools/schemapi/utils.py | 2 ++ 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 72c3ef4b3..42e8c297c 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -917,8 +917,6 @@ def generate_encoding_artifacts( f" {process_description(info.deep_description)}", ) ) - signature_docstring_parameters += [""] - typed_dict_docstring_parameters += [""] signature_doc = indent_docstring( signature_docstring_parameters, indent_level=8, width=100, lstrip=False ) diff --git a/tools/schemapi/codegen.py b/tools/schemapi/codegen.py index 5681a1a37..0533964b4 100644 --- a/tools/schemapi/codegen.py +++ b/tools/schemapi/codegen.py @@ -226,8 +226,6 @@ def docstring(self, indent: int = 0) -> str: f"{prop} : {propinfo.get_python_type_representation()}", f" {self._process_description(propinfo.deep_description)}", ] - if len(doc) > 1: - doc += [""] return indent_docstring(doc, indent_level=indent, width=100, lstrip=True) def init_code(self, indent: int = 0) -> str: diff --git a/tools/schemapi/utils.py b/tools/schemapi/utils.py index 5e27ac8d5..17326a8a1 100644 --- a/tools/schemapi/utils.py +++ b/tools/schemapi/utils.py @@ -687,6 +687,8 @@ def indent_docstring( ) -> str: """Indent a docstring for use in generated code.""" final_lines = [] + if len(lines) > 1: + lines += [""] for i, line in enumerate(lines): stripped = line.lstrip() From d3daf51ee998b648c92f8fb805030f02bd8c5a65 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:55:08 +0100 Subject: [PATCH 26/32] docs(typing): Remove empty type list from `EncodeKwds` --- altair/vegalite/v5/schema/channels.py | 80 +++++++++++++-------------- tools/generate_schema_wrapper.py | 2 +- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/altair/vegalite/v5/schema/channels.py b/altair/vegalite/v5/schema/channels.py index 47f89d5fd..40b097f6d 100644 --- a/altair/vegalite/v5/schema/channels.py +++ b/altair/vegalite/v5/schema/channels.py @@ -31229,9 +31229,9 @@ class EncodeKwds(TypedDict, total=False): Parameters ---------- - angle : + angle Rotation angle of point and text marks. - color : + color Color of the marks - either fill or stroke color based on the ``filled`` property of mark definition. By default, ``color`` represents fill color for ``"area"``, ``"bar"``, ``"tick"``, ``"text"``, ``"trail"``, ``"circle"``, and ``"square"`` / @@ -31247,55 +31247,55 @@ class EncodeKwds(TypedDict, total=False): encoding if conflicting encodings are specified. 2) See the scale documentation for more information about customizing `color scheme `__. - column : + column A field definition for the horizontal facet of trellis plots. - description : + description A text description of this mark for ARIA accessibility (SVG output only). For SVG output the ``"aria-label"`` attribute will be set to this description. - detail : + detail Additional levels of detail for grouping data in aggregate views and in line, trail, and area marks without mapping data to a specific visual channel. - facet : + facet A field definition for the (flexible) facet of trellis plots. If either ``row`` or ``column`` is specified, this channel will be ignored. - fill : + fill Fill color of the marks. **Default value:** If undefined, the default color depends on `mark config `__'s ``color`` property. *Note:* The ``fill`` encoding has higher precedence than ``color``, thus may override the ``color`` encoding if conflicting encodings are specified. - fillOpacity : + fillOpacity Fill opacity of the marks. **Default value:** If undefined, the default opacity depends on `mark config `__'s ``fillOpacity`` property. - href : + href A URL to load upon mouse click. - key : + key A data field to use as a unique key for data binding. When a visualization's data is updated, the key value will be used to match data elements to existing mark instances. Use a key channel to enable object constancy for transitions over dynamic data. - latitude : + latitude Latitude position of geographically projected marks. - latitude2 : + latitude2 Latitude-2 position for geographically projected ranged ``"area"``, ``"bar"``, ``"rect"``, and ``"rule"``. - longitude : + longitude Longitude position of geographically projected marks. - longitude2 : + longitude2 Longitude-2 position for geographically projected ranged ``"area"``, ``"bar"``, ``"rect"``, and ``"rule"``. - opacity : + opacity Opacity of the marks. **Default value:** If undefined, the default opacity depends on `mark config `__'s ``opacity`` property. - order : + order Order of the marks. * For stacked marks, this ``order`` channel encodes `stack order @@ -31309,13 +31309,13 @@ class EncodeKwds(TypedDict, total=False): **Note**: In aggregate plots, ``order`` field should be ``aggregate``d to avoid creating additional aggregation grouping. - radius : + radius The outer radius in pixels of arc marks. - radius2 : + radius2 The inner radius in pixels of arc marks. - row : + row A field definition for the vertical facet of trellis plots. - shape : + shape Shape of the mark. 1. For ``point`` marks the supported values include: - plotting shapes: @@ -31332,7 +31332,7 @@ class EncodeKwds(TypedDict, total=False): **Default value:** If undefined, the default shape depends on `mark config `__'s ``shape`` property. (``"circle"`` if unset.) - size : + size Size of the mark. * For ``"point"``, ``"square"`` and ``"circle"``, - the symbol size, or pixel area @@ -31341,7 +31341,7 @@ class EncodeKwds(TypedDict, total=False): * For ``"text"`` - the text's font size. * Size is unsupported for ``"line"``, ``"area"``, and ``"rect"``. (Use ``"trail"`` instead of line with varying size) - stroke : + stroke Stroke color of the marks. **Default value:** If undefined, the default color depends on `mark config `__'s ``color`` @@ -31349,77 +31349,77 @@ class EncodeKwds(TypedDict, total=False): *Note:* The ``stroke`` encoding has higher precedence than ``color``, thus may override the ``color`` encoding if conflicting encodings are specified. - strokeDash : + strokeDash Stroke dash of the marks. **Default value:** ``[1,0]`` (No dash). - strokeOpacity : + strokeOpacity Stroke opacity of the marks. **Default value:** If undefined, the default opacity depends on `mark config `__'s ``strokeOpacity`` property. - strokeWidth : + strokeWidth Stroke width of the marks. **Default value:** If undefined, the default stroke width depends on `mark config `__'s ``strokeWidth`` property. - text : + text Text of the ``text`` mark. - theta : + theta * For arc marks, the arc length in radians if theta2 is not specified, otherwise the start arc angle. (A value of 0 indicates up or “north”, increasing values proceed clockwise.) * For text marks, polar coordinate angle in radians. - theta2 : + theta2 The end angle of arc marks in radians. A value of 0 indicates up or “north”, increasing values proceed clockwise. - tooltip : + tooltip The tooltip text to show upon mouse hover. Specifying ``tooltip`` encoding overrides `the tooltip property in the mark definition `__. See the `tooltip `__ documentation for a detailed discussion about tooltip in Vega-Lite. - url : + url The URL of an image mark. - x : + x X coordinates of the marks, or width of horizontal ``"bar"`` and ``"area"`` without specified ``x2`` or ``width``. The ``value`` of this channel can be a number or a string ``"width"`` for the width of the plot. - x2 : + x2 X2 coordinates for ranged ``"area"``, ``"bar"``, ``"rect"``, and ``"rule"``. The ``value`` of this channel can be a number or a string ``"width"`` for the width of the plot. - xError : + xError Error value of x coordinates for error specified ``"errorbar"`` and ``"errorband"``. - xError2 : + xError2 Secondary error value of x coordinates for error specified ``"errorbar"`` and ``"errorband"``. - xOffset : + xOffset Offset of x-position of the marks - y : + y Y coordinates of the marks, or height of vertical ``"bar"`` and ``"area"`` without specified ``y2`` or ``height``. The ``value`` of this channel can be a number or a string ``"height"`` for the height of the plot. - y2 : + y2 Y2 coordinates for ranged ``"area"``, ``"bar"``, ``"rect"``, and ``"rule"``. The ``value`` of this channel can be a number or a string ``"height"`` for the height of the plot. - yError : + yError Error value of y coordinates for error specified ``"errorbar"`` and ``"errorband"``. - yError2 : + yError2 Secondary error value of y coordinates for error specified ``"errorbar"`` and ``"errorband"``. - yOffset : + yOffset Offset of y-position of the marks """ diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 42e8c297c..f83286b97 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -913,7 +913,7 @@ def generate_encoding_artifacts( ) typed_dict_docstring_parameters.extend( ( - f"{channel} :", + f"{channel}", f" {process_description(info.deep_description)}", ) ) From 914428a0f478d3c07b6798c0bcc833a17823b8ec Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:02:36 +0100 Subject: [PATCH 27/32] refactor: Renaming, grouping, reducing repetition --- tools/generate_schema_wrapper.py | 44 +++++++++++++++----------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index f83286b97..e8ea331db 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -883,40 +883,38 @@ def generate_encoding_artifacts( - `info.supports_arrays` """ signature_args: list[str] = ["self", "*args: Any"] + type_aliases: list[str] = [] + typed_dict_args: list[str] = [] signature_docstring_parameters: list[str] = ["", "Parameters", "----------"] - channel_type_aliases: list[str] = [] typed_dict_docstring_parameters: list[str] = ["", "Parameters", "----------"] - typed_dict_args: list[str] = [] + for channel, info in channel_infos.items(): - it = info.all_names - it_rst_names = (rst_syntax_for_class(c) for c in info.all_names) + alias_name: str = f"Channel{channel[0].upper()}{channel[1:]}" + + it: Iterator[str] = info.all_names + it_rst_names: Iterator[str] = (rst_syntax_for_class(c) for c in info.all_names) + + docstring_types: list[str] = ["str", next(it_rst_names), "Dict"] + tp_inner: str = ", ".join(chain(("str", next(it), "Map"), it)) + tp_inner = f"Union[{tp_inner}]" - docstring_union_types = ["str", next(it_rst_names), "Dict"] - tp_inner: str = "Union[" + ", ".join(chain(("str", next(it), "Map"), it)) + "]" if info.supports_arrays: - docstring_union_types.append("List") + docstring_types.append("List") tp_inner = f"OneOrSeq[{tp_inner}]" - alias_name = f"Channel{channel[0].upper()}{channel[1:]}" - channel_type_aliases.append( - f"{alias_name}: TypeAlias = {tp_inner}", - ) - typed_dict_args.append(f"{channel}: {alias_name}") + doc_types_flat: str = ", ".join(chain(docstring_types, it_rst_names)) + type_aliases.append(f"{alias_name}: TypeAlias = {tp_inner}") + typed_dict_args.append(f"{channel}: {alias_name}") signature_args.append(f"{channel}: Optional[{alias_name}] = Undefined") + description: str = f" {process_description(info.deep_description)}" + signature_docstring_parameters.extend( - ( - f"{channel} : {', '.join(chain(docstring_union_types, it_rst_names))}", - f" {process_description(info.deep_description)}", - ) - ) - typed_dict_docstring_parameters.extend( - ( - f"{channel}", - f" {process_description(info.deep_description)}", - ) + (f"{channel} : {doc_types_flat}", description) ) + typed_dict_docstring_parameters.extend((f"{channel}", description)) + signature_doc = indent_docstring( signature_docstring_parameters, indent_level=8, width=100, lstrip=False ) @@ -929,7 +927,7 @@ def generate_encoding_artifacts( encode_typed_dict = fmt_typed_dict.format( channels="\n ".join(typed_dict_args), docstring=typed_dict_doc ) - artifacts = [*channel_type_aliases, encode_method, encode_typed_dict] + artifacts = *type_aliases, encode_method, encode_typed_dict yield from artifacts From 11c58c33e8be511fec54eeff005428287c01b83a Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:16:23 +0100 Subject: [PATCH 28/32] refactor: More tidying up, annotating, reformat --- tools/generate_schema_wrapper.py | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index e8ea331db..99e6a60ef 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -885,8 +885,8 @@ def generate_encoding_artifacts( signature_args: list[str] = ["self", "*args: Any"] type_aliases: list[str] = [] typed_dict_args: list[str] = [] - signature_docstring_parameters: list[str] = ["", "Parameters", "----------"] - typed_dict_docstring_parameters: list[str] = ["", "Parameters", "----------"] + signature_doc_params: list[str] = ["", "Parameters", "----------"] + typed_dict_doc_params: list[str] = ["", "Parameters", "----------"] for channel, info in channel_infos.items(): alias_name: str = f"Channel{channel[0].upper()}{channel[1:]}" @@ -910,24 +910,18 @@ def generate_encoding_artifacts( description: str = f" {process_description(info.deep_description)}" - signature_docstring_parameters.extend( - (f"{channel} : {doc_types_flat}", description) - ) - typed_dict_docstring_parameters.extend((f"{channel}", description)) + signature_doc_params.extend((f"{channel} : {doc_types_flat}", description)) + typed_dict_doc_params.extend((f"{channel}", description)) - signature_doc = indent_docstring( - signature_docstring_parameters, indent_level=8, width=100, lstrip=False - ) - typed_dict_doc = indent_docstring( - typed_dict_docstring_parameters, indent_level=4, width=100, lstrip=False - ) - encode_method = fmt_method.format( - method_args=", ".join(signature_args), docstring=signature_doc + method: str = fmt_method.format( + method_args=", ".join(signature_args), + docstring=indent_docstring(signature_doc_params, indent_level=8, lstrip=False), ) - encode_typed_dict = fmt_typed_dict.format( - channels="\n ".join(typed_dict_args), docstring=typed_dict_doc + typed_dict: str = fmt_typed_dict.format( + channels="\n ".join(typed_dict_args), + docstring=indent_docstring(typed_dict_doc_params, indent_level=4, lstrip=False), ) - artifacts = *type_aliases, encode_method, encode_typed_dict + artifacts: Iterable[str] = *type_aliases, method, typed_dict yield from artifacts From 067f4550d9be18a37a63624c920feb80142c3d92 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:18:56 +0100 Subject: [PATCH 29/32] docs: Reference aliases in `generate_encoding_artifacts` --- tools/generate_schema_wrapper.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 99e6a60ef..ff0fbbc8c 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -869,10 +869,11 @@ def generate_encoding_artifacts( channel_infos: dict[str, ChannelInfo], fmt_method: str, fmt_typed_dict: str ) -> Iterator[str]: """ - Generate `.encode()` related things. + Generate ``Chart.encode()`` and related typing structures. - This was previously `_create_encode_signature()`. Now also creates a `TypedDict` as - part of https://github.com/pola-rs/polars/pull/17995. + - `TypeAlias`(s) for each parameter to ``Chart.encode()`` + - Mixin class that provides the ``Chart.encode()`` method + - `TypedDict`, utilising/describing these structures as part of https://github.com/pola-rs/polars/pull/17995. Notes ----- From 6fefd1270d59344bc2b44d7ee4491b8cb84b56ee Mon Sep 17 00:00:00 2001 From: Stefan Binder Date: Wed, 7 Aug 2024 06:35:51 +0200 Subject: [PATCH 30/32] Use full type hints instead of type alias in signatures for typeddict and encode method --- altair/vegalite/v5/schema/channels.py | 180 ++++++++++++++------------ tools/generate_schema_wrapper.py | 7 +- 2 files changed, 105 insertions(+), 82 deletions(-) diff --git a/altair/vegalite/v5/schema/channels.py b/altair/vegalite/v5/schema/channels.py index 40b097f6d..34daf00ab 100644 --- a/altair/vegalite/v5/schema/channels.py +++ b/altair/vegalite/v5/schema/channels.py @@ -30953,46 +30953,66 @@ class _EncodingMixin: def encode( self, *args: Any, - angle: Optional[ChannelAngle] = Undefined, - color: Optional[ChannelColor] = Undefined, - column: Optional[ChannelColumn] = Undefined, - description: Optional[ChannelDescription] = Undefined, - detail: Optional[ChannelDetail] = Undefined, - facet: Optional[ChannelFacet] = Undefined, - fill: Optional[ChannelFill] = Undefined, - fillOpacity: Optional[ChannelFillOpacity] = Undefined, - href: Optional[ChannelHref] = Undefined, - key: Optional[ChannelKey] = Undefined, - latitude: Optional[ChannelLatitude] = Undefined, - latitude2: Optional[ChannelLatitude2] = Undefined, - longitude: Optional[ChannelLongitude] = Undefined, - longitude2: Optional[ChannelLongitude2] = Undefined, - opacity: Optional[ChannelOpacity] = Undefined, - order: Optional[ChannelOrder] = Undefined, - radius: Optional[ChannelRadius] = Undefined, - radius2: Optional[ChannelRadius2] = Undefined, - row: Optional[ChannelRow] = Undefined, - shape: Optional[ChannelShape] = Undefined, - size: Optional[ChannelSize] = Undefined, - stroke: Optional[ChannelStroke] = Undefined, - strokeDash: Optional[ChannelStrokeDash] = Undefined, - strokeOpacity: Optional[ChannelStrokeOpacity] = Undefined, - strokeWidth: Optional[ChannelStrokeWidth] = Undefined, - text: Optional[ChannelText] = Undefined, - theta: Optional[ChannelTheta] = Undefined, - theta2: Optional[ChannelTheta2] = Undefined, - tooltip: Optional[ChannelTooltip] = Undefined, - url: Optional[ChannelUrl] = Undefined, - x: Optional[ChannelX] = Undefined, - x2: Optional[ChannelX2] = Undefined, - xError: Optional[ChannelXError] = Undefined, - xError2: Optional[ChannelXError2] = Undefined, - xOffset: Optional[ChannelXOffset] = Undefined, - y: Optional[ChannelY] = Undefined, - y2: Optional[ChannelY2] = Undefined, - yError: Optional[ChannelYError] = Undefined, - yError2: Optional[ChannelYError2] = Undefined, - yOffset: Optional[ChannelYOffset] = Undefined, + angle: Optional[str | Angle | Map | AngleDatum | AngleValue] = Undefined, + color: Optional[str | Color | Map | ColorDatum | ColorValue] = Undefined, + column: Optional[str | Column | Map] = Undefined, + description: Optional[str | Description | Map | DescriptionValue] = Undefined, + detail: Optional[OneOrSeq[str | Detail | Map]] = Undefined, + facet: Optional[str | Facet | Map] = Undefined, + fill: Optional[str | Fill | Map | FillDatum | FillValue] = Undefined, + fillOpacity: Optional[ + str | FillOpacity | Map | FillOpacityDatum | FillOpacityValue + ] = Undefined, + href: Optional[str | Href | Map | HrefValue] = Undefined, + key: Optional[str | Key | Map] = Undefined, + latitude: Optional[str | Latitude | Map | LatitudeDatum] = Undefined, + latitude2: Optional[ + str | Latitude2 | Map | Latitude2Datum | Latitude2Value + ] = Undefined, + longitude: Optional[str | Longitude | Map | LongitudeDatum] = Undefined, + longitude2: Optional[ + str | Longitude2 | Map | Longitude2Datum | Longitude2Value + ] = Undefined, + opacity: Optional[ + str | Opacity | Map | OpacityDatum | OpacityValue + ] = Undefined, + order: Optional[OneOrSeq[str | Order | Map | OrderValue]] = Undefined, + radius: Optional[str | Radius | Map | RadiusDatum | RadiusValue] = Undefined, + radius2: Optional[ + str | Radius2 | Map | Radius2Datum | Radius2Value + ] = Undefined, + row: Optional[str | Row | Map] = Undefined, + shape: Optional[str | Shape | Map | ShapeDatum | ShapeValue] = Undefined, + size: Optional[str | Size | Map | SizeDatum | SizeValue] = Undefined, + stroke: Optional[str | Stroke | Map | StrokeDatum | StrokeValue] = Undefined, + strokeDash: Optional[ + str | StrokeDash | Map | StrokeDashDatum | StrokeDashValue + ] = Undefined, + strokeOpacity: Optional[ + str | StrokeOpacity | Map | StrokeOpacityDatum | StrokeOpacityValue + ] = Undefined, + strokeWidth: Optional[ + str | StrokeWidth | Map | StrokeWidthDatum | StrokeWidthValue + ] = Undefined, + text: Optional[str | Text | Map | TextDatum | TextValue] = Undefined, + theta: Optional[str | Theta | Map | ThetaDatum | ThetaValue] = Undefined, + theta2: Optional[str | Theta2 | Map | Theta2Datum | Theta2Value] = Undefined, + tooltip: Optional[OneOrSeq[str | Tooltip | Map | TooltipValue]] = Undefined, + url: Optional[str | Url | Map | UrlValue] = Undefined, + x: Optional[str | X | Map | XDatum | XValue] = Undefined, + x2: Optional[str | X2 | Map | X2Datum | X2Value] = Undefined, + xError: Optional[str | XError | Map | XErrorValue] = Undefined, + xError2: Optional[str | XError2 | Map | XError2Value] = Undefined, + xOffset: Optional[ + str | XOffset | Map | XOffsetDatum | XOffsetValue + ] = Undefined, + y: Optional[str | Y | Map | YDatum | YValue] = Undefined, + y2: Optional[str | Y2 | Map | Y2Datum | Y2Value] = Undefined, + yError: Optional[str | YError | Map | YErrorValue] = Undefined, + yError2: Optional[str | YError2 | Map | YError2Value] = Undefined, + yOffset: Optional[ + str | YOffset | Map | YOffsetDatum | YOffsetValue + ] = Undefined, ) -> Self: """ Map properties of the data to visual properties of the chart (see :class:`FacetedEncoding`). @@ -31423,43 +31443,43 @@ class EncodeKwds(TypedDict, total=False): Offset of y-position of the marks """ - angle: ChannelAngle - color: ChannelColor - column: ChannelColumn - description: ChannelDescription - detail: ChannelDetail - facet: ChannelFacet - fill: ChannelFill - fillOpacity: ChannelFillOpacity - href: ChannelHref - key: ChannelKey - latitude: ChannelLatitude - latitude2: ChannelLatitude2 - longitude: ChannelLongitude - longitude2: ChannelLongitude2 - opacity: ChannelOpacity - order: ChannelOrder - radius: ChannelRadius - radius2: ChannelRadius2 - row: ChannelRow - shape: ChannelShape - size: ChannelSize - stroke: ChannelStroke - strokeDash: ChannelStrokeDash - strokeOpacity: ChannelStrokeOpacity - strokeWidth: ChannelStrokeWidth - text: ChannelText - theta: ChannelTheta - theta2: ChannelTheta2 - tooltip: ChannelTooltip - url: ChannelUrl - x: ChannelX - x2: ChannelX2 - xError: ChannelXError - xError2: ChannelXError2 - xOffset: ChannelXOffset - y: ChannelY - y2: ChannelY2 - yError: ChannelYError - yError2: ChannelYError2 - yOffset: ChannelYOffset + angle: str | Angle | Map | AngleDatum | AngleValue + color: str | Color | Map | ColorDatum | ColorValue + column: str | Column | Map + description: str | Description | Map | DescriptionValue + detail: OneOrSeq[str | Detail | Map] + facet: str | Facet | Map + fill: str | Fill | Map | FillDatum | FillValue + fillOpacity: str | FillOpacity | Map | FillOpacityDatum | FillOpacityValue + href: str | Href | Map | HrefValue + key: str | Key | Map + latitude: str | Latitude | Map | LatitudeDatum + latitude2: str | Latitude2 | Map | Latitude2Datum | Latitude2Value + longitude: str | Longitude | Map | LongitudeDatum + longitude2: str | Longitude2 | Map | Longitude2Datum | Longitude2Value + opacity: str | Opacity | Map | OpacityDatum | OpacityValue + order: OneOrSeq[str | Order | Map | OrderValue] + radius: str | Radius | Map | RadiusDatum | RadiusValue + radius2: str | Radius2 | Map | Radius2Datum | Radius2Value + row: str | Row | Map + shape: str | Shape | Map | ShapeDatum | ShapeValue + size: str | Size | Map | SizeDatum | SizeValue + stroke: str | Stroke | Map | StrokeDatum | StrokeValue + strokeDash: str | StrokeDash | Map | StrokeDashDatum | StrokeDashValue + strokeOpacity: str | StrokeOpacity | Map | StrokeOpacityDatum | StrokeOpacityValue + strokeWidth: str | StrokeWidth | Map | StrokeWidthDatum | StrokeWidthValue + text: str | Text | Map | TextDatum | TextValue + theta: str | Theta | Map | ThetaDatum | ThetaValue + theta2: str | Theta2 | Map | Theta2Datum | Theta2Value + tooltip: OneOrSeq[str | Tooltip | Map | TooltipValue] + url: str | Url | Map | UrlValue + x: str | X | Map | XDatum | XValue + x2: str | X2 | Map | X2Datum | X2Value + xError: str | XError | Map | XErrorValue + xError2: str | XError2 | Map | XError2Value + xOffset: str | XOffset | Map | XOffsetDatum | XOffsetValue + y: str | Y | Map | YDatum | YValue + y2: str | Y2 | Map | Y2Datum | Y2Value + yError: str | YError | Map | YErrorValue + yError2: str | YError2 | Map | YError2Value + yOffset: str | YOffset | Map | YOffsetDatum | YOffsetValue diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index ff0fbbc8c..8b4e7dd2e 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -906,8 +906,11 @@ def generate_encoding_artifacts( doc_types_flat: str = ", ".join(chain(docstring_types, it_rst_names)) type_aliases.append(f"{alias_name}: TypeAlias = {tp_inner}") - typed_dict_args.append(f"{channel}: {alias_name}") - signature_args.append(f"{channel}: Optional[{alias_name}] = Undefined") + # We use the full type hints instead of the alias in the signatures below + # as IDEs such as VS Code would else show the name of the alias instead + # of the expanded full type hints. The later are more useful to users. + typed_dict_args.append(f"{channel}: {tp_inner}") + signature_args.append(f"{channel}: Optional[{tp_inner}] = Undefined") description: str = f" {process_description(info.deep_description)}" From b6f84e469dfcb3c5321faf698441984bd2615d0c Mon Sep 17 00:00:00 2001 From: Stefan Binder Date: Thu, 8 Aug 2024 20:46:40 +0200 Subject: [PATCH 31/32] Rename 'Type hints' to 'Typing' --- doc/user_guide/api.rst | 4 ++-- tools/generate_api_docs.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/user_guide/api.rst b/doc/user_guide/api.rst index aed5d5312..eaa9cb602 100644 --- a/doc/user_guide/api.rst +++ b/doc/user_guide/api.rst @@ -638,8 +638,8 @@ API Utility Classes Then ChainedWhen -Type Hints ----------- +Typing +------ .. currentmodule:: altair.typing .. autosummary:: diff --git a/tools/generate_api_docs.py b/tools/generate_api_docs.py index 2f5a79c74..d3771d6b7 100644 --- a/tools/generate_api_docs.py +++ b/tools/generate_api_docs.py @@ -73,8 +73,8 @@ {api_classes} -Type Hints ----------- +Typing +------ .. currentmodule:: altair.typing .. autosummary:: From d4313c09154ed44cf80d851e96bbe51bdec92389 Mon Sep 17 00:00:00 2001 From: Stefan Binder Date: Thu, 8 Aug 2024 20:49:15 +0200 Subject: [PATCH 32/32] Ruff fix --- altair/utils/_transformed_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/altair/utils/_transformed_data.py b/altair/utils/_transformed_data.py index 0bf33c9f7..3839a13d2 100644 --- a/altair/utils/_transformed_data.py +++ b/altair/utils/_transformed_data.py @@ -452,7 +452,7 @@ def get_facet_mapping(group: dict[str, Any], scope: Scope = ()) -> FacetMapping: group, facet_data, scope ) if definition_scope is not None: - facet_mapping[(facet_name, group_scope)] = ( + facet_mapping[facet_name, group_scope] = ( facet_data, definition_scope, )