diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index c1707d166..4964f27b6 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -41,7 +41,7 @@ spell_literal, ) from tools.vega_expr import write_expr_module -from tools.versioning import VERSIONS, inline_versions_literal +from tools.versioning import VERSIONS if TYPE_CHECKING: from collections.abc import Iterable, Iterator @@ -633,7 +633,7 @@ def copy_schemapi_util() -> None: destination_fp.open("w", encoding="utf8") as dest, ): dest.write(HEADER_COMMENT) - dest.writelines(chain(source.readlines(), inline_versions_literal("VERSIONS"))) + dest.writelines(chain(source.readlines(), VERSIONS.iter_inline_literal())) ruff.format(destination_fp) @@ -1377,8 +1377,6 @@ def generate_encoding_artifacts( def main() -> None: - from tools import versioning - parser = argparse.ArgumentParser( prog="generate_schema_wrapper.py", description="Generate the Altair package." ) @@ -1386,14 +1384,10 @@ def main() -> None: "--skip-download", action="store_true", help="skip downloading schema files" ) args = parser.parse_args() - versioning.update_all_versions() + VERSIONS.update_all() copy_schemapi_util() vegalite_main(args.skip_download) - write_expr_module( - vlc.get_vega_version(), - output=EXPR_FILE, - header=HEADER_COMMENT, - ) + write_expr_module(VERSIONS.vlc_vega, output=EXPR_FILE, header=HEADER_COMMENT) # The modules below are imported after the generation of the new schema files # as these modules import Altair. This allows them to use the new changes diff --git a/tools/versioning.py b/tools/versioning.py index a4487fa9f..7df96466b 100644 --- a/tools/versioning.py +++ b/tools/versioning.py @@ -25,7 +25,7 @@ import sys from collections import deque from pathlib import Path -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Any, ClassVar, Literal if sys.version_info >= (3, 11): import tomllib @@ -38,7 +38,14 @@ from tools.schemapi.utils import spell_literal if TYPE_CHECKING: - from collections.abc import Iterable, Mapping, Sequence + from collections.abc import ( + ItemsView, + Iterable, + Iterator, + KeysView, + Mapping, + Sequence, + ) if sys.version_info >= (3, 11): from typing import LiteralString @@ -49,20 +56,22 @@ else: from typing_extensions import TypeAlias -__all__ = ["VERSIONS", "update_all_versions"] +__all__ = ["VERSIONS"] -_TABLE_PATH: Sequence[LiteralString] = "tool", "altair", "vega" _REPO_ROOT: Path = Path(__file__).parent.parent _JUPYTER_INDEX = "altair/jupyter/js/index.js" +_PYPROJECT: Literal["pyproject.toml"] = "pyproject.toml" VegaProject: TypeAlias = Literal[ "vega-datasets", "vega-embed", "vega-lite", "vegafusion", "vl-convert-python" ] -VERSIONS: Mapping[VegaProject, str] + +VERSIONS: _Versions +"""Singleton ``_Versions`` instance.""" def _read_pyproject_toml(fp: Path | None = None, /) -> dict[str, Any]: - source = fp or Path(__file__).parent.parent / "pyproject.toml" + source = fp or Path(__file__).parent.parent / _PYPROJECT return tomllib.loads(source.read_text("utf-8")) @@ -74,63 +83,139 @@ def _keypath(mapping: Mapping[str, Any], path: Iterable[str], /) -> Any: return mut -def update_vega_embed() -> None: - """Updates the **Vega-Lite** version used in ``JupyterChart``.""" - fp = _REPO_ROOT / _JUPYTER_INDEX - embed = VERSIONS["vega-embed"] - vega = parse_version(vlc.get_vega_version()).major - lite = VERSIONS["vega-lite"].lstrip("v") - stmt = f'import vegaEmbed from "https://esm.sh/vega-embed@{embed}?deps=vega@{vega}&deps=vega-lite@{lite}";\n' - - with fp.open("r", encoding="utf-8", newline="\n") as f: - lines = deque(f.readlines()) - lines.popleft() - print(f"Updating import in {fp.as_posix()!r}, to:\n {stmt!r}") - lines.appendleft(stmt) - with fp.open("w", encoding="utf-8", newline="\n") as f: - f.writelines(lines) - - -def inline_versions_literal(name: str, /) -> Iterable[str]: +class _Versions: + _TABLE_PATH: ClassVar[Sequence[LiteralString]] = "tool", "altair", "vega" """ - Yields the ``[tool.altair.vega]`` table as an inline ``dict``. - - Includes a type annotation and docstring. + The table header path split by ``"."``:: - Parameters - ---------- - name - Variable name for the literal. - - Notes - ----- - - Write at the bottom of ``altair.utils.schemapi``. - - Used in ``altair.utils._importers``. + [tool.altair.vega] -> "tool", "altair", "vega" """ - ann = f"Mapping[{spell_literal(VERSIONS)}, str]" - table = f"[{'.'.join(_TABLE_PATH)}]" - yield f"{name}: {ann} = {VERSIONS!r}\n" - yield '"""\n' - yield ( - "Version pins for non-``python`` `vega projects`_.\n\n" - "Notes\n" - "-----\n" - f"When cutting a new release, make sure to update ``{table}`` in ``pyproject.toml``.\n\n" - ".. _vega projects:\n" - " https://github.com/vega\n" - ) - yield '"""\n' - - -def update_all_versions() -> None: - print("Updating Vega project pins") - update_vega_embed() - -def __getattr__(name: str) -> Mapping[VegaProject, str]: + _CONST_NAME: ClassVar[Literal["VERSIONS"]] = "VERSIONS" + """Variable name for the exported literal.""" + + _mapping: Mapping[VegaProject, str] + + def __init__(self) -> None: + self._mapping = _keypath(_read_pyproject_toml(), self._TABLE_PATH) + + def __getitem__(self, key: VegaProject) -> str: + return self._mapping[key] + + def __repr__(self) -> str: + return repr(self._mapping) + + def projects(self) -> KeysView[VegaProject]: + return self._mapping.keys() + + def items(self) -> ItemsView[VegaProject, str]: + return self._mapping.items() + + @property + def vlc_vega(self) -> str: + """ + Returns version of `Vega`_ bundled with `vl-convert`_. + + .. _Vega: + https://github.com/vega/vega + .. _vl-convert: + https://github.com/vega/vl-convert + """ + return vlc.get_vega_version() + + @property + def vlc_vega_embed(self) -> str: + """ + Returns version of `Vega-Embed`_ bundled with `vl-convert`_. + + .. _Vega-Embed: + https://github.com/vega/vega-embed + .. _vl-convert: + https://github.com/vega/vl-convert + """ + return vlc.get_vega_embed_version() + + @property + def vlc_vega_themes(self) -> str: + """ + Returns version of `Vega-Themes`_ bundled with `vl-convert`_. + + .. _Vega-Themes: + https://github.com/vega/vega-themes + .. _vl-convert: + https://github.com/vega/vl-convert. + """ + return vlc.get_vega_themes_version() + + @property + def vlc_vegalite(self) -> list[str]: + """ + Returns versions of `Vega-Lite`_ bundled with `vl-convert`_. + + .. _Vega-Lite: + https://github.com/vega/vega-lite + .. _vl-convert: + https://github.com/vega/vl-convert + """ + return vlc.get_vegalite_versions() + + @property + def _annotation(self) -> str: + return f"Mapping[{spell_literal(self.projects())}, str]" + + @property + def _header(self) -> str: + return f"[{'.'.join(self._TABLE_PATH)}]" + + def iter_inline_literal(self) -> Iterator[str]: + """ + Yields the ``[tool.altair.vega]`` table as an inline ``dict``. + + Includes a type annotation and docstring. + + Notes + ----- + - Write at the bottom of ``altair.utils.schemapi``. + - Used in ``altair.utils._importers``. + """ + yield f"{self._CONST_NAME}: {self._annotation} = {self!r}\n" + yield '"""\n' + yield ( + "Version pins for non-``python`` `vega projects`_.\n\n" + "Notes\n" + "-----\n" + f"When cutting a new release, make sure to update ``{self._header}`` in ``pyproject.toml``.\n\n" + ".. _vega projects:\n" + " https://github.com/vega\n" + ) + yield '"""\n' + + def update_all(self) -> None: + """Update all static version pins.""" + print("Updating Vega project pins") + self.update_vega_embed() + + def update_vega_embed(self) -> None: + """Updates the **Vega-Lite** version used in ``JupyterChart``.""" + fp = _REPO_ROOT / _JUPYTER_INDEX + embed = self["vega-embed"] + vega = parse_version(self.vlc_vega).major + vegalite = self["vega-lite"].lstrip("v") + stmt = f'import vegaEmbed from "https://esm.sh/vega-embed@{embed}?deps=vega@{vega}&deps=vega-lite@{vegalite}";\n' + + with fp.open("r", encoding="utf-8", newline="\n") as f: + lines = deque(f.readlines()) + lines.popleft() + print(f"Updating import in {fp.as_posix()!r}, to:\n {stmt!r}") + lines.appendleft(stmt) + with fp.open("w", encoding="utf-8", newline="\n") as f: + f.writelines(lines) + + +def __getattr__(name: str) -> _Versions: if name == "VERSIONS": global VERSIONS - VERSIONS = _keypath(_read_pyproject_toml(), _TABLE_PATH) + VERSIONS = _Versions() return VERSIONS else: msg = f"module {__name__!r} has no attribute {name!r}"