Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into interactive_tooltip…
Browse files Browse the repository at this point in the history
…_legend
  • Loading branch information
dangotbanned committed Oct 11, 2024
2 parents 4b12df6 + 02ad17d commit d9f8850
Show file tree
Hide file tree
Showing 69 changed files with 18,783 additions and 8,594 deletions.
6 changes: 6 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
changelog:
categories:
- title: Breaking
labels:
- breaking
- title: Deprecation
labels:
- deprecation
- title: Enhancements
labels:
- enhancement
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,6 @@ Untitled*.ipynb

# hatch, doc generation
data.json

# type stubs
typings/
2 changes: 1 addition & 1 deletion NOTES_FOR_MAINTAINERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,4 @@ To cut a new release of Altair, follow the steps outlined in

## Web analytics
We use the privacy-friendly [plausible.io](https://plausible.io/) for tracking usage statistics of our documentation.
It is hosted on [https://views.scientific-python.org](https://views.scientific-python.org). To view the stats, you need an account. Ask another maintainer to invite you.
It is hosted on [https://views.scientific-python.org](https://views.scientific-python.org). You can view the stats [here](https://views.scientific-python.org/altair-viz.github.io). To get an account to edit the settings of the web tracking, ask another maintainer.
55 changes: 37 additions & 18 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

hatch env prune

2. Make certain your branch is in sync with head:
2. Make certain your branch is in sync with head. If you work on a fork, replace `origin` with `upstream`:

git pull upstream main
git pull origin main

3. Do a clean doc build:

Expand All @@ -17,48 +17,67 @@
Navigate to http://localhost:8000 and ensure it looks OK (particularly
do a visual scan of the gallery thumbnails).

4. Update version to, e.g. 5.0.0:
4. Create a new release branch:

git switch -c version_5.0.0

5. Update version to, e.g. 5.0.0:

- in ``altair/__init__.py``
- in ``doc/conf.py``

5. Commit change and push to main:
6. Commit changes and push:

git add . -u
git commit -m "chore: bump version to 5.0.0"
git push upstream main
git commit -m "chore: Bump version to 5.0.0"
git push

7. Merge release branch into main, make sure that all required checks pass

6. Tag the release:
8. Tag the release:

git tag -a v5.0.0 -m "version 5.0.0 release"
git push upstream v5.0.0
git push origin v5.0.0

7. Build source & wheel distributions:
9. On main, build source & wheel distributions. If you work on a fork, replace `origin` with `upstream`:

git switch main
git pull origin main
hatch clean # clean old builds & distributions
hatch build # create a source distribution and universal wheel

8. publish to PyPI (Requires correct PyPI owner permissions):
10. publish to PyPI (Requires correct PyPI owner permissions):

hatch publish

9. build and publish docs (Requires write-access to altair-viz/altair-viz.github.io):
11. build and publish docs (Requires write-access to altair-viz/altair-viz.github.io):

hatch run doc:publish-clean-build

10. update version to, e.g. 5.1.0dev:
12. On main, tag the release. If you work on a fork, replace `origin` with `upstream`:

git tag -a v5.0.0 -m "Version 5.0.0 release"
git push origin v5.0.0

13. Create a new branch:

git switch -c maint_5.1.0dev

14. Update version and add 'dev' suffix, e.g. 5.1.0dev:

- in ``altair/__init__.py``
- in ``doc/conf.py``

11. Commit change and push to main:
15. Commit changes and push:

git add . -u
git commit -m "chore: bump version to 5.1.0dev"
git push upstream main
git commit -m "chore: Bump version to 5.1.0dev"
git push
16. Merge maintenance branch into main

12. Double-check that a conda-forge pull request is generated from the updated
pip package by the conda-forge bot (may take up to ~an hour):
17. Double-check that a conda-forge pull request is generated from the updated
pip package by the conda-forge bot (may take up to several hours):
https://github.com/conda-forge/altair-feedstock/pulls

13. Publish a new release in https://github.com/vega/altair/releases/
18. Publish a new release in https://github.com/vega/altair/releases/
1 change: 1 addition & 0 deletions altair/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@
"mixins",
"param",
"parse_shorthand",
"register_theme",
"renderers",
"repeat",
"sample",
Expand Down
2 changes: 1 addition & 1 deletion altair/expr/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,4 @@ def __repr__(self) -> str:
return f"{self.group}[{self.name!r}]"


IntoExpression: TypeAlias = Union[bool, None, str, OperatorMixin, Dict[str, Any]]
IntoExpression: TypeAlias = Union[bool, None, str, float, OperatorMixin, Dict[str, Any]]
4 changes: 4 additions & 0 deletions altair/typing.py → altair/typing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@
"ChartType",
"EncodeKwds",
"Optional",
"ThemeConfig",
"is_chart_type",
"theme",
]

from altair.typing import theme
from altair.typing.theme import ThemeConfig
from altair.utils.schemapi import Optional
from altair.vegalite.v5.api import ChartType, is_chart_type
from altair.vegalite.v5.schema.channels import (
Expand Down
1 change: 1 addition & 0 deletions altair/typing/theme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from altair.vegalite.v5.schema._config import * # noqa: F403
3 changes: 2 additions & 1 deletion altair/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
from .deprecation import AltairDeprecationWarning, deprecated, deprecated_warn
from .html import spec_to_html
from .plugin_registry import PluginRegistry
from .schemapi import Optional, SchemaBase, Undefined, is_undefined
from .schemapi import Optional, SchemaBase, SchemaLike, Undefined, is_undefined

__all__ = (
"SHORTHAND_KEYS",
"AltairDeprecationWarning",
"Optional",
"PluginRegistry",
"SchemaBase",
"SchemaLike",
"Undefined",
"deprecated",
"deprecated_warn",
Expand Down
136 changes: 79 additions & 57 deletions altair/utils/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,58 @@
from copy import deepcopy
from itertools import groupby
from operator import itemgetter
from typing import TYPE_CHECKING, Any, Callable, Iterator, Literal, TypeVar, cast
from typing import (
TYPE_CHECKING,
Any,
Callable,
Iterator,
Literal,
TypeVar,
cast,
overload,
)

import jsonschema
import narwhals.stable.v1 as nw
from narwhals.dependencies import get_polars, is_pandas_dataframe
from narwhals.typing import IntoDataFrame

from altair.utils.schemapi import SchemaBase, Undefined
from altair.utils.schemapi import SchemaBase, SchemaLike, Undefined

if sys.version_info >= (3, 12):
from typing import Protocol, runtime_checkable
from typing import Protocol, TypeAliasType, runtime_checkable
else:
from typing_extensions import Protocol, runtime_checkable
from typing_extensions import Protocol, TypeAliasType, runtime_checkable
if sys.version_info >= (3, 10):
from typing import ParamSpec
from typing import Concatenate, ParamSpec
else:
from typing_extensions import ParamSpec
from typing_extensions import Concatenate, ParamSpec


if TYPE_CHECKING:
import typing as t
from types import ModuleType

import pandas as pd
from narwhals.typing import IntoExpr

from altair.utils._dfi_types import DataFrame as DfiDataFrame
from altair.vegalite.v5.schema._typing import StandardType_T as InferredVegaLiteType

V = TypeVar("V")
P = ParamSpec("P")
TIntoDataFrame = TypeVar("TIntoDataFrame", bound=IntoDataFrame)
T = TypeVar("T")
P = ParamSpec("P")
R = TypeVar("R")

WrapsFunc = TypeAliasType("WrapsFunc", Callable[..., R], type_params=(R,))
WrappedFunc = TypeAliasType("WrappedFunc", Callable[P, R], type_params=(P, R))
# NOTE: Requires stringized form to avoid `< (3, 11)` issues
# See: https://github.com/vega/altair/actions/runs/10667859416/job/29567290871?pr=3565
WrapsMethod = TypeAliasType(
"WrapsMethod", "Callable[Concatenate[T, ...], R]", type_params=(T, R)
)
WrappedMethod = TypeAliasType(
"WrappedMethod", Callable[Concatenate[T, P], R], type_params=(T, P, R)
)


@runtime_checkable
Expand Down Expand Up @@ -709,39 +729,63 @@ def infer_vegalite_type_for_narwhals(
raise ValueError(msg)


def use_signature(obj: Callable[P, Any]): # -> Callable[..., Callable[P, V]]:
"""Apply call signature and documentation of `obj` to the decorated method."""
def use_signature(tp: Callable[P, Any], /):
"""
Use the signature and doc of ``tp`` for the decorated callable ``cb``.
def decorate(func: Callable[..., V]) -> Callable[P, V]:
# call-signature of func is exposed via __wrapped__.
# we want it to mimic obj.__init__
- **Overload 1**: Decorating method
- **Overload 2**: Decorating function
# error: Accessing "__init__" on an instance is unsound,
# since instance.__init__ could be from an incompatible subclass [misc]
wrapped = (
obj.__init__ if (isinstance(obj, type) and issubclass(obj, object)) else obj # type: ignore [misc]
)
func.__wrapped__ = wrapped # type: ignore[attr-defined]
func._uses_signature = obj # type: ignore[attr-defined]

# Supplement the docstring of func with information from obj
if doc_in := obj.__doc__:
doc_lines = doc_in.splitlines(keepends=True)[1:]
# Patch in a reference to the class this docstring is copied from,
# to generate a hyperlink.
line_1 = f"{func.__doc__ or f'Refer to :class:`{obj.__name__}`'}\n"
func.__doc__ = "".join((line_1, *doc_lines))
return func
Returns
-------
**Adding the annotation breaks typing**:
Overload[Callable[[WrapsMethod[T, R]], WrappedMethod[T, P, R]], Callable[[WrapsFunc[R]], WrappedFunc[P, R]]]
"""

@overload
def decorate(cb: WrapsMethod[T, R], /) -> WrappedMethod[T, P, R]: ...

@overload
def decorate(cb: WrapsFunc[R], /) -> WrappedFunc[P, R]: ...

def decorate(cb: WrapsFunc[R], /) -> WrappedMethod[T, P, R] | WrappedFunc[P, R]:
"""
Raises when no doc was found.
Notes
-----
- Reference to ``tp`` is stored in ``cb.__wrapped__``.
- The doc for ``cb`` will have a ``.rst`` link added, referring to ``tp``.
"""
cb.__wrapped__ = getattr(tp, "__init__", tp) # type: ignore[attr-defined]

if doc_in := tp.__doc__:
line_1 = f"{cb.__doc__ or f'Refer to :class:`{tp.__name__}`'}\n"
cb.__doc__ = "".join((line_1, *doc_in.splitlines(keepends=True)[1:]))
return cb
else:
msg = f"Found no doc for {obj!r}"
msg = f"Found no doc for {tp!r}"
raise AttributeError(msg)

return decorate


@overload
def update_nested(
original: t.MutableMapping[Any, Any],
update: t.Mapping[Any, Any],
copy: Literal[False] = ...,
) -> t.MutableMapping[Any, Any]: ...
@overload
def update_nested(
original: t.Mapping[Any, Any],
update: t.Mapping[Any, Any],
copy: Literal[True],
) -> t.MutableMapping[Any, Any]: ...
def update_nested(
original: Any,
update: t.Mapping[Any, Any],
copy: bool = False,
) -> t.MutableMapping[Any, Any]:
"""
Expand Down Expand Up @@ -812,22 +856,6 @@ class _ChannelCache:
channel_to_name: dict[type[SchemaBase], str]
name_to_channel: dict[str, dict[_ChannelType, type[SchemaBase]]]

@classmethod
def from_channels(cls, channels: ModuleType, /) -> _ChannelCache:
# - This branch is only kept for tests that depend on mocking `channels`.
# - No longer needs to pass around `channels` reference and rebuild every call.
c_to_n = {
c: c._encoding_name
for c in channels.__dict__.values()
if isinstance(c, type)
and issubclass(c, SchemaBase)
and hasattr(c, "_encoding_name")
}
self = cls.__new__(cls)
self.channel_to_name = c_to_n
self.name_to_channel = _invert_group_channels(c_to_n)
return self

@classmethod
def from_cache(cls) -> _ChannelCache:
global _CHANNEL_CACHE
Expand All @@ -853,6 +881,8 @@ def _wrap_in_channel(self, obj: Any, encoding: str, /):
obj = {"shorthand": obj}
elif isinstance(obj, (list, tuple)):
return [self._wrap_in_channel(el, encoding) for el in obj]
elif isinstance(obj, SchemaLike):
obj = obj.to_dict()
if channel := self.name_to_channel.get(encoding):
tp = channel["value" if "value" in obj else "field"]
try:
Expand Down Expand Up @@ -925,9 +955,7 @@ def _reduce(it: Iterator[tuple[type[Any], str]]) -> Any:
return {k: _reduce(chans) for k, chans in grouper}


def infer_encoding_types(
args: tuple[Any, ...], kwargs: dict[str, Any], channels: ModuleType | None = None
):
def infer_encoding_types(args: tuple[Any, ...], kwargs: dict[str, Any]):
"""
Infer typed keyword arguments for args and kwargs.
Expand All @@ -937,20 +965,14 @@ def infer_encoding_types(
Sequence of function args
kwargs : MutableMapping
Dict of function kwargs
channels : ModuleType
The module containing all altair encoding channel classes.
Returns
-------
kwargs : dict
All args and kwargs in a single dict, with keys and types
based on the channels mapping.
"""
cache = (
_ChannelCache.from_channels(channels)
if channels
else _ChannelCache.from_cache()
)
cache = _ChannelCache.from_cache()
# First use the mapping to convert args to kwargs based on their types.
for arg in args:
el = next(iter(arg), None) if isinstance(arg, (list, tuple)) else arg
Expand Down
Loading

0 comments on commit d9f8850

Please sign in to comment.