-
Notifications
You must be signed in to change notification settings - Fork 786
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(typing): Improve Then
annotations, autocompletion, docs
#3567
base: main
Are you sure you want to change the base?
Conversation
Purely demonstrating type checking behaviour for vega#3552 (comment)
Revisting these I found it confusing that they were defined like (A, B, B, A). They are now (A, A, B, B) so it is clearer how they are linked
Aiming to get all of these to pass, without breaking anything else. **CI fail expected**
SchemaLike
Then
annotations, simplify autocompletion
…-compliant Can't define this in `OperatorMixin`, as `Expression` uses `{"type": "string"}`
Added a lot of notes here, since I was surpised `Then` would not be allowed in the cases I had planned originally
We're using a feature that didn't make it into `3.13` yet https://peps.python.org/pep-0728/
The warning raised by `pyright` here was actually quite helpful. Essentially, a shorthand string is never allowed here vega#3552 (comment)
We now provide more precise errors identifying exactly which call produced the issue. Previously, the `.then("min(foo):Q")` was silently allowed - but later rejected in `.otherwise()`.
I'm fairly sure this is fixed in another branch, so leaving this here for less of a conflict https://github.com/vega/altair/pull/3547/files#diff-bbe4e187b18d242a366c820d023afd89041759cb96e4ec66c3f34559a72c2f9d
No longer inherited
def __repr__(self) -> str: | ||
name = type(self).__name__ | ||
COND = "condition: " | ||
LB, RB = "{", "}" | ||
if len(self.condition) == 1: | ||
args = f"{COND}{self.condition!r}".replace("\n", "\n ") | ||
else: | ||
conds = "\n ".join(f"{c!r}" for c in self.condition) | ||
args = f"{COND}[\n " f"{conds}\n ]" | ||
return f"{name}({LB}\n {args}\n{RB})" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with pytest.raises(SchemaValidationError): | ||
# NOTE: `when-then` is allowed as an encoding, but not as a `ConditionalAxisProperty` | ||
# The latter fails validation since it does not have a default `value` | ||
chart.encode( | ||
color=then, | ||
x=alt.X("Cylinders:N").axis(labelColor=then), # type: ignore[call-overload] | ||
y=alt.Y("Origin:N", axis=alt.Axis(labelColor=then)), # type: ignore[arg-type] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was surprised that the current schema doesn't allow this.
Conditional encodings can omit the default, but conditional properties must define one.
I find this strange as all the conditional properties I tested accept value=None
- so maybe this support could be added in vega-lite
?
angle: Optional[ | ||
str | Angle | Map | AngleDatum | AngleValue | IntoCondition | ||
] = Undefined, | ||
color: Optional[ | ||
str | Color | Map | ColorDatum | ColorValue | IntoCondition | ||
] = Undefined, | ||
column: Optional[str | Column | Map | IntoCondition] = Undefined, | ||
description: Optional[ | ||
str | Description | Map | DescriptionValue | IntoCondition | ||
] = Undefined, | ||
detail: Optional[OneOrSeq[str | Detail | Map | IntoCondition]] = Undefined, | ||
facet: Optional[str | Facet | Map | IntoCondition] = Undefined, | ||
fill: Optional[ | ||
str | Fill | Map | FillDatum | FillValue | IntoCondition | ||
] = Undefined, | ||
fillOpacity: Optional[ | ||
str | FillOpacity | Map | FillOpacityDatum | FillOpacityValue | ||
str | ||
| FillOpacity | ||
| Map | ||
| FillOpacityDatum | ||
| FillOpacityValue | ||
| IntoCondition | ||
] = Undefined, | ||
href: Optional[str | Href | Map | HrefValue | IntoCondition] = Undefined, | ||
key: Optional[str | Key | Map | IntoCondition] = Undefined, | ||
latitude: Optional[ | ||
str | Latitude | Map | LatitudeDatum | IntoCondition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking we could simplify these by adding some intermediate, non-exported aliases.
So for the first two channels:
Imports/defs
from typing import Union
from typing_extensions import TypeAlias
from altair import (
Angle,
AngleDatum,
AngleValue,
Color,
ColorDatum,
ColorValue,
Undefined,
)
from altair.utils import Optional
from altair.vegalite.v5.api import IntoCondition
from altair.vegalite.v5.schema._typing import Map
AnyAngle: TypeAlias = Union[Angle, AngleDatum, AngleValue]
AnyColor: TypeAlias = Union[Color, ColorDatum, ColorValue]
class _EncodingMixin:
def encode_current(
self,
*args: Any,
angle: Optional[
str | Angle | Map | AngleDatum | AngleValue | IntoCondition
] = Undefined,
color: Optional[
str | Color | Map | ColorDatum | ColorValue | IntoCondition
] = Undefined,
): ...
def encode_proposed(
self,
*args: Any,
angle: Optional[str | AnyAngle | Map | IntoCondition] = Undefined,
color: Optional[str | AnyColor | Map | IntoCondition] = Undefined,
): ...
Would certainly reduce the amount of repetition in all the names.
Since there are 40 channels, with up to 3 names each - adding just a single new type IntoCondition
has a big impact
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See this thread, regarding potential trade-off with an alias here
IMO this is isn't as big of an issue as before - but still relevant
Removes the need for hardcoding restrictions in multiple places
…edDict`) - Adds some more consistency between `condition` and `when-then-otherwise` - 1 less thing to think about
This isn't a runtime concern, just to satisfy typing
Mostly pushing this as an experiment. Shortens signatures, but there is a cost to the indirection vega#3567 (comment)
Then
annotations, simplify autocompletionThen
annotations, autocompletion, docs
@@ -730,7 +767,7 @@ def test_when_then_interactive() -> None: | |||
.encode( | |||
x="IMDB_Rating:Q", | |||
y="Rotten_Tomatoes_Rating:Q", | |||
color=alt.when(predicate).then(alt.value("grey")), # type: ignore[arg-type] | |||
color=alt.when(predicate).then(alt.value("grey")), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not requiring this # type: ignore
demonstrates that #3552 is fixed
Fixes #3552
Overview of issues by @binste in comment
The draft PR is very helpful! Trying to summarise/reword it for myself:
Then
should not inherit fromSchemaBase
as it muddies autocompletionSchemaBase
pass due toalt.condition
andalt.when().then().otherwise()
. Can beSchemaBase
or a protocolSchemaLike
. If I understand it correctly, we mainly introduce it now to make these types work.How about
Condition
is not yet used anywhere in the codebase and it makes it clearer what we intend to do with the protocol. What do you think?Tasks
Then
inChart.encode()
, without subclassingSchemaBase
Chart.encode()
annotations08bf16e
(#3567)(Condition|Schema)Like
d20109e
(#3567)TypeAlias|TypedDict
inapi.py
0a6d599
(#3567)0a93309
(#3567)Related
0bdfa72
(#3536)Note
This PR started as a draft proposal for addressing multiple related issues.
See #3552 (comment) for further info
Draft PR Demo
Purely demonstrating type checking behavior for #3552 (comment)
Extended chart_encode
Note
mypy
failing is expected and part of the demonstration