Skip to content

Commit

Permalink
Merge branch 'main' into versioning-policy
Browse files Browse the repository at this point in the history
  • Loading branch information
dangotbanned committed Jul 22, 2024
2 parents 71d4856 + 3f96b74 commit f465b6c
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 60 deletions.
3 changes: 2 additions & 1 deletion altair/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from .html import spec_to_html
from .plugin_registry import PluginRegistry
from .deprecation import AltairDeprecationWarning, deprecated, deprecated_warn
from .schemapi import Undefined, Optional
from .schemapi import Undefined, Optional, is_undefined


__all__ = (
Expand All @@ -28,6 +28,7 @@
"display_traceback",
"infer_encoding_types",
"infer_vegalite_type_for_pandas",
"is_undefined",
"parse_shorthand",
"sanitize_narwhals_dataframe",
"sanitize_pandas_dataframe",
Expand Down
13 changes: 13 additions & 0 deletions altair/utils/schemapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,19 @@ def func_2(
"""


def is_undefined(obj: Any) -> TypeIs[UndefinedType]:
"""Type-safe singleton check for `UndefinedType`.
Notes
-----
- Using `obj is Undefined` does not narrow from `UndefinedType` in a union.
- Due to the assumption that other `UndefinedType`'s could exist.
- Current [typing spec advises](https://typing.readthedocs.io/en/latest/spec/concepts.html#support-for-singleton-types-in-unions) using an `Enum`.
- Otherwise, requires an explicit guard to inform the type checker.
"""
return obj is Undefined


class SchemaBase:
"""Base class for schema wrappers.
Expand Down
85 changes: 36 additions & 49 deletions altair/vegalite/v5/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
Union,
TYPE_CHECKING,
TypeVar,
Sequence,
Protocol,
)
from typing_extensions import TypeAlias
Expand Down Expand Up @@ -45,10 +44,6 @@
from typing import TypedDict
else:
from typing_extensions import TypedDict
if sys.version_info >= (3, 12):
from typing import TypeAliasType
else:
from typing_extensions import TypeAliasType

if TYPE_CHECKING:
from ...utils.core import DataFrameLike
Expand Down Expand Up @@ -107,7 +102,6 @@
TopLevelSelectionParameter,
SelectionParameter,
InlineDataset,
UndefinedType,
)
from altair.expr.core import (
BinaryExpression,
Expand All @@ -126,26 +120,12 @@
AggregateOp_T,
MultiTimeUnit_T,
SingleTimeUnit_T,
OneOrSeq,
)


ChartDataType: TypeAlias = Optional[Union[DataType, core.Data, str, core.Generator]]
_TSchemaBase = TypeVar("_TSchemaBase", bound=core.SchemaBase)
_T = TypeVar("_T")
_OneOrSeq = TypeAliasType("_OneOrSeq", Union[_T, Sequence[_T]], type_params=(_T,))
"""One of ``_T`` specified type(s), or a `Sequence` of such.
Examples
--------
The parameters ``short``, ``long`` accept the same range of types::
# ruff: noqa: UP006, UP007
def func(
short: _OneOrSeq[str | bool | float],
long: Union[str, bool, float, Sequence[Union[str, bool, float]],
): ...
"""


# ------------------------------------------------------------------------
Expand Down Expand Up @@ -181,7 +161,7 @@ def _consolidate_data(data: Any, context: Any) -> Any:
kwds = {}

if isinstance(data, core.InlineData):
if _is_undefined(data.name) and not _is_undefined(data.values):
if utils.is_undefined(data.name) and not utils.is_undefined(data.values):
if isinstance(data.values, core.InlineDataset):
values = data.to_dict()["values"]
else:
Expand All @@ -192,7 +172,7 @@ def _consolidate_data(data: Any, context: Any) -> Any:
values = data["values"]
kwds = {k: v for k, v in data.items() if k != "values"}

if not _is_undefined(values):
if not utils.is_undefined(values):
name = _dataset_name(values)
data = core.NamedData(name=name, **kwds)
context.setdefault("datasets", {})[name] = values
Expand Down Expand Up @@ -416,7 +396,7 @@ def _from_expr(self, expr) -> SelectionExpression:

def check_fields_and_encodings(parameter: Parameter, field_name: str) -> bool:
param = parameter.param
if _is_undefined(param) or isinstance(param, core.VariableParameter):
if utils.is_undefined(param) or isinstance(param, core.VariableParameter):
return False
for prop in ["fields", "encodings"]:
try:
Expand Down Expand Up @@ -485,19 +465,6 @@ def _is_test_predicate(obj: Any) -> TypeIs[_TestPredicateType]:
return isinstance(obj, (str, _expr_core.Expression, core.PredicateComposition))


def _is_undefined(obj: Any) -> TypeIs[UndefinedType]:
"""Type-safe singleton check for `UndefinedType`.
Notes
-----
- Using `obj is Undefined` does not narrow from `UndefinedType` in a union.
- Due to the assumption that other `UndefinedType`'s could exist.
- Current [typing spec advises](https://typing.readthedocs.io/en/latest/spec/concepts.html#support-for-singleton-types-in-unions) using an `Enum`.
- Otherwise, requires an explicit guard to inform the type checker.
"""
return obj is Undefined


def _get_predicate_expr(p: Parameter) -> Optional[str | SchemaBase]:
# https://vega.github.io/vega-lite/docs/predicate.html
return getattr(p.param, "expr", Undefined)
Expand All @@ -509,7 +476,7 @@ def _predicate_to_condition(
condition: _ConditionType
if isinstance(predicate, Parameter):
predicate_expr = _get_predicate_expr(predicate)
if predicate.param_type == "selection" or _is_undefined(predicate_expr):
if predicate.param_type == "selection" or utils.is_undefined(predicate_expr):
condition = {"param": predicate.name}
if isinstance(empty, bool):
condition["empty"] = empty
Expand Down Expand Up @@ -585,7 +552,7 @@ class _ConditionExtra(TypedDict, closed=True, total=False): # type: ignore[call
param: Parameter | str
test: _TestPredicateType
value: Any
__extra_items__: _StatementType | _OneOrSeq[_LiteralValue]
__extra_items__: _StatementType | OneOrSeq[_LiteralValue]


_Condition: TypeAlias = _ConditionExtra
Expand Down Expand Up @@ -696,7 +663,7 @@ def _parse_when(
**constraints: _FieldEqualType,
) -> _ConditionType:
composed: _PredicateType
if _is_undefined(predicate):
if utils.is_undefined(predicate):
if more_predicates or constraints:
composed = _parse_when_compose(more_predicates, constraints)
else:
Expand Down Expand Up @@ -1106,7 +1073,7 @@ def param(
empty_remap = {"none": False, "all": True}
parameter = Parameter(name)

if not _is_undefined(empty):
if not utils.is_undefined(empty):
if isinstance(empty, bool) and not isinstance(empty, str):
parameter.empty = empty
elif empty in empty_remap:
Expand Down Expand Up @@ -1470,28 +1437,42 @@ def binding_range(**kwargs):

@overload
def condition(
predicate: _PredicateType, if_true: _StatementType, if_false: _TSchemaBase, **kwargs
predicate: _PredicateType,
if_true: _StatementType,
if_false: _TSchemaBase,
*,
empty: Optional[bool] = ...,
**kwargs,
) -> _TSchemaBase: ...
@overload
def condition(
predicate: _PredicateType, if_true: str, if_false: str, **kwargs
) -> Never: ...
@overload
def condition(
predicate: _PredicateType, if_true: Map | SchemaBase, if_false: Map | str, **kwargs
predicate: _PredicateType,
if_true: Map | SchemaBase,
if_false: Map | str,
*,
empty: Optional[bool] = ...,
**kwargs,
) -> dict[str, _ConditionType | Any]: ...
@overload
def condition(
predicate: _PredicateType,
if_true: Map | str,
if_false: Map,
*,
empty: Optional[bool] = ...,
**kwargs,
) -> dict[str, _ConditionType | Any]: ...
@overload
def condition(
predicate: _PredicateType, if_true: str, if_false: str, **kwargs
) -> Never: ...
# TODO: update the docstring
def condition(
predicate: _PredicateType,
if_true: _StatementType,
if_false: _StatementType,
*,
empty: Optional[bool] = Undefined,
**kwargs,
) -> SchemaBase | dict[str, _ConditionType | Any]:
"""A conditional attribute or encoding
Expand All @@ -1505,6 +1486,13 @@ def condition(
the spec or object to use if the selection predicate is true
if_false:
the spec or object to use if the selection predicate is false
empty
For selection parameters, the predicate of empty selections returns ``True`` by default.
Override this behavior, with ``empty=False``.
.. note::
When ``predicate`` is a ``Parameter`` that is used more than once,
``alt.condition(..., empty=...)`` provides granular control for each :func:`.condition()`.
**kwargs:
additional keyword args are added to the resulting dict
Expand All @@ -1513,7 +1501,6 @@ def condition(
spec: dict or VegaLiteSchema
the spec that describes the condition
"""
empty = kwargs.pop("empty", Undefined)
condition = _predicate_to_condition(predicate, empty=empty)
return _condition_to_selection(condition, if_true, if_false, **kwargs)

Expand Down Expand Up @@ -1604,7 +1591,7 @@ def to_dict(

copy = _top_schema_base(self).copy(deep=False)
original_data = getattr(copy, "data", Undefined)
if not _is_undefined(original_data):
if not utils.is_undefined(original_data):
try:
data = _to_eager_narwhals_dataframe(original_data)
except TypeError:
Expand Down
21 changes: 19 additions & 2 deletions altair/vegalite/v5/schema/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

from __future__ import annotations

from typing import Any, Literal, Mapping
from typing import Any, Literal, Mapping, Sequence, TypeVar, Union

from typing_extensions import TypeAlias
from typing_extensions import TypeAlias, TypeAliasType

__all__ = [
"AggregateOp_T",
Expand All @@ -32,6 +32,7 @@
"Mark_T",
"MultiTimeUnit_T",
"NonArgAggregateOp_T",
"OneOrSeq",
"Orient_T",
"Orientation_T",
"ProjectionType_T",
Expand Down Expand Up @@ -60,6 +61,22 @@
]


T = TypeVar("T")
OneOrSeq = TypeAliasType("OneOrSeq", Union[T, Sequence[T]], type_params=(T,))
"""One of ``T`` specified type(s), or a `Sequence` of such.
Examples
--------
The parameters ``short``, ``long`` accept the same range of types::
# ruff: noqa: UP006, UP007
def func(
short: OneOrSeq[str | bool | float],
long: Union[str, bool, float, Sequence[Union[str, bool, float]],
): ...
"""

Map: TypeAlias = Mapping[str, Any]
AggregateOp_T: TypeAlias = Literal[
"argmax",
Expand Down
24 changes: 23 additions & 1 deletion tools/generate_schema_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,26 @@ def encode({encode_method_args}) -> Self:
return copy
'''

# NOTE: Not yet reasonable to generalize `TypeAliasType`, `TypeVar`
# Revisit if this starts to become more common
TYPING_EXTRA: Final = '''
T = TypeVar("T")
OneOrSeq = TypeAliasType("OneOrSeq", Union[T, Sequence[T]], type_params=(T,))
"""One of ``T`` specified type(s), or a `Sequence` of such.
Examples
--------
The parameters ``short``, ``long`` accept the same range of types::
# ruff: noqa: UP006, UP007
def func(
short: OneOrSeq[str | bool | float],
long: Union[str, bool, float, Sequence[Union[str, bool, float]],
): ...
"""
'''


class SchemaGenerator(codegen.SchemaGenerator):
schema_class_template = textwrap.dedent(
Expand Down Expand Up @@ -815,7 +835,9 @@ def vegalite_main(skip_download: bool = False) -> None:
)
print(msg)
TypeAliasTracer.update_aliases(("Map", "Mapping[str, Any]"))
TypeAliasTracer.write_module(fp_typing, header=HEADER)
TypeAliasTracer.write_module(
fp_typing, "OneOrSeq", header=HEADER, extra=TYPING_EXTRA
)
# Write the pre-generated modules
for fp, contents in files.items():
print(f"Writing\n {schemafile!s}\n ->{fp!s}")
Expand Down
13 changes: 13 additions & 0 deletions tools/schemapi/schemapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,19 @@ def func_2(
"""


def is_undefined(obj: Any) -> TypeIs[UndefinedType]:
"""Type-safe singleton check for `UndefinedType`.
Notes
-----
- Using `obj is Undefined` does not narrow from `UndefinedType` in a union.
- Due to the assumption that other `UndefinedType`'s could exist.
- Current [typing spec advises](https://typing.readthedocs.io/en/latest/spec/concepts.html#support-for-singleton-types-in-unions) using an `Enum`.
- Otherwise, requires an explicit guard to inform the type checker.
"""
return obj is Undefined


class SchemaBase:
"""Base class for schema wrappers.
Expand Down
Loading

0 comments on commit f465b6c

Please sign in to comment.