From a60e8cc28a481f14c5d1b168bc1aad8dcf64b3c3 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 18:14:19 +0100
Subject: [PATCH 01/77] feat: Adds `altair.tools.schemapi.vega_expr`
---
tools/schemapi/vega_expr.py | 1 +
1 file changed, 1 insertion(+)
create mode 100644 tools/schemapi/vega_expr.py
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
new file mode 100644
index 000000000..9d48db4f9
--- /dev/null
+++ b/tools/schemapi/vega_expr.py
@@ -0,0 +1 @@
+from __future__ import annotations
From e7dca1c3130a6289ab68e62cad53902b846b55b1 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 18:20:00 +0100
Subject: [PATCH 02/77] chore: Add imports
---
tools/schemapi/vega_expr.py | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 9d48db4f9..78e5532d7 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -1 +1,27 @@
from __future__ import annotations
+
+import dataclasses
+import functools
+import keyword
+import re
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Iterable, Iterator, Literal, Sequence, overload
+from urllib import request
+
+import mistune
+import mistune.util
+
+from tools.schemapi.utils import RSTParse, RSTRenderer
+
+if TYPE_CHECKING:
+ import sys
+ from re import Pattern
+
+ from mistune import BaseRenderer, BlockParser, BlockState, InlineParser
+
+ if sys.version_info >= (3, 11):
+ from typing import LiteralString, Self, TypeAlias
+ else:
+ from typing_extensions import LiteralString, Self, TypeAlias
+ Token: TypeAlias = "dict[str, Any]"
+
From 49d328d05f3340ee058c92f33f9e27c599518123 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 18:25:33 +0100
Subject: [PATCH 03/77] feat: Add download/ast parse wrapper
---
tools/schemapi/vega_expr.py | 29 ++++++++++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 78e5532d7..81e6d0b47 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -17,7 +17,7 @@
import sys
from re import Pattern
- from mistune import BaseRenderer, BlockParser, BlockState, InlineParser
+ from mistune import BaseRenderer, BlockParser, InlineParser
if sys.version_info >= (3, 11):
from typing import LiteralString, Self, TypeAlias
@@ -25,3 +25,30 @@
from typing_extensions import LiteralString, Self, TypeAlias
Token: TypeAlias = "dict[str, Any]"
+
+EXPRESSIONS_URL = (
+ "https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md"
+)
+
+def download_expressions_md(url: str, /) -> Path:
+ """Download to a temporary file, return that as a ``pathlib.Path``."""
+ tmp, _ = request.urlretrieve(url)
+ fp = Path(tmp)
+ if not fp.exists():
+ msg = (
+ f"Expressions download failed: {fp!s}.\n\n"
+ f"Try manually accessing resource: {url!r}"
+ )
+ raise FileNotFoundError(msg)
+ else:
+ return fp
+
+
+def read_tokens(source: Path, /) -> list[Any]:
+ """
+ Read from ``source``, drop ``BlockState``.
+
+ Factored out to provide accurate typing.
+ """
+ return mistune.create_markdown(renderer="ast").read(source)[0]
+
From 11d759d6c9fdd8110a7082f8c2b0aa3f989cd3bf Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 18:26:57 +0100
Subject: [PATCH 04/77] feat: Define constants
---
tools/schemapi/vega_expr.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 81e6d0b47..47ef97308 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -29,6 +29,12 @@
EXPRESSIONS_URL = (
"https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md"
)
+TYPE: Literal[r"type"] = "type"
+RAW: Literal["raw"] = "raw"
+SOFTBREAK: Literal["softbreak"] = "softbreak"
+TEXT: Literal["text"] = "text"
+CHILDREN: Literal["children"] = "children"
+
def download_expressions_md(url: str, /) -> Path:
"""Download to a temporary file, return that as a ``pathlib.Path``."""
From 0109c8c73f3b2e4f82130644d540f43544b861ac Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 18:29:10 +0100
Subject: [PATCH 05/77] feat: Define some `re` patterns
---
tools/schemapi/vega_expr.py | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 47ef97308..c4ddcd91a 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -29,6 +29,10 @@
EXPRESSIONS_URL = (
"https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md"
)
+
+FUNCTION_DEF_LINE: Pattern[str] = re.compile(r"")
+LIQUID_INCLUDE: Pattern[str] = re.compile(r"( \{% include.+%\})")
+
TYPE: Literal[r"type"] = "type"
RAW: Literal["raw"] = "raw"
SOFTBREAK: Literal["softbreak"] = "softbreak"
@@ -58,3 +62,12 @@ def read_tokens(source: Path, /) -> list[Any]:
"""
return mistune.create_markdown(renderer="ast").read(source)[0]
+
+def strip_include_tag(s: str, /) -> str:
+ """
+ Removes `liquid`_ templating markup.
+
+ .. _liquid:
+ https://shopify.github.io/liquid/
+ """
+ return LIQUID_INCLUDE.sub(r"", s)
From 1f736be91c1d74d20a7fa9d38f19c6616c2c1989 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 18:32:38 +0100
Subject: [PATCH 06/77] feat: Extend `utils.RSTParse` to support external
tokens
---
tools/schemapi/vega_expr.py | 48 ++++++++++++++++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index c4ddcd91a..5a359ae17 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -11,7 +11,8 @@
import mistune
import mistune.util
-from tools.schemapi.utils import RSTParse, RSTRenderer
+from tools.schemapi.utils import RSTParse as _RSTParse
+from tools.schemapi.utils import RSTRenderer
if TYPE_CHECKING:
import sys
@@ -71,3 +72,48 @@ def strip_include_tag(s: str, /) -> str:
https://shopify.github.io/liquid/
"""
return LIQUID_INCLUDE.sub(r"", s)
+
+
+class RSTParse(_RSTParse):
+ """
+ Minor extension to support partial `ast`_ conversion.
+
+ Only need to convert the docstring tokens to `.rst`.
+
+ NOTE
+ ----
+ Once `PR`_ is merged, move this to the parent class and rename
+
+ .. _ast:
+ https://mistune.lepture.com/en/latest/guide.html#abstract-syntax-tree
+ .. _PR:
+ https://github.com/vega/altair/pull/3536
+ """
+
+ def __init__(
+ self,
+ renderer: BaseRenderer,
+ block: BlockParser | None = None,
+ inline: InlineParser | None = None,
+ plugins=None,
+ ) -> None:
+ super().__init__(renderer, block, inline, plugins)
+ if self.renderer is None:
+ msg = "Must provide a renderer, got `None`"
+ raise TypeError(msg)
+ self.renderer: BaseRenderer
+
+ def render_tokens(self, tokens: Iterable[Token], /) -> LiteralString:
+ """
+ Render ast tokens originating from another parser.
+
+ Parameters
+ ----------
+ tokens
+ All tokens will be rendered into a single `.rst` string
+ """
+ state = self.block.state_cls()
+ return self.renderer(self._iter_render(tokens, state), state)
+
+
+parser: RSTParse = RSTParse(RSTRenderer())
From e26083ac721416bac56a521033b46d5b083fff7b Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 18:34:18 +0100
Subject: [PATCH 07/77] feat: Adds `VegaExprParam`
Similar idea to `inspect.Parameter`
---
tools/schemapi/vega_expr.py | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 5a359ae17..cb62eb6bc 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -117,3 +117,27 @@ def render_tokens(self, tokens: Iterable[Token], /) -> LiteralString:
parser: RSTParse = RSTParse(RSTRenderer())
+@dataclasses.dataclass
+class VegaExprParam:
+ name: str
+ required: bool
+ variadic: bool = False
+
+ @classmethod
+ def iter_params(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
+ """Yields an ordered parameter list."""
+ is_required: bool = True
+ for s in raw_texts:
+ if s not in {"(", ")"}:
+ if s == "[":
+ is_required = False
+ continue
+ elif s == "]":
+ is_required = True
+ continue
+ elif s.isalnum():
+ yield cls(s, required=is_required)
+ elif s == "...":
+ yield cls("*args", required=False, variadic=True)
+ else:
+ continue
From 6bb4c27b9160b9de73936722584b8b70cd7fe34a Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 18:36:45 +0100
Subject: [PATCH 08/77] feat: Adds `VegaExprNode`
Similar to `inspect.Signature`
---
tools/schemapi/vega_expr.py | 171 ++++++++++++++++++++++++++++++++++++
1 file changed, 171 insertions(+)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index cb62eb6bc..69e6f6fe0 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -117,6 +117,177 @@ def render_tokens(self, tokens: Iterable[Token], /) -> LiteralString:
parser: RSTParse = RSTParse(RSTRenderer())
+
+@dataclasses.dataclass
+class VegaExprNode:
+ """
+ ``SchemaInfo``-like, but operates on `expressions.md`_.
+
+ .. _expressions.md:
+ https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md
+ """
+
+ name: str
+ _children: Sequence[Token] = dataclasses.field(repr=False)
+ doc: str = ""
+ parameters: list[VegaExprParam] = dataclasses.field(default_factory=list)
+
+ def with_parameters(self) -> Self:
+ raw_texts = self._split_signature_tokens()
+ name = next(raw_texts)
+ # NOTE: Overwriting the with the rendered text
+ if self.name != name:
+ self.name = name
+ self.parameters = list(VegaExprParam.iter_params(raw_texts))
+ return self
+
+ def with_doc(self) -> Self:
+ self.doc = parser.render_tokens(self._doc_tokens())
+ return self
+
+ @functools.cached_property
+ def parameter_names(self) -> frozenset[str]:
+ if self.parameters:
+ return frozenset(param.name for param in self.parameters)
+ else:
+ msg = (
+ f"Cannot provide `parameter_names` until they have been initialized via:\n"
+ f"{type(self).__name__}.with_parameters()"
+ )
+ raise TypeError(msg)
+
+ @property
+ def name_safe(self) -> str:
+ """Use for the method definition, but not when calling internally."""
+ return f"{self.name}_" if self.is_keyword() else self.name
+
+ def _split_signature_tokens(self) -> Iterator[str]:
+ """Very rough splitting/cleaning of relevant token raw text."""
+ it = iter(self)
+ current = next(it)
+ # NOTE: softbreak(s) denote the line the sig appears on
+ while current[TYPE] != SOFTBREAK:
+ current = next(it)
+ current = next(it)
+ while current[TYPE] != SOFTBREAK:
+ # NOTE: This drops html markup tags
+ if current[TYPE] == TEXT:
+ clean = strip_include_tag(current[RAW]).strip(", -")
+ if clean not in {", ", ""}:
+ yield from VegaExprNode.deep_split_punctuation(clean)
+ current = next(it, None)
+ if current is None:
+ break
+
+ def _doc_tokens(self) -> Sequence[Token]:
+ """
+ Return the slice of `self.children` that contains docstring content.
+
+ Works for 100% of cases.
+ """
+ for idx, item in enumerate(self):
+ if item[TYPE] == SOFTBREAK and self[idx + 1][TYPE] == TEXT:
+ return self[idx + 1 :]
+ else:
+ continue
+ msg = f"Expected to find a text node marking the start of docstring content.\nFailed for:\n\n{self!r}"
+ raise NotImplementedError(msg)
+
+ @staticmethod
+ def deep_split_punctuation(s: str, /) -> Iterator[str]:
+ """Deep splitting of ending punctuation."""
+ if s.isalnum():
+ yield s
+ else:
+ end: list[str] = []
+ if s.endswith((")", "]")):
+ end.append(s[-1])
+ s = s[:-1]
+ elif s.endswith("..."):
+ end.append(s[-3:])
+ s = s[:-3]
+ elif s.endswith(" |"):
+ end.append(s[-2:])
+ s = s[:-2]
+ if len(s) == 1:
+ yield s
+ elif len(s) > 1:
+ yield from VegaExprNode.deep_split_punctuation(s)
+ yield from end
+
+ def is_callable(self) -> bool:
+ """
+ Rough filter for excluding `constants`_.
+
+ - Most of the parsing is to handle varying signatures.
+ - Constants can just be referenced by name, so can skip those
+
+ .. _constants:
+ https://vega.github.io/vega/docs/expressions/#constants
+ """
+ name = self.name
+ if name.startswith("string_"):
+ # HACK: There are string/array functions that overlap
+ # - the `.md` handles this by prefixing the ` bool:
+ """
+ ``Vega`` `bound variables`_.
+
+ .. _bound variables:
+ https://vega.github.io/vega/docs/expressions/#bound-variables
+ """
+ RESERVED_NAMES: set[str] = {"datum", "event", "signal"}
+ return self.name in RESERVED_NAMES
+
+ def is_overloaded(self) -> bool:
+ """
+ Covers the `color functions`_.
+
+ These look like:
+
+ lab(l, a, b[, opacity]) | lab(specifier)
+
+ .. _color functions:
+ https://vega.github.io/vega/docs/expressions/#color-functions
+ """
+ for idx, item in enumerate(self):
+ if item[TYPE] == TEXT and item.get(RAW, "") == "]) |":
+ return self[idx + 1][TYPE] == SOFTBREAK
+ else:
+ continue
+ return False
+
+ def is_keyword(self) -> bool:
+ return keyword.iskeyword(self.name)
+
+ def __iter__(self) -> Iterator[Token]:
+ yield from self._children
+
+ @overload
+ def __getitem__(self, index: int) -> Token: ...
+ @overload
+ def __getitem__(self, index: slice) -> Sequence[Token]: ...
+ def __getitem__(self, index: int | slice) -> Token | Sequence[Token]:
+ return self._children.__getitem__(index)
+
+
@dataclasses.dataclass
class VegaExprParam:
name: str
From d1bf70885fd9c0c3ba1e444c5496bedbab83395c Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 18:39:50 +0100
Subject: [PATCH 09/77] feat: Adds `parse_expressions`
---
tools/schemapi/vega_expr.py | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 69e6f6fe0..4d8026cfe 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -312,3 +312,21 @@ def iter_params(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
yield cls("*args", required=False, variadic=True)
else:
continue
+
+
+def parse_expressions(url: str, /) -> Iterator[tuple[str, VegaExprNode]]:
+ """Download, read markdown and iteratively parse into signature representations."""
+ for tok in read_tokens(download_expressions_md(url)):
+ if (
+ (children := tok.get(CHILDREN)) is not None
+ and (child := next(iter(children)).get(RAW)) is not None
+ and (match := FUNCTION_DEF_LINE.match(child))
+ ):
+ node = VegaExprNode(match[1], children)
+ if node.is_callable():
+ yield node.name, node.with_parameters().with_doc()
+ request.urlcleanup()
+
+
+def test_parse() -> dict[str, VegaExprNode]:
+ return dict(parse_expressions(EXPRESSIONS_URL))
From 3e62bd94505d8c77be9eb028e290a07f6b69cae8 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 18:56:04 +0100
Subject: [PATCH 10/77] feat(DRAFT): Add `VegaExprNode._doc_post_process`
---
tools/schemapi/vega_expr.py | 29 ++++++++++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 4d8026cfe..5aa6ce9d0 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -118,6 +118,7 @@ def render_tokens(self, tokens: Iterable[Token], /) -> LiteralString:
parser: RSTParse = RSTParse(RSTRenderer())
+
@dataclasses.dataclass
class VegaExprNode:
"""
@@ -142,7 +143,7 @@ def with_parameters(self) -> Self:
return self
def with_doc(self) -> Self:
- self.doc = parser.render_tokens(self._doc_tokens())
+ self.doc = self._doc_post_process(parser.render_tokens(self._doc_tokens()))
return self
@functools.cached_property
@@ -193,6 +194,32 @@ def _doc_tokens(self) -> Sequence[Token]:
msg = f"Expected to find a text node marking the start of docstring content.\nFailed for:\n\n{self!r}"
raise NotImplementedError(msg)
+ def _doc_post_process(self, rendered: str, /) -> str:
+ r"""
+ Utilizing properties found during parsing to improve docs.
+
+ Temporarily handling this here.
+
+ TODO
+ ----
+ - [x] Replace \*param_name\* -> \`\`param_name\`\`.
+ - [x] References to ``func`` -> ``alt.expr.func``
+ - **Doesn't include other vega expressions yet**
+ - [x] Artifacts like: ``monthAbbrevFormat(0) -> "Jan"``
+ - [ ] Split after first sentence ends
+ - [ ] Replace "For example:" -> an example block
+ - [ ] Fix relative links to vega docs
+ - There's code in ``utils`` for this
+ """
+ highlight_params = re.sub(
+ rf"\*({'|'.join(self.parameter_names)})\*", r"``\g<1>``", rendered
+ )
+ with_alt_references = re.sub(
+ rf"({self.name}\()", f"alt.expr.{self.name_safe}(", highlight_params
+ )
+ unescaped = mistune.util.unescape(with_alt_references)
+ return unescaped
+
@staticmethod
def deep_split_punctuation(s: str, /) -> Iterator[str]:
"""Deep splitting of ending punctuation."""
From 67c6a1e4ba18d5b98f3e6f9f0ac8a901c73fd116 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 19:01:10 +0100
Subject: [PATCH 11/77] fix: Don't include `"*args"` in `parameter_names`
---
tools/schemapi/vega_expr.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 5aa6ce9d0..2f0582a49 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -149,7 +149,9 @@ def with_doc(self) -> Self:
@functools.cached_property
def parameter_names(self) -> frozenset[str]:
if self.parameters:
- return frozenset(param.name for param in self.parameters)
+ return frozenset(
+ param.name for param in self.parameters if not param.variadic
+ )
else:
msg = (
f"Cannot provide `parameter_names` until they have been initialized via:\n"
From 815404ecb894dd7a238e8cc576f22e4bd4192c8b Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 19:15:44 +0100
Subject: [PATCH 12/77] feat(DRAFT): Adds `VegaExprNode.to_signature`
- Only the most common case
- no docs
- No method body
---
tools/schemapi/vega_expr.py | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 2f0582a49..cf88dc906 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -40,6 +40,8 @@
TEXT: Literal["text"] = "text"
CHILDREN: Literal["children"] = "children"
+RETURN_ANNOTATION = "FunctionExpression"
+
def download_expressions_md(url: str, /) -> Path:
"""Download to a temporary file, return that as a ``pathlib.Path``."""
@@ -133,6 +135,17 @@ class VegaExprNode:
doc: str = ""
parameters: list[VegaExprParam] = dataclasses.field(default_factory=list)
+ def to_signature(self) -> str:
+ pre_params = f"def {self.name_safe}(cls, "
+ post_params = f", /) -> {RETURN_ANNOTATION}:"
+ param_list = ""
+ if all(p.required for p in self.parameters):
+ # NOTE: covers 101/147 cases
+ param_list = ", ".join(p.name for p in self.parameters)
+ else:
+ param_list = ""
+ return f"{pre_params}{param_list}{post_params}"
+
def with_parameters(self) -> Self:
raw_texts = self._split_signature_tokens()
name = next(raw_texts)
From 51e569e44dd6ac3d3bf010fd5a1b0f913a006830 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 19:49:13 +0100
Subject: [PATCH 13/77] feat: Finish `to_signature` params component
---
tools/schemapi/vega_expr.py | 28 ++++++++++++++++++++++------
1 file changed, 22 insertions(+), 6 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index cf88dc906..8effd0aec 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -34,13 +34,16 @@
FUNCTION_DEF_LINE: Pattern[str] = re.compile(r"")
LIQUID_INCLUDE: Pattern[str] = re.compile(r"( \{% include.+%\})")
-TYPE: Literal[r"type"] = "type"
+TYPE: Literal[r"type"] = r"type"
RAW: Literal["raw"] = "raw"
SOFTBREAK: Literal["softbreak"] = "softbreak"
TEXT: Literal["text"] = "text"
CHILDREN: Literal["children"] = "children"
RETURN_ANNOTATION = "FunctionExpression"
+EXPR_ANNOTATION = "IntoExpression"
+NONE: Literal[r"None"] = r"None"
+STAR_ARGS: Literal["*args"] = "*args"
def download_expressions_md(url: str, /) -> Path:
@@ -136,14 +139,14 @@ class VegaExprNode:
parameters: list[VegaExprParam] = dataclasses.field(default_factory=list)
def to_signature(self) -> str:
+ """NOTE: 101/147 cases are all required args."""
pre_params = f"def {self.name_safe}(cls, "
post_params = f", /) -> {RETURN_ANNOTATION}:"
param_list = ""
- if all(p.required for p in self.parameters):
- # NOTE: covers 101/147 cases
- param_list = ", ".join(p.name for p in self.parameters)
+ if self.is_overloaded():
+ param_list = VegaExprParam.star_args()
else:
- param_list = ""
+ param_list = ", ".join(p.to_str() for p in self.parameters)
return f"{pre_params}{param_list}{post_params}"
def with_parameters(self) -> Self:
@@ -336,6 +339,19 @@ class VegaExprParam:
required: bool
variadic: bool = False
+ @staticmethod
+ def star_args() -> LiteralString:
+ return f"{STAR_ARGS}: Any"
+
+ def to_str(self) -> str:
+ """Return as an annotated parameter, with a default if needed."""
+ if self.required:
+ return f"{self.name}: {EXPR_ANNOTATION}"
+ elif not self.variadic:
+ return f"{self.name}: {EXPR_ANNOTATION} = {NONE}"
+ else:
+ return self.star_args()
+
@classmethod
def iter_params(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
"""Yields an ordered parameter list."""
@@ -351,7 +367,7 @@ def iter_params(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
elif s.isalnum():
yield cls(s, required=is_required)
elif s == "...":
- yield cls("*args", required=False, variadic=True)
+ yield cls(STAR_ARGS, required=False, variadic=True)
else:
continue
From b3006bcdf12f8569762693605cf103761cde8257 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 20:23:42 +0100
Subject: [PATCH 14/77] feat(DRAFT): Add `render_expr_method`
Currently just collects the pieces, but doesn't render the final str
---
tools/schemapi/vega_expr.py | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 8effd0aec..6f164a82a 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -24,7 +24,10 @@
from typing import LiteralString, Self, TypeAlias
else:
from typing_extensions import LiteralString, Self, TypeAlias
- Token: TypeAlias = "dict[str, Any]"
+
+Token: TypeAlias = "dict[str, Any]"
+WorkInProgress: TypeAlias = Any
+"""Marker for a type that will not be final."""
EXPRESSIONS_URL = (
@@ -44,6 +47,7 @@
EXPR_ANNOTATION = "IntoExpression"
NONE: Literal[r"None"] = r"None"
STAR_ARGS: Literal["*args"] = "*args"
+DECORATOR = r"@classmethod"
def download_expressions_md(url: str, /) -> Path:
@@ -163,11 +167,9 @@ def with_doc(self) -> Self:
return self
@functools.cached_property
- def parameter_names(self) -> frozenset[str]:
+ def parameter_names(self) -> tuple[str, ...]:
if self.parameters:
- return frozenset(
- param.name for param in self.parameters if not param.variadic
- )
+ return tuple(param.name for param in self.parameters if not param.variadic)
else:
msg = (
f"Cannot provide `parameter_names` until they have been initialized via:\n"
@@ -386,5 +388,14 @@ def parse_expressions(url: str, /) -> Iterator[tuple[str, VegaExprNode]]:
request.urlcleanup()
+def render_expr_method(node: VegaExprNode, /) -> WorkInProgress:
+ if node.is_overloaded():
+ body_params = STAR_ARGS[1:]
+ else:
+ body_params = f"({', '.join(param.name for param in node.parameters)})"
+ body = f"return {RETURN_ANNOTATION}({node.name}, {body_params})"
+ return DECORATOR, node.to_signature(), node.doc, body
+
+
def test_parse() -> dict[str, VegaExprNode]:
return dict(parse_expressions(EXPRESSIONS_URL))
From 54f7db0f17fc8ad3ed5ace0ee12adb5315b2f431 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 19 Sep 2024 20:35:41 +0100
Subject: [PATCH 15/77] refactor: `FunctionExpression` -> `Expression` for
annotation only
Also renamed constant `EXPR_ANNOTATION` -> `INPUT_ANNOTATION`
---
tools/schemapi/vega_expr.py | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 6f164a82a..c7d5d63d6 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -43,8 +43,13 @@
TEXT: Literal["text"] = "text"
CHILDREN: Literal["children"] = "children"
-RETURN_ANNOTATION = "FunctionExpression"
-EXPR_ANNOTATION = "IntoExpression"
+RETURN_WRAPPER = "FunctionExpression"
+RETURN_ANNOTATION = "Expression"
+# NOTE: No benefit to annotating with the actual wrapper
+# - `Expression` is shorter, and has all the functionality/attributes
+
+INPUT_ANNOTATION = "IntoExpression"
+
NONE: Literal[r"None"] = r"None"
STAR_ARGS: Literal["*args"] = "*args"
DECORATOR = r"@classmethod"
@@ -348,9 +353,9 @@ def star_args() -> LiteralString:
def to_str(self) -> str:
"""Return as an annotated parameter, with a default if needed."""
if self.required:
- return f"{self.name}: {EXPR_ANNOTATION}"
+ return f"{self.name}: {INPUT_ANNOTATION}"
elif not self.variadic:
- return f"{self.name}: {EXPR_ANNOTATION} = {NONE}"
+ return f"{self.name}: {INPUT_ANNOTATION} = {NONE}"
else:
return self.star_args()
@@ -393,7 +398,7 @@ def render_expr_method(node: VegaExprNode, /) -> WorkInProgress:
body_params = STAR_ARGS[1:]
else:
body_params = f"({', '.join(param.name for param in node.parameters)})"
- body = f"return {RETURN_ANNOTATION}({node.name}, {body_params})"
+ body = f"return {RETURN_WRAPPER}({node.name}, {body_params})"
return DECORATOR, node.to_signature(), node.doc, body
From 328cc9898d943c9cf4633dce64917ef95b6ecab1 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 20 Sep 2024 16:45:37 +0100
Subject: [PATCH 16/77] feat: Handle `(expr|SchemaBase).copy` conflict
---
tools/schemapi/vega_expr.py | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index c7d5d63d6..1758e96d9 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -4,6 +4,7 @@
import functools
import keyword
import re
+from inspect import getmembers
from pathlib import Path
from typing import TYPE_CHECKING, Any, Iterable, Iterator, Literal, Sequence, overload
from urllib import request
@@ -11,6 +12,7 @@
import mistune
import mistune.util
+from tools.schemapi.schemapi import SchemaBase as _SchemaBase
from tools.schemapi.utils import RSTParse as _RSTParse
from tools.schemapi.utils import RSTRenderer
@@ -53,6 +55,7 @@
NONE: Literal[r"None"] = r"None"
STAR_ARGS: Literal["*args"] = "*args"
DECORATOR = r"@classmethod"
+IGNORE_OVERRIDE = r"# type: ignore[override]"
def download_expressions_md(url: str, /) -> Path:
@@ -88,6 +91,20 @@ def strip_include_tag(s: str, /) -> str:
return LIQUID_INCLUDE.sub(r"", s)
+def _override_predicate(obj: Any, /) -> bool:
+ return (
+ callable(obj)
+ and (name := obj.__name__)
+ and isinstance(name, str)
+ and not (name.startswith("_"))
+ )
+
+
+_SCHEMA_BASE_MEMBERS: frozenset[str] = frozenset(
+ nm for nm, _ in getmembers(_SchemaBase, _override_predicate)
+)
+
+
class RSTParse(_RSTParse):
"""
Minor extension to support partial `ast`_ conversion.
@@ -151,6 +168,8 @@ def to_signature(self) -> str:
"""NOTE: 101/147 cases are all required args."""
pre_params = f"def {self.name_safe}(cls, "
post_params = f", /) -> {RETURN_ANNOTATION}:"
+ if self.is_incompatible_override():
+ post_params = f"{post_params} {IGNORE_OVERRIDE}"
param_list = ""
if self.is_overloaded():
param_list = VegaExprParam.star_args()
@@ -329,6 +348,14 @@ def is_overloaded(self) -> bool:
def is_keyword(self) -> bool:
return keyword.iskeyword(self.name)
+ def is_incompatible_override(self) -> bool:
+ """
+ ``self.name_safe`` shadows an unrelated ``SchemaBase`` method.
+
+ Requires an ignore comment for a type checker.
+ """
+ return self.name_safe in _SCHEMA_BASE_MEMBERS
+
def __iter__(self) -> Iterator[Token]:
yield from self._children
From 6e1897d3d76a418b803e4cbe9e00fc62cd90befd Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 20 Sep 2024 20:01:31 +0100
Subject: [PATCH 17/77] fix: Disable `string_` overloads
---
tools/schemapi/vega_expr.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 1758e96d9..e4e42a3ef 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -302,7 +302,7 @@ def is_callable(self) -> bool:
# - the `.md` handles this by prefixing the `
Date: Fri, 20 Sep 2024 20:02:53 +0100
Subject: [PATCH 18/77] fix: Classify more cases as variadic
---
tools/schemapi/vega_expr.py | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index e4e42a3ef..dde4255d0 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -167,7 +167,8 @@ class VegaExprNode:
def to_signature(self) -> str:
"""NOTE: 101/147 cases are all required args."""
pre_params = f"def {self.name_safe}(cls, "
- post_params = f", /) -> {RETURN_ANNOTATION}:"
+ post_params = ")" if self.is_variadic() else ", /)"
+ post_params = f"{post_params} -> {RETURN_ANNOTATION}:"
if self.is_incompatible_override():
post_params = f"{post_params} {IGNORE_OVERRIDE}"
param_list = ""
@@ -235,7 +236,10 @@ def _doc_tokens(self) -> Sequence[Token]:
return self[idx + 1 :]
else:
continue
- msg = f"Expected to find a text node marking the start of docstring content.\nFailed for:\n\n{self!r}"
+ msg = (
+ f"Expected to find a text node marking the start of docstring content.\n"
+ f"Failed for:\n\n{self!r}"
+ )
raise NotImplementedError(msg)
def _doc_post_process(self, rendered: str, /) -> str:
@@ -343,6 +347,14 @@ def is_overloaded(self) -> bool:
return self[idx + 1][TYPE] == SOFTBREAK
else:
continue
+ for idx, p in enumerate(self.parameters):
+ if not p.required:
+ others = self.parameters[idx + 1 :]
+ if not others:
+ return False
+ else:
+ return any(sp.required for sp in others)
+
return False
def is_keyword(self) -> bool:
@@ -356,6 +368,10 @@ def is_incompatible_override(self) -> bool:
"""
return self.name_safe in _SCHEMA_BASE_MEMBERS
+ def is_variadic(self) -> bool:
+ """Position-only parameter separator `"/"` not allowed after `"*"` parameter."""
+ return self.is_overloaded() or any(p.variadic for p in self.parameters)
+
def __iter__(self) -> Iterator[Token]:
yield from self._children
From 1ce15671a60f212a36d96cc2cc26731041d6459c Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 20 Sep 2024 20:04:26 +0100
Subject: [PATCH 19/77] fix: Use quotes for `node.name`
---
tools/schemapi/vega_expr.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index dde4255d0..653b38bec 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -441,7 +441,7 @@ def render_expr_method(node: VegaExprNode, /) -> WorkInProgress:
body_params = STAR_ARGS[1:]
else:
body_params = f"({', '.join(param.name for param in node.parameters)})"
- body = f"return {RETURN_WRAPPER}({node.name}, {body_params})"
+ body = f"return {RETURN_WRAPPER}({node.name!r}, {body_params})"
return DECORATOR, node.to_signature(), node.doc, body
From b88221f41cd0134976410e31e539b4ee054c693a Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 20 Sep 2024 20:07:00 +0100
Subject: [PATCH 20/77] feat(DRAFT): Full `expr` module generation
- Contains all the functionality
- Needs a lot of tidying up
---
tools/schemapi/vega_expr.py | 175 +++++++++++++++++++++++++++++++++++-
1 file changed, 174 insertions(+), 1 deletion(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 653b38bec..6c7d0171d 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -5,6 +5,7 @@
import keyword
import re
from inspect import getmembers
+from itertools import chain
from pathlib import Path
from typing import TYPE_CHECKING, Any, Iterable, Iterator, Literal, Sequence, overload
from urllib import request
@@ -50,12 +51,16 @@
# NOTE: No benefit to annotating with the actual wrapper
# - `Expression` is shorter, and has all the functionality/attributes
+CONST_WRAPPER = "ConstExpression"
+CONST_META = "_ConstExpressionType"
+
INPUT_ANNOTATION = "IntoExpression"
NONE: Literal[r"None"] = r"None"
STAR_ARGS: Literal["*args"] = "*args"
DECORATOR = r"@classmethod"
IGNORE_OVERRIDE = r"# type: ignore[override]"
+IGNORE_MISC = r"# type: ignore[misc]"
def download_expressions_md(url: str, /) -> Path:
@@ -436,14 +441,182 @@ def parse_expressions(url: str, /) -> Iterator[tuple[str, VegaExprNode]]:
request.urlcleanup()
+EXPR_MODULE_PRE = '''\
+"""Tools for creating transform & filter expressions with a python syntax."""
+
+from __future__ import annotations
+
+import sys
+from typing import Any, TYPE_CHECKING
+
+from altair.expr.core import {const}, {func}, {return_ann}, {input_ann}
+from altair.vegalite.v5.schema.core import ExprRef as _ExprRef
+
+if sys.version_info >= (3, 12):
+ from typing import override
+else:
+ from typing_extensions import override
+
+
+class {metaclass}(type):
+ """Metaclass providing read-only class properties for :class:`expr`."""
+
+ @property
+ def NaN(cls) -> {return_ann}:
+ """Not a number (same as JavaScript literal NaN)."""
+ return {const}("NaN")
+
+ @property
+ def LN10(cls) -> {return_ann}:
+ """The natural log of 10 (alias to Math.LN10)."""
+ return {const}("LN10")
+
+ @property
+ def E(cls) -> {return_ann}:
+ """The transcendental number e (alias to Math.E)."""
+ return {const}("E")
+
+ @property
+ def LOG10E(cls) -> {return_ann}:
+ """The base 10 logarithm e (alias to Math.LOG10E)."""
+ return {const}("LOG10E")
+
+ @property
+ def LOG2E(cls) -> {return_ann}:
+ """The base 2 logarithm of e (alias to Math.LOG2E)."""
+ return {const}("LOG2E")
+
+ @property
+ def SQRT1_2(cls) -> {return_ann}:
+ """The square root of 0.5 (alias to Math.SQRT1_2)."""
+ return {const}("SQRT1_2")
+
+ @property
+ def LN2(cls) -> {return_ann}:
+ """The natural log of 2 (alias to Math.LN2)."""
+ return {const}("LN2")
+
+ @property
+ def SQRT2(cls) -> {return_ann}:
+ """The square root of 2 (alias to Math.SQRT1_2)."""
+ return {const}("SQRT2")
+
+ @property
+ def PI(cls) -> {return_ann}:
+ """The transcendental number pi (alias to Math.PI)."""
+ return {const}("PI")
+'''
+
+
+EXPR_MODULE_POST = """\
+_ExprType = expr
+# NOTE: Compatibility alias for previous type of `alt.expr`.
+# `_ExprType` was not referenced in any internal imports/tests.
+"""
+
+EXPR_CLS_DOC = """
+ Utility providing *constants* and *classmethods* to construct expressions.
+
+ `Expressions`_ can be used to write basic formulas that enable custom interactions.
+
+ Alternatively, an `inline expression`_ may be defined via :class:`expr()`.
+
+ Parameters
+ ----------
+ expr: str
+ A `vega expression`_ string.
+
+ Returns
+ -------
+ ``ExprRef``
+
+ .. _Expressions:
+ https://altair-viz.github.io/user_guide/interactions.html#expressions
+ .. _inline expression:
+ https://altair-viz.github.io/user_guide/interactions.html#inline-expressions
+ .. _vega expression:
+ https://vega.github.io/vega/docs/expressions/
+
+ Examples
+ --------
+ >>> import altair as alt
+
+ >>> bind_range = alt.binding_range(min=100, max=300, name="Slider value: ")
+ >>> param_width = alt.param(bind=bind_range, name="param_width")
+ >>> param_color = alt.param(
+ ... expr=alt.expr.if_(param_width < 200, "red", "black"),
+ ... name="param_color",
+ ... )
+ >>> y = alt.Y("yval").axis(titleColor=param_color)
+
+ >>> y
+ Y({
+ axis: {'titleColor': Parameter('param_color', VariableParameter({
+ expr: if((param_width < 200),'red','black'),
+ name: 'param_color'
+ }))},
+ shorthand: 'yval'
+ })
+ """
+
+EXPR_CLS_TEMPLATE = '''\
+class expr({base}, metaclass={metaclass}):
+ """{doc}"""
+
+ @override
+ def __new__(cls: type[{base}], expr: str) -> {base}: {type_ignore}
+ return {base}(expr=expr)
+'''
+
+EXPR_METHOD_TEMPLATE = '''\
+ {decorator}
+ {signature}
+ """
+ {doc}\
+ """
+ {body}
+'''
+
+
+def render_expr_cls():
+ return EXPR_CLS_TEMPLATE.format(
+ base="_ExprRef",
+ metaclass=CONST_META,
+ doc=EXPR_CLS_DOC,
+ type_ignore=IGNORE_MISC,
+ )
+
+
def render_expr_method(node: VegaExprNode, /) -> WorkInProgress:
if node.is_overloaded():
body_params = STAR_ARGS[1:]
else:
body_params = f"({', '.join(param.name for param in node.parameters)})"
body = f"return {RETURN_WRAPPER}({node.name!r}, {body_params})"
- return DECORATOR, node.to_signature(), node.doc, body
+ return EXPR_METHOD_TEMPLATE.format(
+ decorator=DECORATOR, signature=node.to_signature(), doc=node.doc, body=body
+ )
def test_parse() -> dict[str, VegaExprNode]:
return dict(parse_expressions(EXPRESSIONS_URL))
+
+
+def render_expr_full() -> str:
+ it = (render_expr_method(node) for _, node in parse_expressions(EXPRESSIONS_URL))
+ return "\n".join(
+ chain(
+ (
+ EXPR_MODULE_PRE.format(
+ metaclass=CONST_META,
+ const=CONST_WRAPPER,
+ return_ann=RETURN_ANNOTATION,
+ input_ann=INPUT_ANNOTATION,
+ func=RETURN_WRAPPER,
+ ),
+ render_expr_cls(),
+ ),
+ it,
+ [EXPR_MODULE_POST],
+ )
+ )
From 0a099362a2c58d28e9c84cd3d3b9d5b6fc966bd7 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 21 Sep 2024 14:14:11 +0100
Subject: [PATCH 21/77] feat: Strip include tags from docs
---
tools/schemapi/vega_expr.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 6c7d0171d..3abd47e33 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -264,8 +264,9 @@ def _doc_post_process(self, rendered: str, /) -> str:
- [ ] Fix relative links to vega docs
- There's code in ``utils`` for this
"""
+ strip_include = strip_include_tag(rendered)
highlight_params = re.sub(
- rf"\*({'|'.join(self.parameter_names)})\*", r"``\g<1>``", rendered
+ rf"\*({'|'.join(self.parameter_names)})\*", r"``\g<1>``", strip_include
)
with_alt_references = re.sub(
rf"({self.name}\()", f"alt.expr.{self.name_safe}(", highlight_params
From 937759866305f5cc45f87bbfda682177db381f12 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 21 Sep 2024 14:20:00 +0100
Subject: [PATCH 22/77] feat: Split doc summary, wrap body
- Uses the same config as `indent_docstring`
- Can't truly be `numpydoc` though without a parameters section
---
tools/schemapi/vega_expr.py | 31 ++++++++++++++++++++++++++++---
1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 3abd47e33..9466e0cab 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -4,9 +4,11 @@
import functools
import keyword
import re
+from collections import deque
from inspect import getmembers
from itertools import chain
from pathlib import Path
+from textwrap import TextWrapper as _TextWrapper
from typing import TYPE_CHECKING, Any, Iterable, Iterator, Literal, Sequence, overload
from urllib import request
@@ -153,6 +155,27 @@ def render_tokens(self, tokens: Iterable[Token], /) -> LiteralString:
parser: RSTParse = RSTParse(RSTRenderer())
+text_wrap = _TextWrapper(
+ width=100,
+ break_long_words=False,
+ break_on_hyphens=False,
+ initial_indent=8 * " ",
+ subsequent_indent=8 * " ",
+)
+
+
+def _doc_fmt(doc: str, /) -> str:
+ sentences: deque[str] = deque(re.split(r"\. ", doc))
+ if len(sentences) > 1:
+ summary = f"{sentences.popleft()}.\n"
+ last_line = sentences.pop().strip()
+ sentences = deque(f"{s}. " for s in sentences)
+ sentences.append(last_line)
+ sentences = deque(text_wrap.wrap("".join(sentences)))
+ sentences.appendleft(summary)
+ return "\n".join(sentences)
+ else:
+ return sentences.pop().strip()
@dataclasses.dataclass
@@ -259,7 +282,8 @@ def _doc_post_process(self, rendered: str, /) -> str:
- [x] References to ``func`` -> ``alt.expr.func``
- **Doesn't include other vega expressions yet**
- [x] Artifacts like: ``monthAbbrevFormat(0) -> "Jan"``
- - [ ] Split after first sentence ends
+ - [x] Split after first sentence ends
+ - [x] Wrap wide docs
- [ ] Replace "For example:" -> an example block
- [ ] Fix relative links to vega docs
- There's code in ``utils`` for this
@@ -272,7 +296,8 @@ def _doc_post_process(self, rendered: str, /) -> str:
rf"({self.name}\()", f"alt.expr.{self.name_safe}(", highlight_params
)
unescaped = mistune.util.unescape(with_alt_references)
- return unescaped
+ numpydoc_style = _doc_fmt(unescaped)
+ return numpydoc_style
@staticmethod
def deep_split_punctuation(s: str, /) -> Iterator[str]:
@@ -573,7 +598,7 @@ def __new__(cls: type[{base}], expr: str) -> {base}: {type_ignore}
{decorator}
{signature}
"""
- {doc}\
+ {doc}
"""
{body}
'''
From 8e88cd2f2d969a3c689475d9a1c3e631d1d182a3 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 21 Sep 2024 15:12:34 +0100
Subject: [PATCH 23/77] chore: Remove some notes
---
tools/schemapi/vega_expr.py | 20 ++------------------
1 file changed, 2 insertions(+), 18 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 9466e0cab..5451f24fe 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -254,11 +254,7 @@ def _split_signature_tokens(self) -> Iterator[str]:
break
def _doc_tokens(self) -> Sequence[Token]:
- """
- Return the slice of `self.children` that contains docstring content.
-
- Works for 100% of cases.
- """
+ """Return the slice of `self.children` that contains docstring content."""
for idx, item in enumerate(self):
if item[TYPE] == SOFTBREAK and self[idx + 1][TYPE] == TEXT:
return self[idx + 1 :]
@@ -271,22 +267,10 @@ def _doc_tokens(self) -> Sequence[Token]:
raise NotImplementedError(msg)
def _doc_post_process(self, rendered: str, /) -> str:
- r"""
+ """
Utilizing properties found during parsing to improve docs.
Temporarily handling this here.
-
- TODO
- ----
- - [x] Replace \*param_name\* -> \`\`param_name\`\`.
- - [x] References to ``func`` -> ``alt.expr.func``
- - **Doesn't include other vega expressions yet**
- - [x] Artifacts like: ``monthAbbrevFormat(0) -> "Jan"``
- - [x] Split after first sentence ends
- - [x] Wrap wide docs
- - [ ] Replace "For example:" -> an example block
- - [ ] Fix relative links to vega docs
- - There's code in ``utils`` for this
"""
strip_include = strip_include_tag(rendered)
highlight_params = re.sub(
From f5b371743f188a57d8dacb27b3adf9392b1384b1 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 21 Sep 2024 15:59:23 +0100
Subject: [PATCH 24/77] fix: Replace `vega` docs relative links
---
tools/schemapi/vega_expr.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 5451f24fe..fc27c0fc3 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -38,6 +38,7 @@
EXPRESSIONS_URL = (
"https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md"
)
+VEGA_DOCS_URL = "https://vega.github.io/vega/docs/"
FUNCTION_DEF_LINE: Pattern[str] = re.compile(r"")
LIQUID_INCLUDE: Pattern[str] = re.compile(r"( \{% include.+%\})")
@@ -280,7 +281,8 @@ def _doc_post_process(self, rendered: str, /) -> str:
rf"({self.name}\()", f"alt.expr.{self.name_safe}(", highlight_params
)
unescaped = mistune.util.unescape(with_alt_references)
- numpydoc_style = _doc_fmt(unescaped)
+ non_relative_links = re.sub(r"\.\.\/", VEGA_DOCS_URL, unescaped)
+ numpydoc_style = _doc_fmt(non_relative_links)
return numpydoc_style
@staticmethod
From 7ec050502c236992d4748e94332498b10cdef0f8 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 21 Sep 2024 20:53:28 +0100
Subject: [PATCH 25/77] feat: Convert inline links to references
See https://docutils.sourceforge.io/docs/user/rst/quickref.html#external-hyperlink-targets
---
tools/schemapi/vega_expr.py | 62 ++++++++++++++++++++++++++++++-------
1 file changed, 51 insertions(+), 11 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index fc27c0fc3..42e29da84 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -9,6 +9,7 @@
from itertools import chain
from pathlib import Path
from textwrap import TextWrapper as _TextWrapper
+from textwrap import indent
from typing import TYPE_CHECKING, Any, Iterable, Iterator, Literal, Sequence, overload
from urllib import request
@@ -17,13 +18,13 @@
from tools.schemapi.schemapi import SchemaBase as _SchemaBase
from tools.schemapi.utils import RSTParse as _RSTParse
-from tools.schemapi.utils import RSTRenderer
+from tools.schemapi.utils import RSTRenderer as _RSTRenderer
if TYPE_CHECKING:
import sys
from re import Pattern
- from mistune import BaseRenderer, BlockParser, InlineParser
+ from mistune import BlockParser, BlockState, InlineParser
if sys.version_info >= (3, 11):
from typing import LiteralString, Self, TypeAlias
@@ -39,9 +40,11 @@
"https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md"
)
VEGA_DOCS_URL = "https://vega.github.io/vega/docs/"
+EXPRESSIONS_DOCS_URL = f"{VEGA_DOCS_URL}expressions/"
FUNCTION_DEF_LINE: Pattern[str] = re.compile(r"")
LIQUID_INCLUDE: Pattern[str] = re.compile(r"( \{% include.+%\})")
+SENTENCE_BREAK: Pattern[str] = re.compile(r"(? bool:
)
+class RSTRenderer(_RSTRenderer):
+ def __init__(self) -> None:
+ super().__init__()
+
+ def link(self, token: Token, state: BlockState) -> str:
+ """Store link url, for appending at the end of doc."""
+ attrs = token["attrs"]
+ url: str = attrs["url"]
+ if url.startswith("#"):
+ url = f"{EXPRESSIONS_DOCS_URL}{url}"
+ text = self.render_children(token, state)
+ text = text.replace("`", "")
+ inline = f"`{text}`_"
+ state.env["ref_links"][text] = {"url": url}
+ return inline
+
+ def text(self, token: Token, state: BlockState) -> str:
+ text = super().text(token, state)
+ return strip_include_tag(text)
+
+
+def _iter_link_lines(ref_links: Any, /) -> Iterator[str]:
+ links: dict[str, Any] = ref_links
+ for ref_name, attrs in links.items():
+ yield from (f".. _{ref_name}:", f" {attrs['url']}")
+
+
class RSTParse(_RSTParse):
"""
Minor extension to support partial `ast`_ conversion.
@@ -131,7 +161,7 @@ class RSTParse(_RSTParse):
def __init__(
self,
- renderer: BaseRenderer,
+ renderer: RSTRenderer,
block: BlockParser | None = None,
inline: InlineParser | None = None,
plugins=None,
@@ -140,9 +170,9 @@ def __init__(
if self.renderer is None:
msg = "Must provide a renderer, got `None`"
raise TypeError(msg)
- self.renderer: BaseRenderer
+ self.renderer: RSTRenderer
- def render_tokens(self, tokens: Iterable[Token], /) -> LiteralString:
+ def render_tokens(self, tokens: Iterable[Token], /) -> str:
"""
Render ast tokens originating from another parser.
@@ -152,7 +182,11 @@ def render_tokens(self, tokens: Iterable[Token], /) -> LiteralString:
All tokens will be rendered into a single `.rst` string
"""
state = self.block.state_cls()
- return self.renderer(self._iter_render(tokens, state), state)
+ result = self.renderer(self._iter_render(tokens, state), state)
+ if links := state.env.get("ref_links", {}):
+ return "\n".join(chain([result], _iter_link_lines(links)))
+ else:
+ return result
parser: RSTParse = RSTParse(RSTRenderer())
@@ -166,14 +200,19 @@ def render_tokens(self, tokens: Iterable[Token], /) -> LiteralString:
def _doc_fmt(doc: str, /) -> str:
- sentences: deque[str] = deque(re.split(r"\. ", doc))
+ sentences: deque[str] = deque(SENTENCE_BREAK.split(doc))
if len(sentences) > 1:
+ references: str = ""
summary = f"{sentences.popleft()}.\n"
last_line = sentences.pop().strip()
sentences = deque(f"{s}. " for s in sentences)
+ if "\n\n.. _" in last_line:
+ last_line, references = last_line.split("\n\n", maxsplit=1)
sentences.append(last_line)
sentences = deque(text_wrap.wrap("".join(sentences)))
sentences.appendleft(summary)
+ if references:
+ sentences.extend(("", indent(references, 8 * " ")))
return "\n".join(sentences)
else:
return sentences.pop().strip()
@@ -273,10 +312,11 @@ def _doc_post_process(self, rendered: str, /) -> str:
Temporarily handling this here.
"""
- strip_include = strip_include_tag(rendered)
- highlight_params = re.sub(
- rf"\*({'|'.join(self.parameter_names)})\*", r"``\g<1>``", strip_include
- )
+ # NOTE: Avoids adding backticks to parameter names that are also used in a link
+ # - All cases of these are for `unit|units`
+ pre, post = "[^`_]", "[^`]"
+ pattern = rf"({pre})\*({'|'.join(self.parameter_names)})\*({post})"
+ highlight_params = re.sub(pattern, r"\g<1>``\g<2>``\g<3>", rendered)
with_alt_references = re.sub(
rf"({self.name}\()", f"alt.expr.{self.name_safe}(", highlight_params
)
From 29f08dc48352a5ebca0fa5f086ada96c3236d828 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 23 Sep 2024 11:37:55 +0100
Subject: [PATCH 26/77] refactor: Rewrite `VegaExprNode` as a regular class
---
tools/schemapi/vega_expr.py | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 42e29da84..1531a5370 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -218,7 +218,6 @@ def _doc_fmt(doc: str, /) -> str:
return sentences.pop().strip()
-@dataclasses.dataclass
class VegaExprNode:
"""
``SchemaInfo``-like, but operates on `expressions.md`_.
@@ -227,10 +226,11 @@ class VegaExprNode:
https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md
"""
- name: str
- _children: Sequence[Token] = dataclasses.field(repr=False)
- doc: str = ""
- parameters: list[VegaExprParam] = dataclasses.field(default_factory=list)
+ def __init__(self, name: str, children: Sequence[Token], /) -> None:
+ self.name: str = name
+ self._children: Sequence[Token] = children
+ self.parameters: list[VegaExprParam] = []
+ self.doc: str = ""
def to_signature(self) -> str:
"""NOTE: 101/147 cases are all required args."""
@@ -439,6 +439,15 @@ def __getitem__(self, index: slice) -> Sequence[Token]: ...
def __getitem__(self, index: int | slice) -> Token | Sequence[Token]:
return self._children.__getitem__(index)
+ def __repr__(self) -> str:
+ return (
+ f"{type(self).__name__}(\n "
+ f"name={self.name!r},\n "
+ f"parameters={self.parameters!r},\n "
+ f"doc={self.doc!r}\n"
+ ")"
+ )
+
@dataclasses.dataclass
class VegaExprParam:
From f6cc7762a9d68ad592868eaf963328eada07631c Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 23 Sep 2024 15:19:12 +0100
Subject: [PATCH 27/77] feat: Replace all `Vega` function refs in code blocks
- Renamed `name_safe` -> `title`
- Perform an eager pass to extract replacement pairs
https://github.com/vega/altair/pull/3600#discussion_r1771091211
---
tools/schemapi/vega_expr.py | 144 +++++++++++++++++++++++++++++++-----
1 file changed, 127 insertions(+), 17 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 1531a5370..952de759b 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -10,7 +10,18 @@
from pathlib import Path
from textwrap import TextWrapper as _TextWrapper
from textwrap import indent
-from typing import TYPE_CHECKING, Any, Iterable, Iterator, Literal, Sequence, overload
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ ClassVar,
+ Iterable,
+ Iterator,
+ Literal,
+ Mapping,
+ Sequence,
+ overload,
+)
from urllib import request
import mistune
@@ -22,7 +33,7 @@
if TYPE_CHECKING:
import sys
- from re import Pattern
+ from re import Match, Pattern
from mistune import BlockParser, BlockState, InlineParser
@@ -30,6 +41,7 @@
from typing import LiteralString, Self, TypeAlias
else:
from typing_extensions import LiteralString, Self, TypeAlias
+ from _typeshed import SupportsKeysAndGetItem
Token: TypeAlias = "dict[str, Any]"
WorkInProgress: TypeAlias = Any
@@ -218,6 +230,81 @@ def _doc_fmt(doc: str, /) -> str:
return sentences.pop().strip()
+class ReplaceMany:
+ def __init__(
+ self,
+ m: Mapping[str, str] | None = None,
+ /,
+ fmt_match: str = "(?P{0})",
+ fmt_replace: str = "{0}",
+ ) -> None:
+ self._mapping: dict[str, str] = dict(m) if m else {}
+ self._fmt_match: str = fmt_match
+ self._fmt_replace: str = fmt_replace
+ self.pattern: Pattern[str]
+ self.repl: Callable[[Match[str]], str]
+
+ def update(
+ self,
+ m: SupportsKeysAndGetItem[str, str] | Iterable[tuple[str, str]],
+ /,
+ **kwds: str,
+ ) -> None:
+ """Update replacements mapping."""
+ self._mapping.update(m, **kwds)
+
+ def clear(self) -> None:
+ """Reset replacements mapping."""
+ self._mapping.clear()
+
+ def prepare(self) -> None:
+ """
+ Compile replacement pattern and generate substitution function.
+
+ Notes
+ -----
+ Should be called **after** all (old, new) pairs have been collected.
+ """
+ self.pattern = self._compile()
+ self.repl = self._replacer()
+
+ def __call__(self, s: str, count: int = 0, /) -> str:
+ """
+ Replace the leftmost non-overlapping occurrences of ``self.pattern`` in ``s`` using ``self.repl``.
+
+ Wraps `re.sub`_
+
+ .. _re.sub:
+ _https://docs.python.org/3/library/re.html#re.sub
+ """
+ return self.pattern.sub(self.repl, s, count)
+
+ def _compile(self) -> Pattern[str]:
+ if not self._mapping:
+ name = self._mapping.__qualname__
+ msg = (
+ f"Requires {name!r} to be populated, but got:\n"
+ f"{name}={self._mapping!r}"
+ )
+ raise TypeError(msg)
+ return re.compile(rf"{self._fmt_match.format('|'.join(self._mapping))}")
+
+ def _replacer(self) -> Callable[[Match[str]], str]:
+ def repl(m: Match[str], /) -> str:
+ return self._fmt_replace.format(self._mapping[m["key"]])
+
+ return repl
+
+ def __getitem__(self, key: str) -> str:
+ return self._mapping[key]
+
+ def __setitem__(self, key: str, value: str) -> None:
+ self._mapping[key] = value
+
+ def __repr__(self) -> str:
+ return f"{type(self).__name__}(\n {self._mapping!r}\n)"
+
+
class VegaExprNode:
"""
``SchemaInfo``-like, but operates on `expressions.md`_.
@@ -226,15 +313,20 @@ class VegaExprNode:
https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md
"""
+ remap_title: ClassVar[ReplaceMany] = ReplaceMany(
+ fmt_match=r"(?P{0})\(", fmt_replace="{0}("
+ )
+
def __init__(self, name: str, children: Sequence[Token], /) -> None:
self.name: str = name
self._children: Sequence[Token] = children
self.parameters: list[VegaExprParam] = []
self.doc: str = ""
+ self.signature: str = ""
- def to_signature(self) -> str:
+ def with_signature(self) -> Self:
"""NOTE: 101/147 cases are all required args."""
- pre_params = f"def {self.name_safe}(cls, "
+ pre_params = f"def {self.title}(cls, "
post_params = ")" if self.is_variadic() else ", /)"
post_params = f"{post_params} -> {RETURN_ANNOTATION}:"
if self.is_incompatible_override():
@@ -244,7 +336,8 @@ def to_signature(self) -> str:
param_list = VegaExprParam.star_args()
else:
param_list = ", ".join(p.to_str() for p in self.parameters)
- return f"{pre_params}{param_list}{post_params}"
+ self.signature = f"{pre_params}{param_list}{post_params}"
+ return self
def with_parameters(self) -> Self:
raw_texts = self._split_signature_tokens()
@@ -271,9 +364,15 @@ def parameter_names(self) -> tuple[str, ...]:
raise TypeError(msg)
@property
- def name_safe(self) -> str:
- """Use for the method definition, but not when calling internally."""
- return f"{self.name}_" if self.is_keyword() else self.name
+ def title(self) -> str:
+ """
+ Use for the method definition, but not when calling internally.
+
+ Updates ``VegaExprNode.remap_title`` for documentation example substitutions.
+ """
+ title = f"{self.name}_" if self.is_keyword() else self.name
+ type(self).remap_title.update({self.name: f"alt.expr.{title}"})
+ return title
def _split_signature_tokens(self) -> Iterator[str]:
"""Very rough splitting/cleaning of relevant token raw text."""
@@ -317,9 +416,7 @@ def _doc_post_process(self, rendered: str, /) -> str:
pre, post = "[^`_]", "[^`]"
pattern = rf"({pre})\*({'|'.join(self.parameter_names)})\*({post})"
highlight_params = re.sub(pattern, r"\g<1>``\g<2>``\g<3>", rendered)
- with_alt_references = re.sub(
- rf"({self.name}\()", f"alt.expr.{self.name_safe}(", highlight_params
- )
+ with_alt_references = type(self).remap_title(highlight_params)
unescaped = mistune.util.unescape(with_alt_references)
non_relative_links = re.sub(r"\.\.\/", VEGA_DOCS_URL, unescaped)
numpydoc_style = _doc_fmt(non_relative_links)
@@ -423,7 +520,7 @@ def is_incompatible_override(self) -> bool:
Requires an ignore comment for a type checker.
"""
- return self.name_safe in _SCHEMA_BASE_MEMBERS
+ return self.title in _SCHEMA_BASE_MEMBERS
def is_variadic(self) -> bool:
"""Position-only parameter separator `"/"` not allowed after `"*"` parameter."""
@@ -488,7 +585,7 @@ def iter_params(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
continue
-def parse_expressions(url: str, /) -> Iterator[tuple[str, VegaExprNode]]:
+def _parse_expressions(url: str, /) -> Iterator[VegaExprNode]:
"""Download, read markdown and iteratively parse into signature representations."""
for tok in read_tokens(download_expressions_md(url)):
if (
@@ -498,10 +595,22 @@ def parse_expressions(url: str, /) -> Iterator[tuple[str, VegaExprNode]]:
):
node = VegaExprNode(match[1], children)
if node.is_callable():
- yield node.name, node.with_parameters().with_doc()
+ yield node.with_parameters().with_signature()
request.urlcleanup()
+def parse_expressions(url: str, /) -> Iterator[VegaExprNode]:
+ """
+ Eagerly parse signatures of relevant definitions, then yield with docs.
+
+ Ensures each doc can use all remapped names, regardless of the order they appear.
+ """
+ eager = tuple(_parse_expressions(url))
+ VegaExprNode.remap_title.prepare()
+ for node in eager:
+ yield node.with_doc()
+
+
EXPR_MODULE_PRE = '''\
"""Tools for creating transform & filter expressions with a python syntax."""
@@ -655,16 +764,17 @@ def render_expr_method(node: VegaExprNode, /) -> WorkInProgress:
body_params = f"({', '.join(param.name for param in node.parameters)})"
body = f"return {RETURN_WRAPPER}({node.name!r}, {body_params})"
return EXPR_METHOD_TEMPLATE.format(
- decorator=DECORATOR, signature=node.to_signature(), doc=node.doc, body=body
+ decorator=DECORATOR, signature=node.signature, doc=node.doc, body=body
)
def test_parse() -> dict[str, VegaExprNode]:
- return dict(parse_expressions(EXPRESSIONS_URL))
+ """Temporary introspection tool."""
+ return {node.name: node for node in parse_expressions(EXPRESSIONS_URL)}
def render_expr_full() -> str:
- it = (render_expr_method(node) for _, node in parse_expressions(EXPRESSIONS_URL))
+ it = (render_expr_method(node) for node in parse_expressions(EXPRESSIONS_URL))
return "\n".join(
chain(
(
From 321a0cde1bb3d210a381b47c2b07a99940ab077b Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 23 Sep 2024 15:21:46 +0100
Subject: [PATCH 28/77] docs: Update doc for `is_overloaded`
I've identified multiple kinds of overloads.
If we want to further improve accuracy, then these would need to be considered separately
---
tools/schemapi/vega_expr.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 952de759b..aad002851 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -493,8 +493,17 @@ def is_overloaded(self) -> bool:
lab(l, a, b[, opacity]) | lab(specifier)
+ Looping of parameters is for signatures like `sequence`_:
+
+ sequence([start, ]stop[, step])
+
+ The optional first parameter, followed by a required one would need an
+ ``@overload`` in ``python``.
+
.. _color functions:
https://vega.github.io/vega/docs/expressions/#color-functions
+ .. _sequence:
+ https://vega.github.io/vega/docs/expressions/#sequence
"""
for idx, item in enumerate(self):
if item[TYPE] == TEXT and item.get(RAW, "") == "]) |":
From 90f7b0a22aef1914aa10b14b9725d606ad266cef Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 23 Sep 2024 15:22:52 +0100
Subject: [PATCH 29/77] docs: Add note to `_doc_fmt`
---
tools/schemapi/vega_expr.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index aad002851..7153327c3 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -212,6 +212,11 @@ def render_tokens(self, tokens: Iterable[Token], /) -> str:
def _doc_fmt(doc: str, /) -> str:
+ """
+ FIXME: Currently doing too many things.
+
+ Primarily using to exclude summary line + references from ``textwrap``.
+ """
sentences: deque[str] = deque(SENTENCE_BREAK.split(doc))
if len(sentences) > 1:
references: str = ""
From 36ac45aded1221ae06757a0516e33f42fbf76258 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 23 Sep 2024 16:04:48 +0100
Subject: [PATCH 30/77] refactor: Simplify `VegaExprNode.is_callable`
---
tools/schemapi/vega_expr.py | 40 ++++++++++++++++++++++---------------
1 file changed, 24 insertions(+), 16 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 7153327c3..873cac4c2 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -459,24 +459,16 @@ def is_callable(self) -> bool:
.. _constants:
https://vega.github.io/vega/docs/expressions/#constants
"""
- name = self.name
- if name.startswith("string_"):
- # HACK: There are string/array functions that overlap
- # - the `.md` handles this by prefixing the ` bool:
"""
``Vega`` `bound variables`_.
+ These do not provide signatures:
+
+ {"datum", "event", "signal"}
+
.. _bound variables:
https://vega.github.io/vega/docs/expressions/#bound-variables
"""
@@ -525,6 +521,18 @@ def is_overloaded(self) -> bool:
return False
+ def is_overloaded_string_array(self) -> bool:
+ """
+ HACK: There are string/array functions that overlap.
+
+ - the `.md` handles this by prefixing the ` bool:
return keyword.iskeyword(self.name)
From 56dfa276fd932ef4ef419a19fab6cf3e3448b644 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 23 Sep 2024 18:12:31 +0100
Subject: [PATCH 31/77] refactor: Simplify
`VegaExprNode._split_signature_tokens`
- Separates the token vs string iteration
- Improve doc, w/ example
---
tools/schemapi/vega_expr.py | 57 +++++++++++++++++++++++++++----------
1 file changed, 42 insertions(+), 15 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 873cac4c2..05b315ae3 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -95,7 +95,7 @@ def download_expressions_md(url: str, /) -> Path:
return fp
-def read_tokens(source: Path, /) -> list[Any]:
+def read_tokens(source: Path, /) -> list[Token]:
"""
Read from ``source``, drop ``BlockState``.
@@ -348,6 +348,7 @@ def with_parameters(self) -> Self:
raw_texts = self._split_signature_tokens()
name = next(raw_texts)
# NOTE: Overwriting the with the rendered text
+ # - required for `clamprange` -> `clampRange`
if self.name != name:
self.name = name
self.parameters = list(VegaExprParam.iter_params(raw_texts))
@@ -379,23 +380,49 @@ def title(self) -> str:
type(self).remap_title.update({self.name: f"alt.expr.{title}"})
return title
- def _split_signature_tokens(self) -> Iterator[str]:
- """Very rough splitting/cleaning of relevant token raw text."""
- it = iter(self)
+ def _signature_tokens(self) -> Iterator[Token]:
+ """
+ Target for signature appears between 2 softbreak tokens.
+
+ - Proceeds to the first token **after** a softbreak
+ - Yield **only** text tokens
+ - Skips all inline html tags
+ - Stops at 2nd softbreak
+ """
+ it: Iterator[Token] = iter(self)
current = next(it)
- # NOTE: softbreak(s) denote the line the sig appears on
while current[TYPE] != SOFTBREAK:
current = next(it)
- current = next(it)
- while current[TYPE] != SOFTBREAK:
- # NOTE: This drops html markup tags
- if current[TYPE] == TEXT:
- clean = strip_include_tag(current[RAW]).strip(", -")
- if clean not in {", ", ""}:
- yield from VegaExprNode.deep_split_punctuation(clean)
- current = next(it, None)
- if current is None:
+ next(it)
+ for target in it:
+ if target[TYPE] == TEXT:
+ yield target
+ elif target[TYPE] == SOFTBREAK:
break
+ else:
+ continue
+
+ def _split_signature_tokens(self) -> Iterator[str]:
+ """
+ Normalize the text content of the signature.
+
+ Examples
+ --------
+ The following definition:
+
+ #
+ sequence([start, ]stop[, step])
+ Returns an array containing an arithmetic sequence of numbers.
+ ...
+
+ Will yield:
+
+ ['sequence', '(', '[', 'start', ']', 'stop', '[', 'step', ']', ')']
+ """
+ for tok in self._signature_tokens():
+ clean = strip_include_tag(tok[RAW]).strip(", -")
+ if clean not in {", ", ""}:
+ yield from VegaExprNode.deep_split_punctuation(clean)
def _doc_tokens(self) -> Sequence[Token]:
"""Return the slice of `self.children` that contains docstring content."""
@@ -538,7 +565,7 @@ def is_keyword(self) -> bool:
def is_incompatible_override(self) -> bool:
"""
- ``self.name_safe`` shadows an unrelated ``SchemaBase`` method.
+ ``self.title`` shadows an unrelated ``SchemaBase`` method.
Requires an ignore comment for a type checker.
"""
From 9cab1a148793e5e0beab85923ca27df83cad2185 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 23 Sep 2024 20:32:27 +0100
Subject: [PATCH 32/77] docs: Add doc for `deep_split_punctuation`
---
tools/schemapi/vega_expr.py | 45 ++++++++++++++++++++++++++-----------
1 file changed, 32 insertions(+), 13 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 05b315ae3..93d8f0970 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -48,32 +48,43 @@
"""Marker for a type that will not be final."""
+# NOTE: Urls/fragments
EXPRESSIONS_URL = (
"https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md"
)
VEGA_DOCS_URL = "https://vega.github.io/vega/docs/"
EXPRESSIONS_DOCS_URL = f"{VEGA_DOCS_URL}expressions/"
+# NOTE: Regex patterns
FUNCTION_DEF_LINE: Pattern[str] = re.compile(r"")
LIQUID_INCLUDE: Pattern[str] = re.compile(r"( \{% include.+%\})")
SENTENCE_BREAK: Pattern[str] = re.compile(r"(? Self:
post_params = f"{post_params} -> {RETURN_ANNOTATION}:"
if self.is_incompatible_override():
post_params = f"{post_params} {IGNORE_OVERRIDE}"
- param_list = ""
if self.is_overloaded():
param_list = VegaExprParam.star_args()
else:
@@ -456,18 +466,27 @@ def _doc_post_process(self, rendered: str, /) -> str:
@staticmethod
def deep_split_punctuation(s: str, /) -> Iterator[str]:
- """Deep splitting of ending punctuation."""
+ """
+ When ``s`` ends with one of these markers:
+
+ ")", "]", "...", " |"
+
+ - Split ``s`` into rest, match
+ - using the length of the match to index
+ - Append match to ``end``
+ - Recurse
+ """ # noqa: D400
if s.isalnum():
yield s
else:
end: list[str] = []
- if s.endswith((")", "]")):
+ if s.endswith((CLOSE_PAREN, CLOSE_BRACKET)):
end.append(s[-1])
s = s[:-1]
- elif s.endswith("..."):
+ elif s.endswith(ELLIPSIS):
end.append(s[-3:])
s = s[:-3]
- elif s.endswith(" |"):
+ elif s.endswith(INLINE_OVERLOAD):
end.append(s[-2:])
s = s[:-2]
if len(s) == 1:
@@ -497,7 +516,7 @@ def is_callable(self) -> bool:
else:
return False
next(it)
- return next(it).get(RAW, "") == "("
+ return next(it).get(RAW, "") == OPEN_PAREN
def is_bound_variable_name(self) -> bool:
"""
@@ -534,7 +553,7 @@ def is_overloaded(self) -> bool:
https://vega.github.io/vega/docs/expressions/#sequence
"""
for idx, item in enumerate(self):
- if item[TYPE] == TEXT and item.get(RAW, "") == "]) |":
+ if item[TYPE] == TEXT and item.get(RAW, "").endswith(INLINE_OVERLOAD):
return self[idx + 1][TYPE] == SOFTBREAK
else:
continue
@@ -619,16 +638,16 @@ def iter_params(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
"""Yields an ordered parameter list."""
is_required: bool = True
for s in raw_texts:
- if s not in {"(", ")"}:
- if s == "[":
+ if s not in {OPEN_PAREN, CLOSE_PAREN}:
+ if s == OPEN_BRACKET:
is_required = False
continue
- elif s == "]":
+ elif s == CLOSE_BRACKET:
is_required = True
continue
elif s.isalnum():
yield cls(s, required=is_required)
- elif s == "...":
+ elif s == ELLIPSIS:
yield cls(STAR_ARGS, required=False, variadic=True)
else:
continue
From 497f2fd6f6d8c66ddfbf4f64f40be01e38a0e111 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 13:09:09 +0100
Subject: [PATCH 33/77] feat: Refine `ReplaceMany`
- Renaming
- Allow refreshing on any call
- Auto refresh if first call did not
---
tools/schemapi/vega_expr.py | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 93d8f0970..0fb15a1a3 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -249,16 +249,17 @@ def _doc_fmt(doc: str, /) -> str:
class ReplaceMany:
def __init__(
self,
- m: Mapping[str, str] | None = None,
+ mapping: Mapping[str, str] | None = None,
/,
fmt_match: str = "(?P{0})",
fmt_replace: str = "{0}",
) -> None:
- self._mapping: dict[str, str] = dict(m) if m else {}
+ self._mapping: dict[str, str] = dict(mapping) if mapping else {}
self._fmt_match: str = fmt_match
self._fmt_replace: str = fmt_replace
self.pattern: Pattern[str]
self.repl: Callable[[Match[str]], str]
+ self._is_prepared: bool = False
def update(
self,
@@ -273,7 +274,7 @@ def clear(self) -> None:
"""Reset replacements mapping."""
self._mapping.clear()
- def prepare(self) -> None:
+ def refresh(self) -> None:
"""
Compile replacement pattern and generate substitution function.
@@ -283,16 +284,19 @@ def prepare(self) -> None:
"""
self.pattern = self._compile()
self.repl = self._replacer()
+ self._is_prepared = True
- def __call__(self, s: str, count: int = 0, /) -> str:
+ def __call__(self, s: str, count: int = 0, /, refresh: bool = False) -> str:
"""
Replace the leftmost non-overlapping occurrences of ``self.pattern`` in ``s`` using ``self.repl``.
Wraps `re.sub`_
.. _re.sub:
- _https://docs.python.org/3/library/re.html#re.sub
+ https://docs.python.org/3/library/re.html#re.sub
"""
+ if not self._is_prepared or refresh:
+ self.refresh()
return self.pattern.sub(self.repl, s, count)
def _compile(self) -> Pattern[str]:
@@ -674,7 +678,7 @@ def parse_expressions(url: str, /) -> Iterator[VegaExprNode]:
Ensures each doc can use all remapped names, regardless of the order they appear.
"""
eager = tuple(_parse_expressions(url))
- VegaExprNode.remap_title.prepare()
+ VegaExprNode.remap_title.refresh()
for node in eager:
yield node.with_doc()
From b0b19521d54bda8fc23951de272c6a3fd6013649 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 13:12:49 +0100
Subject: [PATCH 34/77] docs: Add doc for `ReplaceMany`
- Already general enough to be used in other areas of `tools`.
- E.g. for `vega-lite` docs using `true`, `false`, `null` -> `True`, `False`, `None`
---
tools/schemapi/vega_expr.py | 46 +++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 0fb15a1a3..1b4557dad 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -247,6 +247,52 @@ def _doc_fmt(doc: str, /) -> str:
class ReplaceMany:
+ """
+ Perform many ``1:1`` replacements on a given text.
+
+ Structured wrapper around a `dict`_ and `re.sub`_.
+
+ Parameters
+ ----------
+ mapping
+ Optional initial mapping.
+ fmt_match
+ **Combined** format string/regex pattern.
+ Receives the keys of the final ``self._mapping`` as a positional argument.
+
+ .. note::
+ Special characters must be escaped **first**, if present.
+
+ fmt_replace
+ Format string applied to a succesful match, after substition.
+ Receives ``self._mapping[key]`` as a positional argument.
+
+ .. _dict:
+ https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
+ .. _re.sub:
+ https://docs.python.org/3/library/re.html#re.sub
+
+ Examples
+ --------
+ Providing a mapping during construction:
+
+ string = "The dog chased the cat, chasing the mouse. Poor mouse"
+ animal_replacer = ReplaceMany({"dog": "cat"})
+ >>> animal_replacer(string)
+ 'The cat chased the cat, chasing the mouse. Poor mouse'
+
+ Updating with new replacements:
+
+ animal_replacer.update({"cat": "mouse", "mouse": "dog"}, duck="rabbit")
+ >>> animal_replacer(string, refresh=True)
+ 'The cat chased the mouse, chasing the dog. Poor dog'
+
+ Further calls will continue using the most recent update:
+
+ >>> animal_replacer("duck")
+ 'rabbit'
+ """
+
def __init__(
self,
mapping: Mapping[str, str] | None = None,
From 2d5271e3362160912f41379913ad93a7c3f3ae60 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 13:53:11 +0100
Subject: [PATCH 35/77] refactor: Replace `VegaExprNode.parameter_names`
property with a filterable iterator
---
tools/schemapi/vega_expr.py | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 1b4557dad..7f8d024b4 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import dataclasses
-import functools
import keyword
import re
from collections import deque
@@ -418,10 +417,15 @@ def with_doc(self) -> Self:
self.doc = self._doc_post_process(parser.render_tokens(self._doc_tokens()))
return self
- @functools.cached_property
- def parameter_names(self) -> tuple[str, ...]:
+ def parameter_names(self, *, variadic: bool = True) -> Iterator[str]:
+ """Pass ``variadic=False`` to omit names like``*args``."""
if self.parameters:
- return tuple(param.name for param in self.parameters if not param.variadic)
+ it: Iterator[str] = (
+ (p.name for p in self.parameters)
+ if variadic
+ else (p.name for p in self.parameters if not p.variadic)
+ )
+ yield from it
else:
msg = (
f"Cannot provide `parameter_names` until they have been initialized via:\n"
@@ -506,7 +510,9 @@ def _doc_post_process(self, rendered: str, /) -> str:
# NOTE: Avoids adding backticks to parameter names that are also used in a link
# - All cases of these are for `unit|units`
pre, post = "[^`_]", "[^`]"
- pattern = rf"({pre})\*({'|'.join(self.parameter_names)})\*({post})"
+ pattern = (
+ rf"({pre})\*({'|'.join(self.parameter_names(variadic=False))})\*({post})"
+ )
highlight_params = re.sub(pattern, r"\g<1>``\g<2>``\g<3>", rendered)
with_alt_references = type(self).remap_title(highlight_params)
unescaped = mistune.util.unescape(with_alt_references)
@@ -866,7 +872,7 @@ def __new__(cls: type[{base}], expr: str) -> {base}: {type_ignore}
'''
-def render_expr_cls():
+def render_expr_cls() -> WorkInProgress:
return EXPR_CLS_TEMPLATE.format(
base="_ExprRef",
metaclass=CONST_META,
@@ -879,7 +885,7 @@ def render_expr_method(node: VegaExprNode, /) -> WorkInProgress:
if node.is_overloaded():
body_params = STAR_ARGS[1:]
else:
- body_params = f"({', '.join(param.name for param in node.parameters)})"
+ body_params = f"({', '.join(node.parameter_names())})"
body = f"return {RETURN_WRAPPER}({node.name!r}, {body_params})"
return EXPR_METHOD_TEMPLATE.format(
decorator=DECORATOR, signature=node.signature, doc=node.doc, body=body
From 849f428bc08e679f0410b58d1572c80d3d25d9b6 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 14:59:48 +0100
Subject: [PATCH 36/77] refactor: Tidy up `"clamprange"` -> `"clampRange"`
special case
- Was previously being handled in multiple places
- Now the correct name is set during the first access to `self.name`
---
tools/schemapi/vega_expr.py | 33 ++++++++++++++++++++-------------
1 file changed, 20 insertions(+), 13 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 7f8d024b4..915fb3def 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -404,13 +404,8 @@ def with_signature(self) -> Self:
return self
def with_parameters(self) -> Self:
- raw_texts = self._split_signature_tokens()
- name = next(raw_texts)
- # NOTE: Overwriting the with the rendered text
- # - required for `clamprange` -> `clampRange`
- if self.name != name:
- self.name = name
- self.parameters = list(VegaExprParam.iter_params(raw_texts))
+ split: Iterator[str] = self._split_signature_tokens(exclude_name=True)
+ self.parameters = list(VegaExprParam.iter_params(split))
return self
def with_doc(self) -> Self:
@@ -466,7 +461,7 @@ def _signature_tokens(self) -> Iterator[Token]:
else:
continue
- def _split_signature_tokens(self) -> Iterator[str]:
+ def _split_signature_tokens(self, *, exclude_name: bool = False) -> Iterator[str]:
"""
Normalize the text content of the signature.
@@ -482,10 +477,15 @@ def _split_signature_tokens(self) -> Iterator[str]:
Will yield:
['sequence', '(', '[', 'start', ']', 'stop', '[', 'step', ']', ')']
+
+ When called with ``exclude_name=True``:
+
+ ['(', '[', 'start', ']', 'stop', '[', 'step', ']', ')']
"""
+ EXCLUDE: set[str] = {", ", "", self.name} if exclude_name else {", ", ""}
for tok in self._signature_tokens():
clean = strip_include_tag(tok[RAW]).strip(", -")
- if clean not in {", ", ""}:
+ if clean not in EXCLUDE:
yield from VegaExprNode.deep_split_punctuation(clean)
def _doc_tokens(self) -> Sequence[Token]:
@@ -558,19 +558,26 @@ def is_callable(self) -> bool:
- Most of the parsing is to handle varying signatures.
- Constants can just be referenced by name, so can skip those
+ Notes
+ -----
+ - Overwriting the with the rendered text
+ - required for `clamprange` -> `clampRange`
+
.. _constants:
https://vega.github.io/vega/docs/expressions/#constants
"""
if self.is_overloaded_string_array() or self.is_bound_variable_name():
return False
it: Iterator[Token] = iter(self)
- current: str = next(it, {}).get(RAW, "").lower()
- name: str = self.name.lower()
- while current != name:
+ current: str = next(it, {}).get(RAW, "")
+ name: str = self.name.casefold()
+ while current.casefold() != name:
if (el := next(it, None)) is not None:
- current = el.get(RAW, "").lower()
+ current = el.get(RAW, "")
else:
return False
+ if current != self.name:
+ self.name = current
next(it)
return next(it).get(RAW, "") == OPEN_PAREN
From bed8d0faae908aa4cab27a7eba9db747018f9afd Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 15:11:36 +0100
Subject: [PATCH 37/77] refactor: Reorder `with_` methods, add docs
Little bit easier to see how they relate
---
tools/schemapi/vega_expr.py | 34 ++++++++++++++++++++++++----------
1 file changed, 24 insertions(+), 10 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 915fb3def..beff25271 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -389,8 +389,31 @@ def __init__(self, name: str, children: Sequence[Token], /) -> None:
self.doc: str = ""
self.signature: str = ""
+ def with_doc(self) -> Self:
+ """
+ Parses docstring content in full.
+
+ Accessible via ``self.doc``
+ """
+ self.doc = self._doc_post_process(parser.render_tokens(self._doc_tokens()))
+ return self
+
+ def with_parameters(self) -> Self:
+ """
+ Parses signature content into an intermediate representation.
+
+ Accessible via ``self.parameters``.
+ """
+ split: Iterator[str] = self._split_signature_tokens(exclude_name=True)
+ self.parameters = list(VegaExprParam.iter_params(split))
+ return self
+
def with_signature(self) -> Self:
- """NOTE: 101/147 cases are all required args."""
+ """
+ Parses ``self.parameters`` into a full signature definition line.
+
+ Accessible via ``self.signature``
+ """
pre_params = f"def {self.title}(cls, "
post_params = ")" if self.is_variadic() else ", /)"
post_params = f"{post_params} -> {RETURN_ANNOTATION}:"
@@ -403,15 +426,6 @@ def with_signature(self) -> Self:
self.signature = f"{pre_params}{param_list}{post_params}"
return self
- def with_parameters(self) -> Self:
- split: Iterator[str] = self._split_signature_tokens(exclude_name=True)
- self.parameters = list(VegaExprParam.iter_params(split))
- return self
-
- def with_doc(self) -> Self:
- self.doc = self._doc_post_process(parser.render_tokens(self._doc_tokens()))
- return self
-
def parameter_names(self, *, variadic: bool = True) -> Iterator[str]:
"""Pass ``variadic=False`` to omit names like``*args``."""
if self.parameters:
From 264e4a6578bbc8730ab1eeae9c18909b1892e24c Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 15:14:56 +0100
Subject: [PATCH 38/77] refactor: Rename reorder `_split_markers`
---
tools/schemapi/vega_expr.py | 64 ++++++++++++++++++-------------------
1 file changed, 32 insertions(+), 32 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index beff25271..a7fd7c2f3 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -500,7 +500,38 @@ def _split_signature_tokens(self, *, exclude_name: bool = False) -> Iterator[str
for tok in self._signature_tokens():
clean = strip_include_tag(tok[RAW]).strip(", -")
if clean not in EXCLUDE:
- yield from VegaExprNode.deep_split_punctuation(clean)
+ yield from VegaExprNode._split_markers(clean)
+
+ @staticmethod
+ def _split_markers(s: str, /) -> Iterator[str]:
+ """
+ When ``s`` ends with one of these markers:
+
+ ")", "]", "...", " |"
+
+ - Split ``s`` into rest, match
+ - using the length of the match to index
+ - Append match to ``end``
+ - Recurse
+ """ # noqa: D400
+ if s.isalnum():
+ yield s
+ else:
+ end: list[str] = []
+ if s.endswith((CLOSE_PAREN, CLOSE_BRACKET)):
+ end.append(s[-1])
+ s = s[:-1]
+ elif s.endswith(ELLIPSIS):
+ end.append(s[-3:])
+ s = s[:-3]
+ elif s.endswith(INLINE_OVERLOAD):
+ end.append(s[-2:])
+ s = s[:-2]
+ if len(s) == 1:
+ yield s
+ elif len(s) > 1:
+ yield from VegaExprNode._split_markers(s)
+ yield from end
def _doc_tokens(self) -> Sequence[Token]:
"""Return the slice of `self.children` that contains docstring content."""
@@ -534,37 +565,6 @@ def _doc_post_process(self, rendered: str, /) -> str:
numpydoc_style = _doc_fmt(non_relative_links)
return numpydoc_style
- @staticmethod
- def deep_split_punctuation(s: str, /) -> Iterator[str]:
- """
- When ``s`` ends with one of these markers:
-
- ")", "]", "...", " |"
-
- - Split ``s`` into rest, match
- - using the length of the match to index
- - Append match to ``end``
- - Recurse
- """ # noqa: D400
- if s.isalnum():
- yield s
- else:
- end: list[str] = []
- if s.endswith((CLOSE_PAREN, CLOSE_BRACKET)):
- end.append(s[-1])
- s = s[:-1]
- elif s.endswith(ELLIPSIS):
- end.append(s[-3:])
- s = s[:-3]
- elif s.endswith(INLINE_OVERLOAD):
- end.append(s[-2:])
- s = s[:-2]
- if len(s) == 1:
- yield s
- elif len(s) > 1:
- yield from VegaExprNode.deep_split_punctuation(s)
- yield from end
-
def is_callable(self) -> bool:
"""
Rough filter for excluding `constants`_.
From 7c24e4615b5ea1baa85c9850d88e800b7cc6e65d Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 15:28:02 +0100
Subject: [PATCH 39/77] refactor: Move url expansion to renderer
---
tools/schemapi/vega_expr.py | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index a7fd7c2f3..e960dcc69 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -143,11 +143,19 @@ def __init__(self) -> None:
super().__init__()
def link(self, token: Token, state: BlockState) -> str:
- """Store link url, for appending at the end of doc."""
+ """
+ Store link url, for appending at the end of doc.
+
+ TODO
+ ----
+ - Parameterize `"#"`, `"../"` expansion during init
+ """
attrs = token["attrs"]
url: str = attrs["url"]
if url.startswith("#"):
url = f"{EXPRESSIONS_DOCS_URL}{url}"
+ else:
+ url = url.replace(r"../", VEGA_DOCS_URL)
text = self.render_children(token, state)
text = text.replace("`", "")
inline = f"`{text}`_"
@@ -561,8 +569,7 @@ def _doc_post_process(self, rendered: str, /) -> str:
highlight_params = re.sub(pattern, r"\g<1>``\g<2>``\g<3>", rendered)
with_alt_references = type(self).remap_title(highlight_params)
unescaped = mistune.util.unescape(with_alt_references)
- non_relative_links = re.sub(r"\.\.\/", VEGA_DOCS_URL, unescaped)
- numpydoc_style = _doc_fmt(non_relative_links)
+ numpydoc_style = _doc_fmt(unescaped)
return numpydoc_style
def is_callable(self) -> bool:
From 051bcb228e0931fdd030fb5d36f0987cde5c8ead Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 17:36:01 +0100
Subject: [PATCH 40/77] feat: Adds initial `vega_expr` api
There are more things I want to change, but ideally keep `write_expr_module` consistent
---
tools/generate_schema_wrapper.py | 5 +++
tools/schemapi/__init__.py | 2 +
tools/schemapi/vega_expr.py | 67 +++++++++++++++++++++++++++++---
3 files changed, 68 insertions(+), 6 deletions(-)
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index a0fa08e72..6b2e24c51 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -27,6 +27,7 @@
arg_kwds,
arg_required_kwds,
codegen,
+ write_expr_module,
)
from tools.schemapi.utils import (
SchemaProperties,
@@ -61,6 +62,9 @@
SCHEMA_URL_TEMPLATE: Final = "https://vega.github.io/schema/{library}/{version}.json"
SCHEMA_FILE = "vega-lite-schema.json"
THEMES_FILE = "vega-themes.json"
+EXPR_FILE: Path = (
+ Path(__file__).parent / ".." / "altair" / "expr" / "dummy.py"
+).resolve()
CHANNEL_MYPY_IGNORE_STATEMENTS: Final = """\
# These errors need to be ignored as they come from the overload methods
@@ -1206,6 +1210,7 @@ def main() -> None:
args = parser.parse_args()
copy_schemapi_util()
vegalite_main(args.skip_download)
+ write_expr_module(source_url="static", output=EXPR_FILE)
# 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/schemapi/__init__.py b/tools/schemapi/__init__.py
index 55c5f4148..a08b8f44e 100644
--- a/tools/schemapi/__init__.py
+++ b/tools/schemapi/__init__.py
@@ -9,6 +9,7 @@
)
from tools.schemapi.schemapi import SchemaBase, Undefined
from tools.schemapi.utils import OneOrSeq, SchemaInfo
+from tools.schemapi.vega_expr import write_expr_module
__all__ = [
"CodeSnippet",
@@ -21,4 +22,5 @@
"arg_required_kwds",
"codegen",
"utils",
+ "write_expr_module",
]
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index e960dcc69..14ce9cfa7 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import dataclasses
+import enum
import keyword
import re
from collections import deque
@@ -29,6 +30,9 @@
from tools.schemapi.schemapi import SchemaBase as _SchemaBase
from tools.schemapi.utils import RSTParse as _RSTParse
from tools.schemapi.utils import RSTRenderer as _RSTRenderer
+from tools.schemapi.utils import (
+ ruff_write_lint_format_str as _ruff_write_lint_format_str,
+)
if TYPE_CHECKING:
import sys
@@ -42,18 +46,23 @@
from typing_extensions import LiteralString, Self, TypeAlias
from _typeshed import SupportsKeysAndGetItem
+__all__ = ["render_expr_full", "test_parse", "write_expr_module"]
+
Token: TypeAlias = "dict[str, Any]"
WorkInProgress: TypeAlias = Any
"""Marker for a type that will not be final."""
# NOTE: Urls/fragments
-EXPRESSIONS_URL = (
- "https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md"
-)
VEGA_DOCS_URL = "https://vega.github.io/vega/docs/"
EXPRESSIONS_DOCS_URL = f"{VEGA_DOCS_URL}expressions/"
+
+class Source(str, enum.Enum):
+ LIVE = "https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md"
+ STATIC = "https://raw.githubusercontent.com/vega/vega/ff98519cce32b776a98d01dd982467d76fc9ee34/docs/docs/expressions.md"
+
+
# NOTE: Regex patterns
FUNCTION_DEF_LINE: Pattern[str] = re.compile(r"")
LIQUID_INCLUDE: Pattern[str] = re.compile(r"( \{% include.+%\})")
@@ -354,7 +363,7 @@ def __call__(self, s: str, count: int = 0, /, refresh: bool = False) -> str:
def _compile(self) -> Pattern[str]:
if not self._mapping:
- name = self._mapping.__qualname__
+ name = self._mapping.__qualname__ # type: ignore[attr-defined]
msg = (
f"Requires {name!r} to be populated, but got:\n"
f"{name}={self._mapping!r}"
@@ -922,11 +931,12 @@ def render_expr_method(node: VegaExprNode, /) -> WorkInProgress:
def test_parse() -> dict[str, VegaExprNode]:
"""Temporary introspection tool."""
- return {node.name: node for node in parse_expressions(EXPRESSIONS_URL)}
+ return {node.name: node for node in parse_expressions(Source.LIVE.value)}
def render_expr_full() -> str:
- it = (render_expr_method(node) for node in parse_expressions(EXPRESSIONS_URL))
+ """Temporary sample of **pre-ruff** module."""
+ it = (render_expr_method(node) for node in parse_expressions(Source.LIVE.value))
return "\n".join(
chain(
(
@@ -943,3 +953,48 @@ def render_expr_full() -> str:
[EXPR_MODULE_POST],
)
)
+
+
+def write_expr_module(
+ source_url: Literal["live", "static"] | str, output: Path
+) -> None:
+ """
+ Parse an ``expressions.md`` into a ``.py`` module.
+
+ Parameters
+ ----------
+ source_url
+ - ``"live"``: current version
+ - ``"static"``: most recent version available during testing
+ - Or provide an alternative as a ``str``
+ output
+ Target path to write to.
+ """
+ if source_url == "live":
+ url = Source.LIVE.value
+ elif source_url == "static":
+ url = Source.STATIC.value
+ else:
+ url = source_url
+ content = (
+ EXPR_MODULE_PRE.format(
+ metaclass=CONST_META,
+ const=CONST_WRAPPER,
+ return_ann=RETURN_ANNOTATION,
+ input_ann=INPUT_ANNOTATION,
+ func=RETURN_WRAPPER,
+ ),
+ EXPR_CLS_TEMPLATE.format(
+ base="_ExprRef",
+ metaclass=CONST_META,
+ doc=EXPR_CLS_DOC,
+ type_ignore=IGNORE_MISC,
+ ),
+ )
+ contents = chain(
+ content,
+ (render_expr_method(node) for node in parse_expressions(url)),
+ [EXPR_MODULE_POST],
+ )
+ print(f"Generating\n {url!s}\n ->{output!s}")
+ _ruff_write_lint_format_str(output, contents)
From 5e75051a1977016ca3b34f45f68e64f3031d25f2 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 17:39:11 +0100
Subject: [PATCH 41/77] build: run `generate-schema-wrapper`
- Need to do some experimenting with a new test suite for `expr`
- Temporarily isolating this to `expr.dummy.py`
- Will later replace `expr.__init__.py`
---
altair/expr/dummy.py | 1678 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1678 insertions(+)
create mode 100644 altair/expr/dummy.py
diff --git a/altair/expr/dummy.py b/altair/expr/dummy.py
new file mode 100644
index 000000000..78b72f96e
--- /dev/null
+++ b/altair/expr/dummy.py
@@ -0,0 +1,1678 @@
+"""Tools for creating transform & filter expressions with a python syntax."""
+
+from __future__ import annotations
+
+import sys
+from typing import Any
+
+from altair.expr.core import (
+ ConstExpression,
+ Expression,
+ FunctionExpression,
+ IntoExpression,
+)
+from altair.vegalite.v5.schema.core import ExprRef as _ExprRef
+
+if sys.version_info >= (3, 12):
+ from typing import override
+else:
+ from typing_extensions import override
+
+
+class _ConstExpressionType(type):
+ """Metaclass providing read-only class properties for :class:`expr`."""
+
+ @property
+ def NaN(cls) -> Expression:
+ """Not a number (same as JavaScript literal NaN)."""
+ return ConstExpression("NaN")
+
+ @property
+ def LN10(cls) -> Expression:
+ """The natural log of 10 (alias to Math.LN10)."""
+ return ConstExpression("LN10")
+
+ @property
+ def E(cls) -> Expression:
+ """The transcendental number e (alias to Math.E)."""
+ return ConstExpression("E")
+
+ @property
+ def LOG10E(cls) -> Expression:
+ """The base 10 logarithm e (alias to Math.LOG10E)."""
+ return ConstExpression("LOG10E")
+
+ @property
+ def LOG2E(cls) -> Expression:
+ """The base 2 logarithm of e (alias to Math.LOG2E)."""
+ return ConstExpression("LOG2E")
+
+ @property
+ def SQRT1_2(cls) -> Expression:
+ """The square root of 0.5 (alias to Math.SQRT1_2)."""
+ return ConstExpression("SQRT1_2")
+
+ @property
+ def LN2(cls) -> Expression:
+ """The natural log of 2 (alias to Math.LN2)."""
+ return ConstExpression("LN2")
+
+ @property
+ def SQRT2(cls) -> Expression:
+ """The square root of 2 (alias to Math.SQRT1_2)."""
+ return ConstExpression("SQRT2")
+
+ @property
+ def PI(cls) -> Expression:
+ """The transcendental number pi (alias to Math.PI)."""
+ return ConstExpression("PI")
+
+
+class expr(_ExprRef, metaclass=_ConstExpressionType):
+ """
+ Utility providing *constants* and *classmethods* to construct expressions.
+
+ `Expressions`_ can be used to write basic formulas that enable custom interactions.
+
+ Alternatively, an `inline expression`_ may be defined via :class:`expr()`.
+
+ Parameters
+ ----------
+ expr: str
+ A `vega expression`_ string.
+
+ Returns
+ -------
+ ``ExprRef``
+
+ .. _Expressions:
+ https://altair-viz.github.io/user_guide/interactions.html#expressions
+ .. _inline expression:
+ https://altair-viz.github.io/user_guide/interactions.html#inline-expressions
+ .. _vega expression:
+ https://vega.github.io/vega/docs/expressions/
+
+ Examples
+ --------
+ >>> import altair as alt
+
+ >>> bind_range = alt.binding_range(min=100, max=300, name="Slider value: ")
+ >>> param_width = alt.param(bind=bind_range, name="param_width")
+ >>> param_color = alt.param(
+ ... expr=alt.expr.if_(param_width < 200, "red", "black"),
+ ... name="param_color",
+ ... )
+ >>> y = alt.Y("yval").axis(titleColor=param_color)
+
+ >>> y
+ Y({
+ axis: {'titleColor': Parameter('param_color', VariableParameter({
+ expr: if((param_width < 200),'red','black'),
+ name: 'param_color'
+ }))},
+ shorthand: 'yval'
+ })
+ """
+
+ @override
+ def __new__(cls: type[_ExprRef], expr: str) -> _ExprRef: # type: ignore[misc]
+ return _ExprRef(expr=expr)
+
+ @classmethod
+ def isArray(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is an array, false otherwise."""
+ return FunctionExpression("isArray", (value))
+
+ @classmethod
+ def isBoolean(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is a boolean (``true`` or ``false``), false otherwise."""
+ return FunctionExpression("isBoolean", (value))
+
+ @classmethod
+ def isDate(cls, value: IntoExpression, /) -> Expression:
+ """
+ Returns true if ``value`` is a Date object, false otherwise.
+
+ This method will return false for timestamp numbers or date-formatted strings; it recognizes
+ Date objects only.
+ """
+ return FunctionExpression("isDate", (value))
+
+ @classmethod
+ def isDefined(cls, value: IntoExpression, /) -> Expression:
+ """
+ Returns true if ``value`` is a defined value, false if ``value`` equals ``undefined``.
+
+ This method will return true for ``null`` and ``NaN`` values.
+ """
+ return FunctionExpression("isDefined", (value))
+
+ @classmethod
+ def isNumber(cls, value: IntoExpression, /) -> Expression:
+ """
+ Returns true if ``value`` is a number, false otherwise.
+
+ ``NaN`` and ``Infinity`` are considered numbers.
+ """
+ return FunctionExpression("isNumber", (value))
+
+ @classmethod
+ def isObject(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is an object (including arrays and Dates), false otherwise."""
+ return FunctionExpression("isObject", (value))
+
+ @classmethod
+ def isRegExp(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is a RegExp (regular expression) object, false otherwise."""
+ return FunctionExpression("isRegExp", (value))
+
+ @classmethod
+ def isString(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is a string, false otherwise."""
+ return FunctionExpression("isString", (value))
+
+ @classmethod
+ def isValid(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is not ``null``, ``undefined``, or ``NaN``, false otherwise."""
+ return FunctionExpression("isValid", (value))
+
+ @classmethod
+ def toBoolean(cls, value: IntoExpression, /) -> Expression:
+ """
+ Coerces the input ``value`` to a string.
+
+ Null values and empty strings are mapped to ``null``.
+ """
+ return FunctionExpression("toBoolean", (value))
+
+ @classmethod
+ def toDate(cls, value: IntoExpression, /) -> Expression:
+ """
+ Coerces the input ``value`` to a Date instance.
+
+ Null values and empty strings are mapped to ``null``. If an optional *parser* function is
+ provided, it is used to perform date parsing, otherwise ``Date.parse`` is used. Be aware
+ that ``Date.parse`` has different implementations across browsers!
+ """
+ return FunctionExpression("toDate", (value))
+
+ @classmethod
+ def toNumber(cls, value: IntoExpression, /) -> Expression:
+ """
+ Coerces the input ``value`` to a number.
+
+ Null values and empty strings are mapped to ``null``.
+ """
+ return FunctionExpression("toNumber", (value))
+
+ @classmethod
+ def toString(cls, value: IntoExpression, /) -> Expression:
+ """
+ Coerces the input ``value`` to a string.
+
+ Null values and empty strings are mapped to ``null``.
+ """
+ return FunctionExpression("toString", (value))
+
+ @classmethod
+ def if_(
+ cls,
+ test: IntoExpression,
+ thenValue: IntoExpression,
+ elseValue: IntoExpression,
+ /,
+ ) -> Expression:
+ """
+ If ``test`` is truthy, returns ``thenValue``.
+
+ Otherwise, returns ``elseValue``. The *if* function is equivalent to the ternary operator
+ ``a ? b : c``.
+ """
+ return FunctionExpression("if", (test, thenValue, elseValue))
+
+ @classmethod
+ def isNaN(cls, value: IntoExpression, /) -> Expression:
+ """
+ Returns true if ``value`` is not a number.
+
+ Same as JavaScript's `Number.isNaN`_.
+
+ .. _Number.isNaN:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNan
+ """
+ return FunctionExpression("isNaN", (value))
+
+ @classmethod
+ def isFinite(cls, value: IntoExpression, /) -> Expression:
+ """
+ Returns true if ``value`` is a finite number.
+
+ Same as JavaScript's `Number.isFinite`_.
+
+ .. _Number.isFinite:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite
+ """
+ return FunctionExpression("isFinite", (value))
+
+ @classmethod
+ def abs(cls, value: IntoExpression, /) -> Expression:
+ """
+ Returns the absolute value of ``value``.
+
+ Same as JavaScript's `Math.abs`_.
+
+ .. _Math.abs:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs
+ """
+ return FunctionExpression("abs", (value))
+
+ @classmethod
+ def acos(cls, value: IntoExpression, /) -> Expression:
+ """
+ Trigonometric arccosine.
+
+ Same as JavaScript's `Math.acos`_.
+
+ .. _Math.acos:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos
+ """
+ return FunctionExpression("acos", (value))
+
+ @classmethod
+ def asin(cls, value: IntoExpression, /) -> Expression:
+ """
+ Trigonometric arcsine.
+
+ Same as JavaScript's `Math.asin`_.
+
+ .. _Math.asin:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin
+ """
+ return FunctionExpression("asin", (value))
+
+ @classmethod
+ def atan(cls, value: IntoExpression, /) -> Expression:
+ """
+ Trigonometric arctangent.
+
+ Same as JavaScript's `Math.atan`_.
+
+ .. _Math.atan:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan
+ """
+ return FunctionExpression("atan", (value))
+
+ @classmethod
+ def atan2(cls, dy: IntoExpression, dx: IntoExpression, /) -> Expression:
+ """
+ Returns the arctangent of *dy / dx*.
+
+ Same as JavaScript's `Math.atan2`_.
+
+ .. _Math.atan2:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2
+ """
+ return FunctionExpression("atan2", (dy, dx))
+
+ @classmethod
+ def ceil(cls, value: IntoExpression, /) -> Expression:
+ """
+ Rounds ``value`` to the nearest integer of equal or greater value.
+
+ Same as JavaScript's `Math.ceil`_.
+
+ .. _Math.ceil:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil
+ """
+ return FunctionExpression("ceil", (value))
+
+ @classmethod
+ def clamp(
+ cls, value: IntoExpression, min: IntoExpression, max: IntoExpression, /
+ ) -> Expression:
+ """Restricts ``value`` to be between the specified ``min`` and ``max``."""
+ return FunctionExpression("clamp", (value, min, max))
+
+ @classmethod
+ def cos(cls, value: IntoExpression, /) -> Expression:
+ """
+ Trigonometric cosine.
+
+ Same as JavaScript's `Math.cos`_.
+
+ .. _Math.cos:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos
+ """
+ return FunctionExpression("cos", (value))
+
+ @classmethod
+ def exp(cls, exponent: IntoExpression, /) -> Expression:
+ """
+ Returns the value of *e* raised to the provided ``exponent``.
+
+ Same as JavaScript's `Math.exp`_.
+
+ .. _Math.exp:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp
+ """
+ return FunctionExpression("exp", (exponent))
+
+ @classmethod
+ def floor(cls, value: IntoExpression, /) -> Expression:
+ """
+ Rounds ``value`` to the nearest integer of equal or lower value.
+
+ Same as JavaScript's `Math.floor`_.
+
+ .. _Math.floor:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor
+ """
+ return FunctionExpression("floor", (value))
+
+ @classmethod
+ def hypot(cls, value: IntoExpression, /) -> Expression:
+ """
+ Returns the square root of the sum of squares of its arguments.
+
+ Same as JavaScript's `Math.hypot`_.
+
+ .. _Math.hypot:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot
+ """
+ return FunctionExpression("hypot", (value))
+
+ @classmethod
+ def log(cls, value: IntoExpression, /) -> Expression:
+ """
+ Returns the natural logarithm of ``value``.
+
+ Same as JavaScript's `Math.log`_.
+
+ .. _Math.log:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log
+ """
+ return FunctionExpression("log", (value))
+
+ @classmethod
+ def max(
+ cls, value1: IntoExpression, value2: IntoExpression, *args: Any
+ ) -> Expression:
+ """
+ Returns the maximum argument value.
+
+ Same as JavaScript's `Math.max`_.
+
+ .. _Math.max:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max
+ """
+ return FunctionExpression("max", (value1, value2, *args))
+
+ @classmethod
+ def min(
+ cls, value1: IntoExpression, value2: IntoExpression, *args: Any
+ ) -> Expression:
+ """
+ Returns the minimum argument value.
+
+ Same as JavaScript's `Math.min`_.
+
+ .. _Math.min:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min
+ """
+ return FunctionExpression("min", (value1, value2, *args))
+
+ @classmethod
+ def pow(cls, value: IntoExpression, exponent: IntoExpression, /) -> Expression:
+ """
+ Returns ``value`` raised to the given ``exponent``.
+
+ Same as JavaScript's `Math.pow`_.
+
+ .. _Math.pow:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow
+ """
+ return FunctionExpression("pow", (value, exponent))
+
+ @classmethod
+ def round(cls, value: IntoExpression, /) -> Expression:
+ """
+ Rounds ``value`` to the nearest integer.
+
+ Same as JavaScript's `Math.round`_.
+
+ .. _Math.round:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
+ """
+ return FunctionExpression("round", (value))
+
+ @classmethod
+ def sin(cls, value: IntoExpression, /) -> Expression:
+ """
+ Trigonometric sine.
+
+ Same as JavaScript's `Math.sin`_.
+
+ .. _Math.sin:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin
+ """
+ return FunctionExpression("sin", (value))
+
+ @classmethod
+ def sqrt(cls, value: IntoExpression, /) -> Expression:
+ """
+ Square root function.
+
+ Same as JavaScript's `Math.sqrt`_.
+
+ .. _Math.sqrt:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt
+ """
+ return FunctionExpression("sqrt", (value))
+
+ @classmethod
+ def tan(cls, value: IntoExpression, /) -> Expression:
+ """
+ Trigonometric tangent.
+
+ Same as JavaScript's `Math.tan`_.
+
+ .. _Math.tan:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan
+ """
+ return FunctionExpression("tan", (value))
+
+ @classmethod
+ def sampleNormal(
+ cls, mean: IntoExpression = None, stdev: IntoExpression = None, /
+ ) -> Expression:
+ """
+ Returns a sample from a univariate `normal (Gaussian) probability distribution`_ with specified ``mean`` and standard deviation ``stdev``.
+
+ If unspecified, the mean defaults to ``0`` and the standard deviation defaults to ``1``.
+
+ .. _normal (Gaussian) probability distribution:
+ https://en.wikipedia.org/wiki/Normal_distribution
+ """
+ return FunctionExpression("sampleNormal", (mean, stdev))
+
+ @classmethod
+ def sampleLogNormal(
+ cls, mean: IntoExpression = None, stdev: IntoExpression = None, /
+ ) -> Expression:
+ """
+ Returns a sample from a univariate `log-normal probability distribution`_ with specified log ``mean`` and log standard deviation ``stdev``.
+
+ If unspecified, the log mean defaults to ``0`` and the log standard deviation defaults to
+ ``1``.
+
+ .. _log-normal probability distribution:
+ https://en.wikipedia.org/wiki/Log-normal_distribution
+ """
+ return FunctionExpression("sampleLogNormal", (mean, stdev))
+
+ @classmethod
+ def sampleUniform(
+ cls, min: IntoExpression = None, max: IntoExpression = None, /
+ ) -> Expression:
+ """
+ Returns a sample from a univariate `continuous uniform probability distribution`_) over the interval [``min``, ``max``).
+
+ If unspecified, ``min`` defaults to ``0`` and ``max`` defaults to ``1``. If only one
+ argument is provided, it is interpreted as the ``max`` value.
+
+ .. _continuous uniform probability distribution:
+ https://en.wikipedia.org/wiki/Uniform_distribution_(continuous
+ """
+ return FunctionExpression("sampleUniform", (min, max))
+
+ @classmethod
+ def datetime(
+ cls,
+ year: IntoExpression,
+ month: IntoExpression,
+ day: IntoExpression = None,
+ hour: IntoExpression = None,
+ min: IntoExpression = None,
+ sec: IntoExpression = None,
+ millisec: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns a new ``Date`` instance.
+
+ The ``month`` is 0-based, such that ``1`` represents February.
+ """
+ return FunctionExpression(
+ "datetime", (year, month, day, hour, min, sec, millisec)
+ )
+
+ @classmethod
+ def date(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the day of the month for the given ``datetime`` value, in local time."""
+ return FunctionExpression("date", (datetime))
+
+ @classmethod
+ def day(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the day of the week for the given ``datetime`` value, in local time."""
+ return FunctionExpression("day", (datetime))
+
+ @classmethod
+ def dayofyear(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the one-based day of the year for the given ``datetime`` value, in local time."""
+ return FunctionExpression("dayofyear", (datetime))
+
+ @classmethod
+ def year(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the year for the given ``datetime`` value, in local time."""
+ return FunctionExpression("year", (datetime))
+
+ @classmethod
+ def quarter(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the quarter of the year (0-3) for the given ``datetime`` value, in local time."""
+ return FunctionExpression("quarter", (datetime))
+
+ @classmethod
+ def month(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the (zero-based) month for the given ``datetime`` value, in local time."""
+ return FunctionExpression("month", (datetime))
+
+ @classmethod
+ def week(cls, date: IntoExpression, /) -> Expression:
+ """
+ Returns the week number of the year for the given *datetime*, in local time.
+
+ This function assumes Sunday-based weeks. Days before the first Sunday of the year are
+ considered to be in week 0, the first Sunday of the year is the start of week 1, the second
+ Sunday week 2, *etc.*.
+ """
+ return FunctionExpression("week", (date))
+
+ @classmethod
+ def hours(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the hours component for the given ``datetime`` value, in local time."""
+ return FunctionExpression("hours", (datetime))
+
+ @classmethod
+ def minutes(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the minutes component for the given ``datetime`` value, in local time."""
+ return FunctionExpression("minutes", (datetime))
+
+ @classmethod
+ def seconds(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the seconds component for the given ``datetime`` value, in local time."""
+ return FunctionExpression("seconds", (datetime))
+
+ @classmethod
+ def milliseconds(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the milliseconds component for the given ``datetime`` value, in local time."""
+ return FunctionExpression("milliseconds", (datetime))
+
+ @classmethod
+ def time(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the epoch-based timestamp for the given ``datetime`` value."""
+ return FunctionExpression("time", (datetime))
+
+ @classmethod
+ def timezoneoffset(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the timezone offset from the local timezone to UTC for the given ``datetime`` value."""
+ return FunctionExpression("timezoneoffset", (datetime))
+
+ @classmethod
+ def timeOffset(
+ cls, unit: IntoExpression, date: IntoExpression, step: IntoExpression = None, /
+ ) -> Expression:
+ """
+ Returns a new ``Date`` instance that offsets the given ``date`` by the specified time `*unit*`_ in the local timezone.
+
+ The optional ``step`` argument indicates the number of time unit steps to offset by (default
+ 1).
+
+ .. _*unit*:
+ https://vega.github.io/vega/docs/api/time/#time-units
+ """
+ return FunctionExpression("timeOffset", (unit, date, step))
+
+ @classmethod
+ def timeSequence(
+ cls,
+ unit: IntoExpression,
+ start: IntoExpression,
+ stop: IntoExpression,
+ step: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns an array of ``Date`` instances from ``start`` (inclusive) to ``stop`` (exclusive), with each entry separated by the given time `*unit*`_ in the local timezone.
+
+ The optional ``step`` argument indicates the number of time unit steps to take between each
+ sequence entry (default 1).
+
+ .. _*unit*:
+ https://vega.github.io/vega/docs/api/time/#time-units
+ """
+ return FunctionExpression("timeSequence", (unit, start, stop, step))
+
+ @classmethod
+ def utc(
+ cls,
+ year: IntoExpression,
+ month: IntoExpression,
+ day: IntoExpression = None,
+ hour: IntoExpression = None,
+ min: IntoExpression = None,
+ sec: IntoExpression = None,
+ millisec: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns a timestamp for the given UTC date.
+
+ The ``month`` is 0-based, such that ``1`` represents February.
+ """
+ return FunctionExpression("utc", (year, month, day, hour, min, sec, millisec))
+
+ @classmethod
+ def utcdate(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the day of the month for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcdate", (datetime))
+
+ @classmethod
+ def utcday(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the day of the week for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcday", (datetime))
+
+ @classmethod
+ def utcdayofyear(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the one-based day of the year for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcdayofyear", (datetime))
+
+ @classmethod
+ def utcyear(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the year for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcyear", (datetime))
+
+ @classmethod
+ def utcquarter(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the quarter of the year (0-3) for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcquarter", (datetime))
+
+ @classmethod
+ def utcmonth(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the (zero-based) month for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcmonth", (datetime))
+
+ @classmethod
+ def utcweek(cls, date: IntoExpression, /) -> Expression:
+ """
+ Returns the week number of the year for the given *datetime*, in UTC time.
+
+ This function assumes Sunday-based weeks. Days before the first Sunday of the year are
+ considered to be in week 0, the first Sunday of the year is the start of week 1, the second
+ Sunday week 2, *etc.*.
+ """
+ return FunctionExpression("utcweek", (date))
+
+ @classmethod
+ def utchours(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the hours component for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utchours", (datetime))
+
+ @classmethod
+ def utcminutes(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the minutes component for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcminutes", (datetime))
+
+ @classmethod
+ def utcseconds(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the seconds component for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcseconds", (datetime))
+
+ @classmethod
+ def utcmilliseconds(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the milliseconds component for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcmilliseconds", (datetime))
+
+ @classmethod
+ def utcOffset(
+ cls, unit: IntoExpression, date: IntoExpression, step: IntoExpression = None, /
+ ) -> Expression:
+ """
+ Returns a new ``Date`` instance that offsets the given ``date`` by the specified time `*unit*`_ in UTC time.
+
+ The optional ``step`` argument indicates the number of time unit steps to offset by (default
+ 1).
+
+ .. _*unit*:
+ https://vega.github.io/vega/docs/api/time/#time-units
+ """
+ return FunctionExpression("utcOffset", (unit, date, step))
+
+ @classmethod
+ def utcSequence(
+ cls,
+ unit: IntoExpression,
+ start: IntoExpression,
+ stop: IntoExpression,
+ step: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns an array of ``Date`` instances from ``start`` (inclusive) to ``stop`` (exclusive), with each entry separated by the given time `*unit*`_ in UTC time.
+
+ The optional ``step`` argument indicates the number of time unit steps to take between each
+ sequence entry (default 1).
+
+ .. _*unit*:
+ https://vega.github.io/vega/docs/api/time/#time-units
+ """
+ return FunctionExpression("utcSequence", (unit, start, stop, step))
+
+ @classmethod
+ def extent(cls, array: IntoExpression, /) -> Expression:
+ """Returns a new *[min, max]* array with the minimum and maximum values of the input array, ignoring ``null``, ``undefined``, and ``NaN`` values."""
+ return FunctionExpression("extent", (array))
+
+ @classmethod
+ def clampRange(
+ cls, range: IntoExpression, min: IntoExpression, max: IntoExpression, /
+ ) -> Expression:
+ """
+ Clamps a two-element ``range`` array in a span-preserving manner.
+
+ If the span of the input ``range`` is less than *(max - min)* and an endpoint exceeds either
+ the ``min`` or ``max`` value, the range is translated such that the span is preserved and
+ one endpoint touches the boundary of the *[min, max]* range. If the span exceeds *(max -
+ min)*, the range *[min, max]* is returned.
+ """
+ return FunctionExpression("clampRange", (range, min, max))
+
+ @classmethod
+ def indexof(cls, array: IntoExpression, value: IntoExpression, /) -> Expression:
+ """Returns the first index of ``value`` in the input ``array``."""
+ return FunctionExpression("indexof", (array, value))
+
+ @classmethod
+ def inrange(cls, value: IntoExpression, range: IntoExpression, /) -> Expression:
+ """Tests whether ``value`` lies within (or is equal to either) the first and last values of the ``range`` array."""
+ return FunctionExpression("inrange", (value, range))
+
+ @classmethod
+ def join(
+ cls, array: IntoExpression, separator: IntoExpression = None, /
+ ) -> Expression:
+ """Returns a new string by concatenating all of the elements of the input ``array``, separated by commas or a specified ``separator`` string."""
+ return FunctionExpression("join", (array, separator))
+
+ @classmethod
+ def lastindexof(cls, array: IntoExpression, value: IntoExpression, /) -> Expression:
+ """Returns the last index of ``value`` in the input ``array``."""
+ return FunctionExpression("lastindexof", (array, value))
+
+ @classmethod
+ def length(cls, array: IntoExpression, /) -> Expression:
+ """Returns the length of the input ``array``."""
+ return FunctionExpression("length", (array))
+
+ @classmethod
+ def lerp(cls, array: IntoExpression, fraction: IntoExpression, /) -> Expression:
+ """
+ Returns the linearly interpolated value between the first and last entries in the ``array`` for the provided interpolation ``fraction`` (typically between 0 and 1).
+
+ For example, ``alt.expr.lerp([0, 50], 0.5)`` returns 25.
+ """
+ return FunctionExpression("lerp", (array, fraction))
+
+ @classmethod
+ def peek(cls, array: IntoExpression, /) -> Expression:
+ """
+ Returns the last element in the input ``array``.
+
+ Similar to the built-in ``Array.pop`` method, except that it does not remove the last
+ element. This method is a convenient shorthand for ``array[array.length - 1]``.
+ """
+ return FunctionExpression("peek", (array))
+
+ @classmethod
+ def pluck(cls, array: IntoExpression, field: IntoExpression, /) -> Expression:
+ """
+ Retrieves the value for the specified ``field`` from a given ``array`` of objects.
+
+ The input ``field`` string may include nested properties (e.g., ``foo.bar.bz``).
+ """
+ return FunctionExpression("pluck", (array, field))
+
+ @classmethod
+ def reverse(cls, array: IntoExpression, /) -> Expression:
+ """
+ Returns a new array with elements in a reverse order of the input ``array``.
+
+ The first array element becomes the last, and the last array element becomes the first.
+ """
+ return FunctionExpression("reverse", (array))
+
+ @classmethod
+ def sequence(cls, *args: Any) -> Expression:
+ """
+ Returns an array containing an arithmetic sequence of numbers.
+
+ If ``step`` is omitted, it defaults to 1. If ``start`` is omitted, it defaults to 0. The
+ ``stop`` value is exclusive; it is not included in the result. If ``step`` is positive, the
+ last element is the largest *start + i * step* less than ``stop``; if ``step`` is negative,
+ the last element is the smallest *start + i * step* greater than ``stop``. If the returned
+ array would contain an infinite number of values, an empty range is returned. The arguments
+ are not required to be integers.
+ """
+ return FunctionExpression("sequence", args)
+
+ @classmethod
+ def slice(
+ cls, array: IntoExpression, start: IntoExpression, end: IntoExpression = None, /
+ ) -> Expression:
+ """
+ Returns a section of ``array`` between the ``start`` and ``end`` indices.
+
+ If the ``end`` argument is negative, it is treated as an offset from the end of the array
+ (*alt.expr.length(array) + end*).
+ """
+ return FunctionExpression("slice", (array, start, end))
+
+ @classmethod
+ def span(cls, array: IntoExpression, /) -> Expression:
+ """Returns the span of ``array``: the difference between the last and first elements, or *array[array.length-1] - array[0]*."""
+ return FunctionExpression("span", (array))
+
+ @classmethod
+ def lower(cls, string: IntoExpression, /) -> Expression:
+ """Transforms ``string`` to lower-case letters."""
+ return FunctionExpression("lower", (string))
+
+ @classmethod
+ def pad(
+ cls,
+ string: IntoExpression,
+ length: IntoExpression,
+ character: IntoExpression = None,
+ align: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Pads a ``string`` value with repeated instances of a ``character`` up to a specified ``length``.
+
+ If ``character`` is not specified, a space (' ') is used. By default, padding is added to
+ the end of a string. An optional ``align`` parameter specifies if padding should be added to
+ the ``'left'`` (beginning), ``'center'``, or ``'right'`` (end) of the input string.
+ """
+ return FunctionExpression("pad", (string, length, character, align))
+
+ @classmethod
+ def parseFloat(cls, string: IntoExpression, /) -> Expression:
+ """
+ Parses the input ``string`` to a floating-point value.
+
+ Same as JavaScript's ``parseFloat``.
+ """
+ return FunctionExpression("parseFloat", (string))
+
+ @classmethod
+ def parseInt(cls, string: IntoExpression, /) -> Expression:
+ """
+ Parses the input ``string`` to an integer value.
+
+ Same as JavaScript's ``parseInt``.
+ """
+ return FunctionExpression("parseInt", (string))
+
+ @classmethod
+ def replace(
+ cls,
+ string: IntoExpression,
+ pattern: IntoExpression,
+ replacement: IntoExpression,
+ /,
+ ) -> Expression:
+ """
+ Returns a new string with some or all matches of ``pattern`` replaced by a ``replacement`` string.
+
+ The ``pattern`` can be a string or a regular expression. If ``pattern`` is a string, only
+ the first instance will be replaced. Same as `JavaScript's String.replace`_.
+
+ .. _JavaScript's String.replace:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
+ """
+ return FunctionExpression("replace", (string, pattern, replacement))
+
+ @classmethod
+ def substring(
+ cls,
+ string: IntoExpression,
+ start: IntoExpression,
+ end: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """Returns a section of ``string`` between the ``start`` and ``end`` indices."""
+ return FunctionExpression("substring", (string, start, end))
+
+ @classmethod
+ def trim(cls, string: IntoExpression, /) -> Expression:
+ """Returns a trimmed string with preceding and trailing whitespace removed."""
+ return FunctionExpression("trim", (string))
+
+ @classmethod
+ def truncate(
+ cls,
+ string: IntoExpression,
+ length: IntoExpression,
+ align: IntoExpression = None,
+ ellipsis: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Truncates an input ``string`` to a target ``length``.
+
+ The optional ``align`` argument indicates what part of the string should be truncated:
+ ``'left'`` (the beginning), ``'center'``, or ``'right'`` (the end). By default, the
+ ``'right'`` end of the string is truncated. The optional ``ellipsis`` argument indicates the
+ string to use to indicate truncated content; by default the ellipsis character ``…``
+ (``\u2026``) is used.
+ """
+ return FunctionExpression("truncate", (string, length, align, ellipsis))
+
+ @classmethod
+ def upper(cls, string: IntoExpression, /) -> Expression:
+ """Transforms ``string`` to upper-case letters."""
+ return FunctionExpression("upper", (string))
+
+ @classmethod
+ def merge(
+ cls, object1: IntoExpression, object2: IntoExpression = None, *args: Any
+ ) -> Expression:
+ """
+ Merges the input objects ``object1``, ``object2``, etc into a new output object.
+
+ Inputs are visited in sequential order, such that key values from later arguments can
+ overwrite those from earlier arguments. Example: ``alt.expr.merge({a:1, b:2}, {a:3}) ->
+ {a:3, b:2}``.
+ """
+ return FunctionExpression("merge", (object1, object2, *args))
+
+ @classmethod
+ def dayFormat(cls, day: IntoExpression, /) -> Expression:
+ """
+ Formats a (0-6) *weekday* number as a full week day name, according to the current locale.
+
+ For example: ``alt.expr.dayFormat(0) -> "Sunday"``.
+ """
+ return FunctionExpression("dayFormat", (day))
+
+ @classmethod
+ def dayAbbrevFormat(cls, day: IntoExpression, /) -> Expression:
+ """
+ Formats a (0-6) *weekday* number as an abbreviated week day name, according to the current locale.
+
+ For example: ``alt.expr.dayAbbrevFormat(0) -> "Sun"``.
+ """
+ return FunctionExpression("dayAbbrevFormat", (day))
+
+ @classmethod
+ def format(cls, value: IntoExpression, specifier: IntoExpression, /) -> Expression:
+ """
+ Formats a numeric ``value`` as a string.
+
+ The ``specifier`` must be a valid `d3-format specifier`_ (e.g., ``alt.expr.format(value,
+ ',.2f')``. Null values are formatted as ``"null"``.
+
+ .. _d3-format specifier:
+ https://github.com/d3/d3-format/
+ """
+ return FunctionExpression("format", (value, specifier))
+
+ @classmethod
+ def monthFormat(cls, month: IntoExpression, /) -> Expression:
+ """
+ Formats a (zero-based) ``month`` number as a full month name, according to the current locale.
+
+ For example: ``alt.expr.monthFormat(0) -> "January"``.
+ """
+ return FunctionExpression("monthFormat", (month))
+
+ @classmethod
+ def monthAbbrevFormat(cls, month: IntoExpression, /) -> Expression:
+ """
+ Formats a (zero-based) ``month`` number as an abbreviated month name, according to the current locale.
+
+ For example: ``alt.expr.monthAbbrevFormat(0) -> "Jan"``.
+ """
+ return FunctionExpression("monthAbbrevFormat", (month))
+
+ @classmethod
+ def timeUnitSpecifier(
+ cls, units: IntoExpression, specifiers: IntoExpression = None, /
+ ) -> Expression:
+ """
+ Returns a time format specifier string for the given time `*units*`_.
+
+ The optional ``specifiers`` object provides a set of specifier sub-strings for customizing
+ the format; for more, see the `timeUnitSpecifier API documentation`_. The resulting
+ specifier string can then be used as input to the `timeFormat`_ or `utcFormat`_ functions,
+ or as the *format* parameter of an axis or legend. For example: ``alt.expr.timeFormat(date,
+ alt.expr.timeUnitSpecifier('year'))`` or ``alt.expr.timeFormat(date,
+ alt.expr.timeUnitSpecifier(['hours', 'minutes']))``.
+
+ .. _*units*:
+ https://vega.github.io/vega/docs/api/time/#time-units
+ .. _timeUnitSpecifier API documentation:
+ https://vega.github.io/vega/docs/api/time/#timeUnitSpecifier
+ .. _timeFormat:
+ https://vega.github.io/vega/docs/expressions/#timeFormat
+ .. _utcFormat:
+ https://vega.github.io/vega/docs/expressions/#utcFormat
+ """
+ return FunctionExpression("timeUnitSpecifier", (units, specifiers))
+
+ @classmethod
+ def timeFormat(
+ cls, value: IntoExpression, specifier: IntoExpression, /
+ ) -> Expression:
+ """
+ Formats a datetime ``value`` (either a ``Date`` object or timestamp) as a string, according to the local time.
+
+ The ``specifier`` must be a valid `d3-time-format specifier`_ or `TimeMultiFormat object`_.
+ For example: ``alt.expr.timeFormat(timestamp, '%A')``. Null values are formatted as
+ ``"null"``.
+
+ .. _d3-time-format specifier:
+ https://github.com/d3/d3-time-format/
+ .. _TimeMultiFormat object:
+ https://vega.github.io/vega/docs/types/#TimeMultiFormat
+ """
+ return FunctionExpression("timeFormat", (value, specifier))
+
+ @classmethod
+ def timeParse(
+ cls, string: IntoExpression, specifier: IntoExpression, /
+ ) -> Expression:
+ """
+ Parses a ``string`` value to a Date object, according to the local time.
+
+ The ``specifier`` must be a valid `d3-time-format specifier`_. For example:
+ ``alt.expr.timeParse('June 30, 2015', '%B %d, %Y')``.
+
+ .. _d3-time-format specifier:
+ https://github.com/d3/d3-time-format/
+ """
+ return FunctionExpression("timeParse", (string, specifier))
+
+ @classmethod
+ def utcFormat(
+ cls, value: IntoExpression, specifier: IntoExpression, /
+ ) -> Expression:
+ """
+ Formats a datetime ``value`` (either a ``Date`` object or timestamp) as a string, according to `UTC`_ time.
+
+ The ``specifier`` must be a valid `d3-time-format specifier`_ or `TimeMultiFormat object`_.
+ For example: ``alt.expr.utcFormat(timestamp, '%A')``. Null values are formatted as
+ ``"null"``.
+
+ .. _UTC:
+ https://en.wikipedia.org/wiki/Coordinated_Universal_Time
+ .. _d3-time-format specifier:
+ https://github.com/d3/d3-time-format/
+ .. _TimeMultiFormat object:
+ https://vega.github.io/vega/docs/types/#TimeMultiFormat
+ """
+ return FunctionExpression("utcFormat", (value, specifier))
+
+ @classmethod
+ def utcParse(
+ cls, value: IntoExpression, specifier: IntoExpression, /
+ ) -> Expression:
+ """
+ Parses a *string* value to a Date object, according to `UTC`_ time.
+
+ The ``specifier`` must be a valid `d3-time-format specifier`_. For example:
+ ``alt.expr.utcParse('June 30, 2015', '%B %d, %Y')``.
+
+ .. _UTC:
+ https://en.wikipedia.org/wiki/Coordinated_Universal_Time
+ .. _d3-time-format specifier:
+ https://github.com/d3/d3-time-format/
+ """
+ return FunctionExpression("utcParse", (value, specifier))
+
+ @classmethod
+ def regexp(
+ cls, pattern: IntoExpression, flags: IntoExpression = None, /
+ ) -> Expression:
+ """
+ Creates a regular expression instance from an input ``pattern`` string and optional ``flags``.
+
+ Same as `JavaScript's RegExp`_.
+
+ .. _JavaScript's RegExp:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
+ """
+ return FunctionExpression("regexp", (pattern, flags))
+
+ @classmethod
+ def test(
+ cls, regexp: IntoExpression, string: IntoExpression = None, /
+ ) -> Expression:
+ r"""
+ Evaluates a regular expression ``regexp`` against the input ``string``, returning ``true`` if the string matches the pattern, ``false`` otherwise.
+
+ For example: ``alt.expr.test(/\\d{3}/, "32-21-9483") -> true``.
+ """
+ return FunctionExpression("test", (regexp, string))
+
+ @classmethod
+ def rgb(cls, *args: Any) -> Expression:
+ """
+ Constructs a new `RGB`_ color.
+
+ If ``r``, ``g`` and ``b`` are specified, these represent the channel values of the returned
+ color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+ string is specified, it is parsed and then converted to the RGB color space. Uses
+ `d3-color's rgb function`_.
+
+ .. _RGB:
+ https://en.wikipedia.org/wiki/RGB_color_model
+ .. _d3-color's rgb function:
+ https://github.com/d3/d3-color#rgb
+ """
+ return FunctionExpression("rgb", args)
+
+ @classmethod
+ def hsl(cls, *args: Any) -> Expression:
+ """
+ Constructs a new `HSL`_ color.
+
+ If ``h``, ``s`` and ``l`` are specified, these represent the channel values of the returned
+ color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+ string is specified, it is parsed and then converted to the HSL color space. Uses
+ `d3-color's hsl function`_.
+
+ .. _HSL:
+ https://en.wikipedia.org/wiki/HSL_and_HSV
+ .. _d3-color's hsl function:
+ https://github.com/d3/d3-color#hsl
+ """
+ return FunctionExpression("hsl", args)
+
+ @classmethod
+ def lab(cls, *args: Any) -> Expression:
+ """
+ Constructs a new `CIE LAB`_ color.
+
+ If ``l``, ``a`` and ``b`` are specified, these represent the channel values of the returned
+ color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+ string is specified, it is parsed and then converted to the LAB color space. Uses
+ `d3-color's lab function`_.
+
+ .. _CIE LAB:
+ https://en.wikipedia.org/wiki/Lab_color_space#CIELAB
+ .. _d3-color's lab function:
+ https://github.com/d3/d3-color#lab
+ """
+ return FunctionExpression("lab", args)
+
+ @classmethod
+ def hcl(cls, *args: Any) -> Expression:
+ """
+ Constructs a new `HCL`_ (hue, chroma, luminance) color.
+
+ If ``h``, ``c`` and ``l`` are specified, these represent the channel values of the returned
+ color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+ string is specified, it is parsed and then converted to the HCL color space. Uses
+ `d3-color's hcl function`_.
+
+ .. _HCL:
+ https://en.wikipedia.org/wiki/Lab_color_space#CIELAB
+ .. _d3-color's hcl function:
+ https://github.com/d3/d3-color#hcl
+ """
+ return FunctionExpression("hcl", args)
+
+ @classmethod
+ def group(cls, name: IntoExpression = None, /) -> Expression:
+ """
+ Returns the scenegraph group mark item in which the current event has occurred.
+
+ If no arguments are provided, the immediate parent group is returned. If a group name is
+ provided, the matching ancestor group item is returned.
+ """
+ return FunctionExpression("group", (name))
+
+ @classmethod
+ def xy(cls, item: IntoExpression = None, /) -> Expression:
+ """
+ Returns the x- and y-coordinates for the current event as a two-element array.
+
+ If no arguments are provided, the top-level coordinate space of the view is used. If a
+ scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
+ item is used.
+ """
+ return FunctionExpression("xy", (item))
+
+ @classmethod
+ def x(cls, item: IntoExpression = None, /) -> Expression:
+ """
+ Returns the x coordinate for the current event.
+
+ If no arguments are provided, the top-level coordinate space of the view is used. If a
+ scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
+ item is used.
+ """
+ return FunctionExpression("x", (item))
+
+ @classmethod
+ def y(cls, item: IntoExpression = None, /) -> Expression:
+ """
+ Returns the y coordinate for the current event.
+
+ If no arguments are provided, the top-level coordinate space of the view is used. If a
+ scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
+ item is used.
+ """
+ return FunctionExpression("y", (item))
+
+ @classmethod
+ def pinchDistance(cls, event: IntoExpression, /) -> Expression:
+ """Returns the pixel distance between the first two touch points of a multi-touch event."""
+ return FunctionExpression("pinchDistance", (event))
+
+ @classmethod
+ def pinchAngle(cls, event: IntoExpression, /) -> Expression:
+ """Returns the angle of the line connecting the first two touch points of a multi-touch event."""
+ return FunctionExpression("pinchAngle", (event))
+
+ @classmethod
+ def inScope(cls, item: IntoExpression, /) -> Expression:
+ """Returns true if the given scenegraph ``item`` is a descendant of the group mark in which the event handler was defined, false otherwise."""
+ return FunctionExpression("inScope", (item))
+
+ @classmethod
+ def data(cls, name: IntoExpression, /) -> Expression:
+ """
+ Returns the array of data objects for the Vega data set with the given ``name``.
+
+ If the data set is not found, returns an empty array.
+ """
+ return FunctionExpression("data", (name))
+
+ @classmethod
+ def indata(
+ cls, name: IntoExpression, field: IntoExpression, value: IntoExpression, /
+ ) -> Expression:
+ """
+ Tests if the data set with a given ``name`` contains a datum with a ``field`` value that matches the input ``value``.
+
+ For example: ``alt.expr.indata('table', 'category', value)``.
+ """
+ return FunctionExpression("indata", (name, field, value))
+
+ @classmethod
+ def scale(
+ cls,
+ name: IntoExpression,
+ value: IntoExpression,
+ group: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Applies the named scale transform (or projection) to the specified ``value``.
+
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale or projection.
+ """
+ return FunctionExpression("scale", (name, value, group))
+
+ @classmethod
+ def invert(
+ cls,
+ name: IntoExpression,
+ value: IntoExpression,
+ group: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Inverts the named scale transform (or projection) for the specified ``value``.
+
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale or projection.
+ """
+ return FunctionExpression("invert", (name, value, group))
+
+ @classmethod
+ def copy(cls, name: IntoExpression, group: IntoExpression = None, /) -> Expression: # type: ignore[override]
+ """
+ Returns a copy (a new cloned instance) of the named scale transform of projection, or ``undefined`` if no scale or projection is found.
+
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale or projection.
+ """
+ return FunctionExpression("copy", (name, group))
+
+ @classmethod
+ def domain(
+ cls, name: IntoExpression, group: IntoExpression = None, /
+ ) -> Expression:
+ """
+ Returns the scale domain array for the named scale transform, or an empty array if the scale is not found.
+
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale.
+ """
+ return FunctionExpression("domain", (name, group))
+
+ @classmethod
+ def range(cls, name: IntoExpression, group: IntoExpression = None, /) -> Expression:
+ """
+ Returns the scale range array for the named scale transform, or an empty array if the scale is not found.
+
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale.
+ """
+ return FunctionExpression("range", (name, group))
+
+ @classmethod
+ def bandwidth(
+ cls, name: IntoExpression, group: IntoExpression = None, /
+ ) -> Expression:
+ """
+ Returns the current band width for the named band scale transform, or zero if the scale is not found or is not a band scale.
+
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale.
+ """
+ return FunctionExpression("bandwidth", (name, group))
+
+ @classmethod
+ def bandspace(
+ cls,
+ count: IntoExpression,
+ paddingInner: IntoExpression = None,
+ paddingOuter: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the number of steps needed within a band scale, based on the ``count`` of domain elements and the inner and outer padding values.
+
+ While normally calculated within the scale itself, this function can be helpful for
+ determining the size of a chart's layout.
+ """
+ return FunctionExpression("bandspace", (count, paddingInner, paddingOuter))
+
+ @classmethod
+ def gradient(
+ cls,
+ scale: IntoExpression,
+ p0: IntoExpression,
+ p1: IntoExpression,
+ count: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns a linear color gradient for the ``scale`` (whose range must be a `continuous color scheme`_) and starting and ending points ``p0`` and ``p1``, each an *[x, y]* array.
+
+ The points ``p0`` and ``p1`` should be expressed in normalized coordinates in the domain [0,
+ 1], relative to the bounds of the item being colored. If unspecified, ``p0`` defaults to
+ ``[0, 0]`` and ``p1`` defaults to ``[1, 0]``, for a horizontal gradient that spans the full
+ bounds of an item. The optional ``count`` argument indicates a desired target number of
+ sample points to take from the color scale.
+
+ .. _continuous color scheme:
+ https://vega.github.io/vega/docs/schemes
+ """
+ return FunctionExpression("gradient", (scale, p0, p1, count))
+
+ @classmethod
+ def panLinear(cls, domain: IntoExpression, delta: IntoExpression, /) -> Expression:
+ """
+ Given a linear scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
+
+ The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+ indicates panning the scale domain to the right by half the scale range.
+ """
+ return FunctionExpression("panLinear", (domain, delta))
+
+ @classmethod
+ def panLog(cls, domain: IntoExpression, delta: IntoExpression, /) -> Expression:
+ """
+ Given a log scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
+
+ The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+ indicates panning the scale domain to the right by half the scale range.
+ """
+ return FunctionExpression("panLog", (domain, delta))
+
+ @classmethod
+ def panPow(
+ cls, domain: IntoExpression, delta: IntoExpression, exponent: IntoExpression, /
+ ) -> Expression:
+ """
+ Given a power scale ``domain`` array with numeric or datetime values and the given ``exponent``, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
+
+ The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+ indicates panning the scale domain to the right by half the scale range.
+ """
+ return FunctionExpression("panPow", (domain, delta, exponent))
+
+ @classmethod
+ def panSymlog(
+ cls, domain: IntoExpression, delta: IntoExpression, constant: IntoExpression, /
+ ) -> Expression:
+ """
+ Given a symmetric log scale ``domain`` array with numeric or datetime values parameterized by the given ``constant``, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
+
+ The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+ indicates panning the scale domain to the right by half the scale range.
+ """
+ return FunctionExpression("panSymlog", (domain, delta, constant))
+
+ @classmethod
+ def zoomLinear(
+ cls,
+ domain: IntoExpression,
+ anchor: IntoExpression,
+ scaleFactor: IntoExpression,
+ /,
+ ) -> Expression:
+ """
+ Given a linear scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
+
+ The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+ range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
+ """
+ return FunctionExpression("zoomLinear", (domain, anchor, scaleFactor))
+
+ @classmethod
+ def zoomLog(
+ cls,
+ domain: IntoExpression,
+ anchor: IntoExpression,
+ scaleFactor: IntoExpression,
+ /,
+ ) -> Expression:
+ """
+ Given a log scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
+
+ The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+ range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
+ """
+ return FunctionExpression("zoomLog", (domain, anchor, scaleFactor))
+
+ @classmethod
+ def zoomPow(
+ cls,
+ domain: IntoExpression,
+ anchor: IntoExpression,
+ scaleFactor: IntoExpression,
+ exponent: IntoExpression,
+ /,
+ ) -> Expression:
+ """
+ Given a power scale ``domain`` array with numeric or datetime values and the given ``exponent``, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
+
+ The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+ range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
+ """
+ return FunctionExpression("zoomPow", (domain, anchor, scaleFactor, exponent))
+
+ @classmethod
+ def zoomSymlog(
+ cls,
+ domain: IntoExpression,
+ anchor: IntoExpression,
+ scaleFactor: IntoExpression,
+ constant: IntoExpression,
+ /,
+ ) -> Expression:
+ """
+ Given a symmetric log scale ``domain`` array with numeric or datetime values parameterized by the given ``constant``, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
+
+ The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+ range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
+ """
+ return FunctionExpression("zoomSymlog", (domain, anchor, scaleFactor, constant))
+
+ @classmethod
+ def geoArea(
+ cls,
+ projection: IntoExpression,
+ feature: IntoExpression,
+ group: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the projected planar area (typically in square pixels) of a GeoJSON ``feature`` according to the named ``projection``.
+
+ If the ``projection`` argument is ``null``, computes the spherical area in steradians using
+ unprojected longitude, latitude coordinates. The optional ``group`` argument takes a
+ scenegraph group mark item to indicate the specific scope in which to look up the
+ projection. Uses d3-geo's `geoArea`_ and `path.area`_ methods.
+
+ .. _geoArea:
+ https://github.com/d3/d3-geo#geoArea
+ .. _path.area:
+ https://github.com/d3/d3-geo#path_area
+ """
+ return FunctionExpression("geoArea", (projection, feature, group))
+
+ @classmethod
+ def geoBounds(
+ cls,
+ projection: IntoExpression,
+ feature: IntoExpression,
+ group: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the projected planar bounding box (typically in pixels) for the specified GeoJSON ``feature``, according to the named ``projection``.
+
+ The bounding box is represented by a two-dimensional array: [[*x₀*, *y₀*], [*x₁*, *y₁*]],
+ where *x₀* is the minimum x-coordinate, *y₀* is the minimum y-coordinate, *x₁* is the
+ maximum x-coordinate, and *y₁* is the maximum y-coordinate. If the ``projection`` argument
+ is ``null``, computes the spherical bounding box using unprojected longitude, latitude
+ coordinates. The optional ``group`` argument takes a scenegraph group mark item to indicate
+ the specific scope in which to look up the projection. Uses d3-geo's `geoBounds`_ and
+ `path.bounds`_ methods.
+
+ .. _geoBounds:
+ https://github.com/d3/d3-geo#geoBounds
+ .. _path.bounds:
+ https://github.com/d3/d3-geo#path_bounds
+ """
+ return FunctionExpression("geoBounds", (projection, feature, group))
+
+ @classmethod
+ def geoCentroid(
+ cls,
+ projection: IntoExpression,
+ feature: IntoExpression,
+ group: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the projected planar centroid (typically in pixels) for the specified GeoJSON ``feature``, according to the named ``projection``.
+
+ If the ``projection`` argument is ``null``, computes the spherical centroid using
+ unprojected longitude, latitude coordinates. The optional ``group`` argument takes a
+ scenegraph group mark item to indicate the specific scope in which to look up the
+ projection. Uses d3-geo's `geoCentroid`_ and `path.centroid`_ methods.
+
+ .. _geoCentroid:
+ https://github.com/d3/d3-geo#geoCentroid
+ .. _path.centroid:
+ https://github.com/d3/d3-geo#path_centroid
+ """
+ return FunctionExpression("geoCentroid", (projection, feature, group))
+
+ @classmethod
+ def geoScale(
+ cls, projection: IntoExpression, group: IntoExpression = None, /
+ ) -> Expression:
+ """
+ Returns the scale value for the named ``projection``.
+
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the projection.
+ """
+ return FunctionExpression("geoScale", (projection, group))
+
+ @classmethod
+ def treePath(
+ cls, name: IntoExpression, source: IntoExpression, target: IntoExpression, /
+ ) -> Expression:
+ """
+ For the hierarchy data set with the given ``name``, returns the shortest path through from the ``source`` node id to the ``target`` node id.
+
+ The path starts at the ``source`` node, ascends to the least common ancestor of the
+ ``source`` node and the ``target`` node, and then descends to the ``target`` node.
+ """
+ return FunctionExpression("treePath", (name, source, target))
+
+ @classmethod
+ def treeAncestors(cls, name: IntoExpression, node: IntoExpression, /) -> Expression:
+ """For the hierarchy data set with the given ``name``, returns the array of ancestors nodes, starting with the input ``node``, then followed by each parent up to the root."""
+ return FunctionExpression("treeAncestors", (name, node))
+
+ @classmethod
+ def warn(
+ cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
+ ) -> Expression:
+ """
+ Logs a warning message and returns the last argument.
+
+ For the message to appear in the console, the visualization view must have the appropriate
+ logging level set.
+ """
+ return FunctionExpression("warn", (value1, value2, *args))
+
+ @classmethod
+ def info(
+ cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
+ ) -> Expression:
+ """
+ Logs an informative message and returns the last argument.
+
+ For the message to appear in the console, the visualization view must have the appropriate
+ logging level set.
+ """
+ return FunctionExpression("info", (value1, value2, *args))
+
+ @classmethod
+ def debug(
+ cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
+ ) -> Expression:
+ """
+ Logs a debugging message and returns the last argument.
+
+ For the message to appear in the console, the visualization view must have the appropriate
+ logging level set.
+ """
+ return FunctionExpression("debug", (value1, value2, *args))
+
+
+_ExprType = expr
+# NOTE: Compatibility alias for previous type of `alt.expr`.
+# `_ExprType` was not referenced in any internal imports/tests.
From 222d03e1462513f4898933d209d4c5b62532875c Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 18:10:10 +0100
Subject: [PATCH 42/77] chore: Add note to `test_expr.py`
Need to make a change here to start a GH thread
---
tests/expr/test_expr.py | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py
index 8842e0ced..8aa97f30a 100644
--- a/tests/expr/test_expr.py
+++ b/tests/expr/test_expr.py
@@ -88,7 +88,14 @@ def test_abs():
@pytest.mark.parametrize(("veganame", "methodname"), _remap_classmethod_names(expr))
def test_expr_funcs(veganame: str, methodname: str):
- """Test all functions defined in expr.funcs."""
+ """
+ Test all functions defined in expr.funcs.
+
+ # FIXME: These tests are no longer suitable
+ They only work for functions with a **single** argument:
+
+ TypeError: expr.if_() missing 2 required positional arguments: 'thenValue' and 'elseValue'.
+ """
func = getattr(expr, methodname)
z = func(datum.xxx)
assert repr(z) == f"{veganame}(datum.xxx)"
From f7a47a54af99df1da596b806eeb054a6e791e1bf Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 21:17:06 +0100
Subject: [PATCH 43/77] fix: Add trailing comma to single arg methods
---
altair/expr/dummy.py | 150 ++++++++++++++++++------------------
tools/schemapi/vega_expr.py | 6 +-
2 files changed, 80 insertions(+), 76 deletions(-)
diff --git a/altair/expr/dummy.py b/altair/expr/dummy.py
index 78b72f96e..bc7e743a6 100644
--- a/altair/expr/dummy.py
+++ b/altair/expr/dummy.py
@@ -121,12 +121,12 @@ def __new__(cls: type[_ExprRef], expr: str) -> _ExprRef: # type: ignore[misc]
@classmethod
def isArray(cls, value: IntoExpression, /) -> Expression:
"""Returns true if ``value`` is an array, false otherwise."""
- return FunctionExpression("isArray", (value))
+ return FunctionExpression("isArray", (value,))
@classmethod
def isBoolean(cls, value: IntoExpression, /) -> Expression:
"""Returns true if ``value`` is a boolean (``true`` or ``false``), false otherwise."""
- return FunctionExpression("isBoolean", (value))
+ return FunctionExpression("isBoolean", (value,))
@classmethod
def isDate(cls, value: IntoExpression, /) -> Expression:
@@ -136,7 +136,7 @@ def isDate(cls, value: IntoExpression, /) -> Expression:
This method will return false for timestamp numbers or date-formatted strings; it recognizes
Date objects only.
"""
- return FunctionExpression("isDate", (value))
+ return FunctionExpression("isDate", (value,))
@classmethod
def isDefined(cls, value: IntoExpression, /) -> Expression:
@@ -145,7 +145,7 @@ def isDefined(cls, value: IntoExpression, /) -> Expression:
This method will return true for ``null`` and ``NaN`` values.
"""
- return FunctionExpression("isDefined", (value))
+ return FunctionExpression("isDefined", (value,))
@classmethod
def isNumber(cls, value: IntoExpression, /) -> Expression:
@@ -154,27 +154,27 @@ def isNumber(cls, value: IntoExpression, /) -> Expression:
``NaN`` and ``Infinity`` are considered numbers.
"""
- return FunctionExpression("isNumber", (value))
+ return FunctionExpression("isNumber", (value,))
@classmethod
def isObject(cls, value: IntoExpression, /) -> Expression:
"""Returns true if ``value`` is an object (including arrays and Dates), false otherwise."""
- return FunctionExpression("isObject", (value))
+ return FunctionExpression("isObject", (value,))
@classmethod
def isRegExp(cls, value: IntoExpression, /) -> Expression:
"""Returns true if ``value`` is a RegExp (regular expression) object, false otherwise."""
- return FunctionExpression("isRegExp", (value))
+ return FunctionExpression("isRegExp", (value,))
@classmethod
def isString(cls, value: IntoExpression, /) -> Expression:
"""Returns true if ``value`` is a string, false otherwise."""
- return FunctionExpression("isString", (value))
+ return FunctionExpression("isString", (value,))
@classmethod
def isValid(cls, value: IntoExpression, /) -> Expression:
"""Returns true if ``value`` is not ``null``, ``undefined``, or ``NaN``, false otherwise."""
- return FunctionExpression("isValid", (value))
+ return FunctionExpression("isValid", (value,))
@classmethod
def toBoolean(cls, value: IntoExpression, /) -> Expression:
@@ -183,7 +183,7 @@ def toBoolean(cls, value: IntoExpression, /) -> Expression:
Null values and empty strings are mapped to ``null``.
"""
- return FunctionExpression("toBoolean", (value))
+ return FunctionExpression("toBoolean", (value,))
@classmethod
def toDate(cls, value: IntoExpression, /) -> Expression:
@@ -194,7 +194,7 @@ def toDate(cls, value: IntoExpression, /) -> Expression:
provided, it is used to perform date parsing, otherwise ``Date.parse`` is used. Be aware
that ``Date.parse`` has different implementations across browsers!
"""
- return FunctionExpression("toDate", (value))
+ return FunctionExpression("toDate", (value,))
@classmethod
def toNumber(cls, value: IntoExpression, /) -> Expression:
@@ -203,7 +203,7 @@ def toNumber(cls, value: IntoExpression, /) -> Expression:
Null values and empty strings are mapped to ``null``.
"""
- return FunctionExpression("toNumber", (value))
+ return FunctionExpression("toNumber", (value,))
@classmethod
def toString(cls, value: IntoExpression, /) -> Expression:
@@ -212,7 +212,7 @@ def toString(cls, value: IntoExpression, /) -> Expression:
Null values and empty strings are mapped to ``null``.
"""
- return FunctionExpression("toString", (value))
+ return FunctionExpression("toString", (value,))
@classmethod
def if_(
@@ -240,7 +240,7 @@ def isNaN(cls, value: IntoExpression, /) -> Expression:
.. _Number.isNaN:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNan
"""
- return FunctionExpression("isNaN", (value))
+ return FunctionExpression("isNaN", (value,))
@classmethod
def isFinite(cls, value: IntoExpression, /) -> Expression:
@@ -252,7 +252,7 @@ def isFinite(cls, value: IntoExpression, /) -> Expression:
.. _Number.isFinite:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite
"""
- return FunctionExpression("isFinite", (value))
+ return FunctionExpression("isFinite", (value,))
@classmethod
def abs(cls, value: IntoExpression, /) -> Expression:
@@ -264,7 +264,7 @@ def abs(cls, value: IntoExpression, /) -> Expression:
.. _Math.abs:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs
"""
- return FunctionExpression("abs", (value))
+ return FunctionExpression("abs", (value,))
@classmethod
def acos(cls, value: IntoExpression, /) -> Expression:
@@ -276,7 +276,7 @@ def acos(cls, value: IntoExpression, /) -> Expression:
.. _Math.acos:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos
"""
- return FunctionExpression("acos", (value))
+ return FunctionExpression("acos", (value,))
@classmethod
def asin(cls, value: IntoExpression, /) -> Expression:
@@ -288,7 +288,7 @@ def asin(cls, value: IntoExpression, /) -> Expression:
.. _Math.asin:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin
"""
- return FunctionExpression("asin", (value))
+ return FunctionExpression("asin", (value,))
@classmethod
def atan(cls, value: IntoExpression, /) -> Expression:
@@ -300,7 +300,7 @@ def atan(cls, value: IntoExpression, /) -> Expression:
.. _Math.atan:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan
"""
- return FunctionExpression("atan", (value))
+ return FunctionExpression("atan", (value,))
@classmethod
def atan2(cls, dy: IntoExpression, dx: IntoExpression, /) -> Expression:
@@ -324,7 +324,7 @@ def ceil(cls, value: IntoExpression, /) -> Expression:
.. _Math.ceil:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil
"""
- return FunctionExpression("ceil", (value))
+ return FunctionExpression("ceil", (value,))
@classmethod
def clamp(
@@ -343,7 +343,7 @@ def cos(cls, value: IntoExpression, /) -> Expression:
.. _Math.cos:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos
"""
- return FunctionExpression("cos", (value))
+ return FunctionExpression("cos", (value,))
@classmethod
def exp(cls, exponent: IntoExpression, /) -> Expression:
@@ -355,7 +355,7 @@ def exp(cls, exponent: IntoExpression, /) -> Expression:
.. _Math.exp:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp
"""
- return FunctionExpression("exp", (exponent))
+ return FunctionExpression("exp", (exponent,))
@classmethod
def floor(cls, value: IntoExpression, /) -> Expression:
@@ -367,7 +367,7 @@ def floor(cls, value: IntoExpression, /) -> Expression:
.. _Math.floor:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor
"""
- return FunctionExpression("floor", (value))
+ return FunctionExpression("floor", (value,))
@classmethod
def hypot(cls, value: IntoExpression, /) -> Expression:
@@ -379,7 +379,7 @@ def hypot(cls, value: IntoExpression, /) -> Expression:
.. _Math.hypot:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot
"""
- return FunctionExpression("hypot", (value))
+ return FunctionExpression("hypot", (value,))
@classmethod
def log(cls, value: IntoExpression, /) -> Expression:
@@ -391,7 +391,7 @@ def log(cls, value: IntoExpression, /) -> Expression:
.. _Math.log:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log
"""
- return FunctionExpression("log", (value))
+ return FunctionExpression("log", (value,))
@classmethod
def max(
@@ -443,7 +443,7 @@ def round(cls, value: IntoExpression, /) -> Expression:
.. _Math.round:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
"""
- return FunctionExpression("round", (value))
+ return FunctionExpression("round", (value,))
@classmethod
def sin(cls, value: IntoExpression, /) -> Expression:
@@ -455,7 +455,7 @@ def sin(cls, value: IntoExpression, /) -> Expression:
.. _Math.sin:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin
"""
- return FunctionExpression("sin", (value))
+ return FunctionExpression("sin", (value,))
@classmethod
def sqrt(cls, value: IntoExpression, /) -> Expression:
@@ -467,7 +467,7 @@ def sqrt(cls, value: IntoExpression, /) -> Expression:
.. _Math.sqrt:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt
"""
- return FunctionExpression("sqrt", (value))
+ return FunctionExpression("sqrt", (value,))
@classmethod
def tan(cls, value: IntoExpression, /) -> Expression:
@@ -479,7 +479,7 @@ def tan(cls, value: IntoExpression, /) -> Expression:
.. _Math.tan:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan
"""
- return FunctionExpression("tan", (value))
+ return FunctionExpression("tan", (value,))
@classmethod
def sampleNormal(
@@ -549,32 +549,32 @@ def datetime(
@classmethod
def date(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the day of the month for the given ``datetime`` value, in local time."""
- return FunctionExpression("date", (datetime))
+ return FunctionExpression("date", (datetime,))
@classmethod
def day(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the day of the week for the given ``datetime`` value, in local time."""
- return FunctionExpression("day", (datetime))
+ return FunctionExpression("day", (datetime,))
@classmethod
def dayofyear(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the one-based day of the year for the given ``datetime`` value, in local time."""
- return FunctionExpression("dayofyear", (datetime))
+ return FunctionExpression("dayofyear", (datetime,))
@classmethod
def year(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the year for the given ``datetime`` value, in local time."""
- return FunctionExpression("year", (datetime))
+ return FunctionExpression("year", (datetime,))
@classmethod
def quarter(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the quarter of the year (0-3) for the given ``datetime`` value, in local time."""
- return FunctionExpression("quarter", (datetime))
+ return FunctionExpression("quarter", (datetime,))
@classmethod
def month(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the (zero-based) month for the given ``datetime`` value, in local time."""
- return FunctionExpression("month", (datetime))
+ return FunctionExpression("month", (datetime,))
@classmethod
def week(cls, date: IntoExpression, /) -> Expression:
@@ -585,37 +585,37 @@ def week(cls, date: IntoExpression, /) -> Expression:
considered to be in week 0, the first Sunday of the year is the start of week 1, the second
Sunday week 2, *etc.*.
"""
- return FunctionExpression("week", (date))
+ return FunctionExpression("week", (date,))
@classmethod
def hours(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the hours component for the given ``datetime`` value, in local time."""
- return FunctionExpression("hours", (datetime))
+ return FunctionExpression("hours", (datetime,))
@classmethod
def minutes(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the minutes component for the given ``datetime`` value, in local time."""
- return FunctionExpression("minutes", (datetime))
+ return FunctionExpression("minutes", (datetime,))
@classmethod
def seconds(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the seconds component for the given ``datetime`` value, in local time."""
- return FunctionExpression("seconds", (datetime))
+ return FunctionExpression("seconds", (datetime,))
@classmethod
def milliseconds(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the milliseconds component for the given ``datetime`` value, in local time."""
- return FunctionExpression("milliseconds", (datetime))
+ return FunctionExpression("milliseconds", (datetime,))
@classmethod
def time(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the epoch-based timestamp for the given ``datetime`` value."""
- return FunctionExpression("time", (datetime))
+ return FunctionExpression("time", (datetime,))
@classmethod
def timezoneoffset(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the timezone offset from the local timezone to UTC for the given ``datetime`` value."""
- return FunctionExpression("timezoneoffset", (datetime))
+ return FunctionExpression("timezoneoffset", (datetime,))
@classmethod
def timeOffset(
@@ -674,32 +674,32 @@ def utc(
@classmethod
def utcdate(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the day of the month for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcdate", (datetime))
+ return FunctionExpression("utcdate", (datetime,))
@classmethod
def utcday(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the day of the week for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcday", (datetime))
+ return FunctionExpression("utcday", (datetime,))
@classmethod
def utcdayofyear(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the one-based day of the year for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcdayofyear", (datetime))
+ return FunctionExpression("utcdayofyear", (datetime,))
@classmethod
def utcyear(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the year for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcyear", (datetime))
+ return FunctionExpression("utcyear", (datetime,))
@classmethod
def utcquarter(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the quarter of the year (0-3) for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcquarter", (datetime))
+ return FunctionExpression("utcquarter", (datetime,))
@classmethod
def utcmonth(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the (zero-based) month for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcmonth", (datetime))
+ return FunctionExpression("utcmonth", (datetime,))
@classmethod
def utcweek(cls, date: IntoExpression, /) -> Expression:
@@ -710,27 +710,27 @@ def utcweek(cls, date: IntoExpression, /) -> Expression:
considered to be in week 0, the first Sunday of the year is the start of week 1, the second
Sunday week 2, *etc.*.
"""
- return FunctionExpression("utcweek", (date))
+ return FunctionExpression("utcweek", (date,))
@classmethod
def utchours(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the hours component for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utchours", (datetime))
+ return FunctionExpression("utchours", (datetime,))
@classmethod
def utcminutes(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the minutes component for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcminutes", (datetime))
+ return FunctionExpression("utcminutes", (datetime,))
@classmethod
def utcseconds(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the seconds component for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcseconds", (datetime))
+ return FunctionExpression("utcseconds", (datetime,))
@classmethod
def utcmilliseconds(cls, datetime: IntoExpression, /) -> Expression:
"""Returns the milliseconds component for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcmilliseconds", (datetime))
+ return FunctionExpression("utcmilliseconds", (datetime,))
@classmethod
def utcOffset(
@@ -770,7 +770,7 @@ def utcSequence(
@classmethod
def extent(cls, array: IntoExpression, /) -> Expression:
"""Returns a new *[min, max]* array with the minimum and maximum values of the input array, ignoring ``null``, ``undefined``, and ``NaN`` values."""
- return FunctionExpression("extent", (array))
+ return FunctionExpression("extent", (array,))
@classmethod
def clampRange(
@@ -811,7 +811,7 @@ def lastindexof(cls, array: IntoExpression, value: IntoExpression, /) -> Express
@classmethod
def length(cls, array: IntoExpression, /) -> Expression:
"""Returns the length of the input ``array``."""
- return FunctionExpression("length", (array))
+ return FunctionExpression("length", (array,))
@classmethod
def lerp(cls, array: IntoExpression, fraction: IntoExpression, /) -> Expression:
@@ -830,7 +830,7 @@ def peek(cls, array: IntoExpression, /) -> Expression:
Similar to the built-in ``Array.pop`` method, except that it does not remove the last
element. This method is a convenient shorthand for ``array[array.length - 1]``.
"""
- return FunctionExpression("peek", (array))
+ return FunctionExpression("peek", (array,))
@classmethod
def pluck(cls, array: IntoExpression, field: IntoExpression, /) -> Expression:
@@ -848,7 +848,7 @@ def reverse(cls, array: IntoExpression, /) -> Expression:
The first array element becomes the last, and the last array element becomes the first.
"""
- return FunctionExpression("reverse", (array))
+ return FunctionExpression("reverse", (array,))
@classmethod
def sequence(cls, *args: Any) -> Expression:
@@ -879,12 +879,12 @@ def slice(
@classmethod
def span(cls, array: IntoExpression, /) -> Expression:
"""Returns the span of ``array``: the difference between the last and first elements, or *array[array.length-1] - array[0]*."""
- return FunctionExpression("span", (array))
+ return FunctionExpression("span", (array,))
@classmethod
def lower(cls, string: IntoExpression, /) -> Expression:
"""Transforms ``string`` to lower-case letters."""
- return FunctionExpression("lower", (string))
+ return FunctionExpression("lower", (string,))
@classmethod
def pad(
@@ -911,7 +911,7 @@ def parseFloat(cls, string: IntoExpression, /) -> Expression:
Same as JavaScript's ``parseFloat``.
"""
- return FunctionExpression("parseFloat", (string))
+ return FunctionExpression("parseFloat", (string,))
@classmethod
def parseInt(cls, string: IntoExpression, /) -> Expression:
@@ -920,7 +920,7 @@ def parseInt(cls, string: IntoExpression, /) -> Expression:
Same as JavaScript's ``parseInt``.
"""
- return FunctionExpression("parseInt", (string))
+ return FunctionExpression("parseInt", (string,))
@classmethod
def replace(
@@ -955,7 +955,7 @@ def substring(
@classmethod
def trim(cls, string: IntoExpression, /) -> Expression:
"""Returns a trimmed string with preceding and trailing whitespace removed."""
- return FunctionExpression("trim", (string))
+ return FunctionExpression("trim", (string,))
@classmethod
def truncate(
@@ -980,7 +980,7 @@ def truncate(
@classmethod
def upper(cls, string: IntoExpression, /) -> Expression:
"""Transforms ``string`` to upper-case letters."""
- return FunctionExpression("upper", (string))
+ return FunctionExpression("upper", (string,))
@classmethod
def merge(
@@ -1002,7 +1002,7 @@ def dayFormat(cls, day: IntoExpression, /) -> Expression:
For example: ``alt.expr.dayFormat(0) -> "Sunday"``.
"""
- return FunctionExpression("dayFormat", (day))
+ return FunctionExpression("dayFormat", (day,))
@classmethod
def dayAbbrevFormat(cls, day: IntoExpression, /) -> Expression:
@@ -1011,7 +1011,7 @@ def dayAbbrevFormat(cls, day: IntoExpression, /) -> Expression:
For example: ``alt.expr.dayAbbrevFormat(0) -> "Sun"``.
"""
- return FunctionExpression("dayAbbrevFormat", (day))
+ return FunctionExpression("dayAbbrevFormat", (day,))
@classmethod
def format(cls, value: IntoExpression, specifier: IntoExpression, /) -> Expression:
@@ -1033,7 +1033,7 @@ def monthFormat(cls, month: IntoExpression, /) -> Expression:
For example: ``alt.expr.monthFormat(0) -> "January"``.
"""
- return FunctionExpression("monthFormat", (month))
+ return FunctionExpression("monthFormat", (month,))
@classmethod
def monthAbbrevFormat(cls, month: IntoExpression, /) -> Expression:
@@ -1042,7 +1042,7 @@ def monthAbbrevFormat(cls, month: IntoExpression, /) -> Expression:
For example: ``alt.expr.monthAbbrevFormat(0) -> "Jan"``.
"""
- return FunctionExpression("monthAbbrevFormat", (month))
+ return FunctionExpression("monthAbbrevFormat", (month,))
@classmethod
def timeUnitSpecifier(
@@ -1240,7 +1240,7 @@ def group(cls, name: IntoExpression = None, /) -> Expression:
If no arguments are provided, the immediate parent group is returned. If a group name is
provided, the matching ancestor group item is returned.
"""
- return FunctionExpression("group", (name))
+ return FunctionExpression("group", (name,))
@classmethod
def xy(cls, item: IntoExpression = None, /) -> Expression:
@@ -1251,7 +1251,7 @@ def xy(cls, item: IntoExpression = None, /) -> Expression:
scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
item is used.
"""
- return FunctionExpression("xy", (item))
+ return FunctionExpression("xy", (item,))
@classmethod
def x(cls, item: IntoExpression = None, /) -> Expression:
@@ -1262,7 +1262,7 @@ def x(cls, item: IntoExpression = None, /) -> Expression:
scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
item is used.
"""
- return FunctionExpression("x", (item))
+ return FunctionExpression("x", (item,))
@classmethod
def y(cls, item: IntoExpression = None, /) -> Expression:
@@ -1273,22 +1273,22 @@ def y(cls, item: IntoExpression = None, /) -> Expression:
scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
item is used.
"""
- return FunctionExpression("y", (item))
+ return FunctionExpression("y", (item,))
@classmethod
def pinchDistance(cls, event: IntoExpression, /) -> Expression:
"""Returns the pixel distance between the first two touch points of a multi-touch event."""
- return FunctionExpression("pinchDistance", (event))
+ return FunctionExpression("pinchDistance", (event,))
@classmethod
def pinchAngle(cls, event: IntoExpression, /) -> Expression:
"""Returns the angle of the line connecting the first two touch points of a multi-touch event."""
- return FunctionExpression("pinchAngle", (event))
+ return FunctionExpression("pinchAngle", (event,))
@classmethod
def inScope(cls, item: IntoExpression, /) -> Expression:
"""Returns true if the given scenegraph ``item`` is a descendant of the group mark in which the event handler was defined, false otherwise."""
- return FunctionExpression("inScope", (item))
+ return FunctionExpression("inScope", (item,))
@classmethod
def data(cls, name: IntoExpression, /) -> Expression:
@@ -1297,7 +1297,7 @@ def data(cls, name: IntoExpression, /) -> Expression:
If the data set is not found, returns an empty array.
"""
- return FunctionExpression("data", (name))
+ return FunctionExpression("data", (name,))
@classmethod
def indata(
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 14ce9cfa7..8a723baed 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -922,7 +922,11 @@ def render_expr_method(node: VegaExprNode, /) -> WorkInProgress:
if node.is_overloaded():
body_params = STAR_ARGS[1:]
else:
- body_params = f"({', '.join(node.parameter_names())})"
+ body_params = ", ".join(node.parameter_names())
+ if "," not in body_params:
+ body_params = f"({body_params}, )"
+ else:
+ body_params = f"({body_params})"
body = f"return {RETURN_WRAPPER}({node.name!r}, {body_params})"
return EXPR_METHOD_TEMPLATE.format(
decorator=DECORATOR, signature=node.signature, doc=node.doc, body=body
From 9238fb635c37c3527851a3f59244ff71fe1571b2 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 24 Sep 2024 21:30:49 +0100
Subject: [PATCH 44/77] test: Adds `test_dummy_expr_funcs`
https://github.com/vega/altair/pull/3600#discussion_r1773774533
---
tests/expr/test_expr.py | 51 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 49 insertions(+), 2 deletions(-)
diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py
index 8aa97f30a..1304b1435 100644
--- a/tests/expr/test_expr.py
+++ b/tests/expr/test_expr.py
@@ -2,14 +2,23 @@
import operator
import sys
-from inspect import classify_class_attrs, getmembers
-from typing import Any, Iterator
+from inspect import classify_class_attrs, getmembers, signature
+from typing import TYPE_CHECKING, Any, Iterator, cast
import pytest
from jsonschema.exceptions import ValidationError
from altair import datum, expr, ExprRef
from altair.expr import _ConstExpressionType
+from altair.expr import dummy as dummy
+from altair.expr.core import GetAttrExpression
+
+if TYPE_CHECKING:
+ from inspect import Signature
+ from typing import Callable, Container
+
+ from altair.expr.core import Expression
+
# This maps vega expression function names to the Python name
VEGA_REMAP = {"if_": "if"}
@@ -35,6 +44,44 @@ def _get_property_names(tp: type[Any], /) -> Iterator[str]:
yield nm
+def signature_n_params(
+ sig: Signature, /, *, exclude: Container[str] = frozenset(("cls", "self"))
+) -> int:
+ return len([p for p in sig.parameters.values() if p.name not in exclude])
+
+
+def _get_classmethod_members(
+ tp: type[Any], /
+) -> Iterator[tuple[str, Callable[..., Any]]]:
+ for m in classify_class_attrs(tp):
+ if m.kind == "class method" and m.defining_class is tp:
+ yield m.name, cast("classmethod[Any, Any, Any]", m.object).__func__
+
+
+def _get_classmethod_signatures(
+ tp: type[Any], /
+) -> Iterator[tuple[str, Callable[..., Expression], int]]:
+ for name, fn in _get_classmethod_members(tp):
+ yield (
+ VEGA_REMAP.get(name, name),
+ fn.__get__(tp),
+ signature_n_params(signature(fn)),
+ )
+
+
+@pytest.mark.parametrize(
+ ("veganame", "fn", "n_params"), _get_classmethod_signatures(dummy.expr)
+)
+def test_dummy_expr_funcs(
+ veganame: str, fn: Callable[..., Expression], n_params: int
+) -> None:
+ datum_names = [f"col_{n}" for n in range(n_params)]
+ datum_args = ",".join(f"datum.{nm}" for nm in datum_names)
+
+ fn_call = fn(*(GetAttrExpression("datum", nm) for nm in datum_names))
+ assert repr(fn_call) == f"{veganame}({datum_args})"
+
+
def test_unary_operations():
OP_MAP = {"-": operator.neg, "+": operator.pos}
for op, func in OP_MAP.items():
From 7311fdf5bb7f5991fdaefe04aa1435919fbc37b8 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 09:58:26 +0100
Subject: [PATCH 45/77] test: Refactor `test_expr`
- Combined some single use functions
- `signature_n_params`
- Collect the signature here, since the function is prefixed with `signature_`
- Use a cheaper method of calculating number of target params
- Reordered, renamed, prepare `test_expr_methods` for the new `expr` type
https://github.com/vega/altair/pull/3600#discussion_r1773774533
---
tests/expr/test_expr.py | 63 ++++++++++++++++++-----------------------
1 file changed, 27 insertions(+), 36 deletions(-)
diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py
index 1304b1435..65935f739 100644
--- a/tests/expr/test_expr.py
+++ b/tests/expr/test_expr.py
@@ -3,7 +3,7 @@
import operator
import sys
from inspect import classify_class_attrs, getmembers, signature
-from typing import TYPE_CHECKING, Any, Iterator, cast
+from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, TypeVar, cast
import pytest
from jsonschema.exceptions import ValidationError
@@ -11,14 +11,12 @@
from altair import datum, expr, ExprRef
from altair.expr import _ConstExpressionType
from altair.expr import dummy as dummy
-from altair.expr.core import GetAttrExpression
+from altair.expr.core import Expression, GetAttrExpression
if TYPE_CHECKING:
- from inspect import Signature
- from typing import Callable, Container
-
- from altair.expr.core import Expression
+ from inspect import _IntrospectableCallable
+T = TypeVar("T")
# This maps vega expression function names to the Python name
VEGA_REMAP = {"if_": "if"}
@@ -45,41 +43,23 @@ def _get_property_names(tp: type[Any], /) -> Iterator[str]:
def signature_n_params(
- sig: Signature, /, *, exclude: Container[str] = frozenset(("cls", "self"))
+ obj: _IntrospectableCallable,
+ /,
+ *,
+ exclude: Iterable[str] = frozenset(("cls", "self")),
) -> int:
- return len([p for p in sig.parameters.values() if p.name not in exclude])
+ sig = signature(obj)
+ return len(set(sig.parameters).difference(exclude))
-def _get_classmethod_members(
- tp: type[Any], /
-) -> Iterator[tuple[str, Callable[..., Any]]]:
+def _iter_classmethod_specs(
+ tp: type[T], /
+) -> Iterator[tuple[str, Callable[..., Expression], int]]:
for m in classify_class_attrs(tp):
if m.kind == "class method" and m.defining_class is tp:
- yield m.name, cast("classmethod[Any, Any, Any]", m.object).__func__
-
-
-def _get_classmethod_signatures(
- tp: type[Any], /
-) -> Iterator[tuple[str, Callable[..., Expression], int]]:
- for name, fn in _get_classmethod_members(tp):
- yield (
- VEGA_REMAP.get(name, name),
- fn.__get__(tp),
- signature_n_params(signature(fn)),
- )
-
-
-@pytest.mark.parametrize(
- ("veganame", "fn", "n_params"), _get_classmethod_signatures(dummy.expr)
-)
-def test_dummy_expr_funcs(
- veganame: str, fn: Callable[..., Expression], n_params: int
-) -> None:
- datum_names = [f"col_{n}" for n in range(n_params)]
- datum_args = ",".join(f"datum.{nm}" for nm in datum_names)
-
- fn_call = fn(*(GetAttrExpression("datum", nm) for nm in datum_names))
- assert repr(fn_call) == f"{veganame}({datum_args})"
+ name = m.name
+ fn = cast("classmethod[T, ..., Expression]", m.object).__func__
+ yield (VEGA_REMAP.get(name, name), fn.__get__(tp), signature_n_params(fn))
def test_unary_operations():
@@ -133,6 +113,17 @@ def test_abs():
assert repr(z) == "abs(datum.xxx)"
+@pytest.mark.parametrize(("veganame", "fn", "n_params"), _iter_classmethod_specs(expr))
+def test_expr_methods(
+ veganame: str, fn: Callable[..., Expression], n_params: int
+) -> None:
+ datum_names = [f"col_{n}" for n in range(n_params)]
+ datum_args = ",".join(f"datum.{nm}" for nm in datum_names)
+
+ fn_call = fn(*(GetAttrExpression("datum", nm) for nm in datum_names))
+ assert repr(fn_call) == f"{veganame}({datum_args})"
+
+
@pytest.mark.parametrize(("veganame", "methodname"), _remap_classmethod_names(expr))
def test_expr_funcs(veganame: str, methodname: str):
"""
From 6077075b45c223b9d4b44072b5870b97859892af Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 11:13:20 +0100
Subject: [PATCH 46/77] fix: Move some imports to `TYPE_CHECKING` block
`ruff` was warning that `TYPE_CHECKING` was unused
---
tools/schemapi/vega_expr.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 8a723baed..d77ba2433 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -780,7 +780,7 @@ def parse_expressions(url: str, /) -> Iterator[VegaExprNode]:
import sys
from typing import Any, TYPE_CHECKING
-from altair.expr.core import {const}, {func}, {return_ann}, {input_ann}
+from altair.expr.core import {const}, {func}
from altair.vegalite.v5.schema.core import ExprRef as _ExprRef
if sys.version_info >= (3, 12):
@@ -788,6 +788,9 @@ def parse_expressions(url: str, /) -> Iterator[VegaExprNode]:
else:
from typing_extensions import override
+if TYPE_CHECKING:
+ from altair.expr.core import {return_ann}, {input_ann}
+
class {metaclass}(type):
"""Metaclass providing read-only class properties for :class:`expr`."""
From 0094596217fd07a18e310db706c5cb0b28f1ad59 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 11:15:38 +0100
Subject: [PATCH 47/77] chore: Delete `expr.dummy.py`
https://github.com/vega/altair/pull/3600#discussion_r1774050096
---
altair/expr/dummy.py | 1678 ---------------------------------------
tests/expr/test_expr.py | 1 -
2 files changed, 1679 deletions(-)
delete mode 100644 altair/expr/dummy.py
diff --git a/altair/expr/dummy.py b/altair/expr/dummy.py
deleted file mode 100644
index bc7e743a6..000000000
--- a/altair/expr/dummy.py
+++ /dev/null
@@ -1,1678 +0,0 @@
-"""Tools for creating transform & filter expressions with a python syntax."""
-
-from __future__ import annotations
-
-import sys
-from typing import Any
-
-from altair.expr.core import (
- ConstExpression,
- Expression,
- FunctionExpression,
- IntoExpression,
-)
-from altair.vegalite.v5.schema.core import ExprRef as _ExprRef
-
-if sys.version_info >= (3, 12):
- from typing import override
-else:
- from typing_extensions import override
-
-
-class _ConstExpressionType(type):
- """Metaclass providing read-only class properties for :class:`expr`."""
-
- @property
- def NaN(cls) -> Expression:
- """Not a number (same as JavaScript literal NaN)."""
- return ConstExpression("NaN")
-
- @property
- def LN10(cls) -> Expression:
- """The natural log of 10 (alias to Math.LN10)."""
- return ConstExpression("LN10")
-
- @property
- def E(cls) -> Expression:
- """The transcendental number e (alias to Math.E)."""
- return ConstExpression("E")
-
- @property
- def LOG10E(cls) -> Expression:
- """The base 10 logarithm e (alias to Math.LOG10E)."""
- return ConstExpression("LOG10E")
-
- @property
- def LOG2E(cls) -> Expression:
- """The base 2 logarithm of e (alias to Math.LOG2E)."""
- return ConstExpression("LOG2E")
-
- @property
- def SQRT1_2(cls) -> Expression:
- """The square root of 0.5 (alias to Math.SQRT1_2)."""
- return ConstExpression("SQRT1_2")
-
- @property
- def LN2(cls) -> Expression:
- """The natural log of 2 (alias to Math.LN2)."""
- return ConstExpression("LN2")
-
- @property
- def SQRT2(cls) -> Expression:
- """The square root of 2 (alias to Math.SQRT1_2)."""
- return ConstExpression("SQRT2")
-
- @property
- def PI(cls) -> Expression:
- """The transcendental number pi (alias to Math.PI)."""
- return ConstExpression("PI")
-
-
-class expr(_ExprRef, metaclass=_ConstExpressionType):
- """
- Utility providing *constants* and *classmethods* to construct expressions.
-
- `Expressions`_ can be used to write basic formulas that enable custom interactions.
-
- Alternatively, an `inline expression`_ may be defined via :class:`expr()`.
-
- Parameters
- ----------
- expr: str
- A `vega expression`_ string.
-
- Returns
- -------
- ``ExprRef``
-
- .. _Expressions:
- https://altair-viz.github.io/user_guide/interactions.html#expressions
- .. _inline expression:
- https://altair-viz.github.io/user_guide/interactions.html#inline-expressions
- .. _vega expression:
- https://vega.github.io/vega/docs/expressions/
-
- Examples
- --------
- >>> import altair as alt
-
- >>> bind_range = alt.binding_range(min=100, max=300, name="Slider value: ")
- >>> param_width = alt.param(bind=bind_range, name="param_width")
- >>> param_color = alt.param(
- ... expr=alt.expr.if_(param_width < 200, "red", "black"),
- ... name="param_color",
- ... )
- >>> y = alt.Y("yval").axis(titleColor=param_color)
-
- >>> y
- Y({
- axis: {'titleColor': Parameter('param_color', VariableParameter({
- expr: if((param_width < 200),'red','black'),
- name: 'param_color'
- }))},
- shorthand: 'yval'
- })
- """
-
- @override
- def __new__(cls: type[_ExprRef], expr: str) -> _ExprRef: # type: ignore[misc]
- return _ExprRef(expr=expr)
-
- @classmethod
- def isArray(cls, value: IntoExpression, /) -> Expression:
- """Returns true if ``value`` is an array, false otherwise."""
- return FunctionExpression("isArray", (value,))
-
- @classmethod
- def isBoolean(cls, value: IntoExpression, /) -> Expression:
- """Returns true if ``value`` is a boolean (``true`` or ``false``), false otherwise."""
- return FunctionExpression("isBoolean", (value,))
-
- @classmethod
- def isDate(cls, value: IntoExpression, /) -> Expression:
- """
- Returns true if ``value`` is a Date object, false otherwise.
-
- This method will return false for timestamp numbers or date-formatted strings; it recognizes
- Date objects only.
- """
- return FunctionExpression("isDate", (value,))
-
- @classmethod
- def isDefined(cls, value: IntoExpression, /) -> Expression:
- """
- Returns true if ``value`` is a defined value, false if ``value`` equals ``undefined``.
-
- This method will return true for ``null`` and ``NaN`` values.
- """
- return FunctionExpression("isDefined", (value,))
-
- @classmethod
- def isNumber(cls, value: IntoExpression, /) -> Expression:
- """
- Returns true if ``value`` is a number, false otherwise.
-
- ``NaN`` and ``Infinity`` are considered numbers.
- """
- return FunctionExpression("isNumber", (value,))
-
- @classmethod
- def isObject(cls, value: IntoExpression, /) -> Expression:
- """Returns true if ``value`` is an object (including arrays and Dates), false otherwise."""
- return FunctionExpression("isObject", (value,))
-
- @classmethod
- def isRegExp(cls, value: IntoExpression, /) -> Expression:
- """Returns true if ``value`` is a RegExp (regular expression) object, false otherwise."""
- return FunctionExpression("isRegExp", (value,))
-
- @classmethod
- def isString(cls, value: IntoExpression, /) -> Expression:
- """Returns true if ``value`` is a string, false otherwise."""
- return FunctionExpression("isString", (value,))
-
- @classmethod
- def isValid(cls, value: IntoExpression, /) -> Expression:
- """Returns true if ``value`` is not ``null``, ``undefined``, or ``NaN``, false otherwise."""
- return FunctionExpression("isValid", (value,))
-
- @classmethod
- def toBoolean(cls, value: IntoExpression, /) -> Expression:
- """
- Coerces the input ``value`` to a string.
-
- Null values and empty strings are mapped to ``null``.
- """
- return FunctionExpression("toBoolean", (value,))
-
- @classmethod
- def toDate(cls, value: IntoExpression, /) -> Expression:
- """
- Coerces the input ``value`` to a Date instance.
-
- Null values and empty strings are mapped to ``null``. If an optional *parser* function is
- provided, it is used to perform date parsing, otherwise ``Date.parse`` is used. Be aware
- that ``Date.parse`` has different implementations across browsers!
- """
- return FunctionExpression("toDate", (value,))
-
- @classmethod
- def toNumber(cls, value: IntoExpression, /) -> Expression:
- """
- Coerces the input ``value`` to a number.
-
- Null values and empty strings are mapped to ``null``.
- """
- return FunctionExpression("toNumber", (value,))
-
- @classmethod
- def toString(cls, value: IntoExpression, /) -> Expression:
- """
- Coerces the input ``value`` to a string.
-
- Null values and empty strings are mapped to ``null``.
- """
- return FunctionExpression("toString", (value,))
-
- @classmethod
- def if_(
- cls,
- test: IntoExpression,
- thenValue: IntoExpression,
- elseValue: IntoExpression,
- /,
- ) -> Expression:
- """
- If ``test`` is truthy, returns ``thenValue``.
-
- Otherwise, returns ``elseValue``. The *if* function is equivalent to the ternary operator
- ``a ? b : c``.
- """
- return FunctionExpression("if", (test, thenValue, elseValue))
-
- @classmethod
- def isNaN(cls, value: IntoExpression, /) -> Expression:
- """
- Returns true if ``value`` is not a number.
-
- Same as JavaScript's `Number.isNaN`_.
-
- .. _Number.isNaN:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNan
- """
- return FunctionExpression("isNaN", (value,))
-
- @classmethod
- def isFinite(cls, value: IntoExpression, /) -> Expression:
- """
- Returns true if ``value`` is a finite number.
-
- Same as JavaScript's `Number.isFinite`_.
-
- .. _Number.isFinite:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite
- """
- return FunctionExpression("isFinite", (value,))
-
- @classmethod
- def abs(cls, value: IntoExpression, /) -> Expression:
- """
- Returns the absolute value of ``value``.
-
- Same as JavaScript's `Math.abs`_.
-
- .. _Math.abs:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs
- """
- return FunctionExpression("abs", (value,))
-
- @classmethod
- def acos(cls, value: IntoExpression, /) -> Expression:
- """
- Trigonometric arccosine.
-
- Same as JavaScript's `Math.acos`_.
-
- .. _Math.acos:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos
- """
- return FunctionExpression("acos", (value,))
-
- @classmethod
- def asin(cls, value: IntoExpression, /) -> Expression:
- """
- Trigonometric arcsine.
-
- Same as JavaScript's `Math.asin`_.
-
- .. _Math.asin:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin
- """
- return FunctionExpression("asin", (value,))
-
- @classmethod
- def atan(cls, value: IntoExpression, /) -> Expression:
- """
- Trigonometric arctangent.
-
- Same as JavaScript's `Math.atan`_.
-
- .. _Math.atan:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan
- """
- return FunctionExpression("atan", (value,))
-
- @classmethod
- def atan2(cls, dy: IntoExpression, dx: IntoExpression, /) -> Expression:
- """
- Returns the arctangent of *dy / dx*.
-
- Same as JavaScript's `Math.atan2`_.
-
- .. _Math.atan2:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2
- """
- return FunctionExpression("atan2", (dy, dx))
-
- @classmethod
- def ceil(cls, value: IntoExpression, /) -> Expression:
- """
- Rounds ``value`` to the nearest integer of equal or greater value.
-
- Same as JavaScript's `Math.ceil`_.
-
- .. _Math.ceil:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil
- """
- return FunctionExpression("ceil", (value,))
-
- @classmethod
- def clamp(
- cls, value: IntoExpression, min: IntoExpression, max: IntoExpression, /
- ) -> Expression:
- """Restricts ``value`` to be between the specified ``min`` and ``max``."""
- return FunctionExpression("clamp", (value, min, max))
-
- @classmethod
- def cos(cls, value: IntoExpression, /) -> Expression:
- """
- Trigonometric cosine.
-
- Same as JavaScript's `Math.cos`_.
-
- .. _Math.cos:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos
- """
- return FunctionExpression("cos", (value,))
-
- @classmethod
- def exp(cls, exponent: IntoExpression, /) -> Expression:
- """
- Returns the value of *e* raised to the provided ``exponent``.
-
- Same as JavaScript's `Math.exp`_.
-
- .. _Math.exp:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp
- """
- return FunctionExpression("exp", (exponent,))
-
- @classmethod
- def floor(cls, value: IntoExpression, /) -> Expression:
- """
- Rounds ``value`` to the nearest integer of equal or lower value.
-
- Same as JavaScript's `Math.floor`_.
-
- .. _Math.floor:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor
- """
- return FunctionExpression("floor", (value,))
-
- @classmethod
- def hypot(cls, value: IntoExpression, /) -> Expression:
- """
- Returns the square root of the sum of squares of its arguments.
-
- Same as JavaScript's `Math.hypot`_.
-
- .. _Math.hypot:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot
- """
- return FunctionExpression("hypot", (value,))
-
- @classmethod
- def log(cls, value: IntoExpression, /) -> Expression:
- """
- Returns the natural logarithm of ``value``.
-
- Same as JavaScript's `Math.log`_.
-
- .. _Math.log:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log
- """
- return FunctionExpression("log", (value,))
-
- @classmethod
- def max(
- cls, value1: IntoExpression, value2: IntoExpression, *args: Any
- ) -> Expression:
- """
- Returns the maximum argument value.
-
- Same as JavaScript's `Math.max`_.
-
- .. _Math.max:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max
- """
- return FunctionExpression("max", (value1, value2, *args))
-
- @classmethod
- def min(
- cls, value1: IntoExpression, value2: IntoExpression, *args: Any
- ) -> Expression:
- """
- Returns the minimum argument value.
-
- Same as JavaScript's `Math.min`_.
-
- .. _Math.min:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min
- """
- return FunctionExpression("min", (value1, value2, *args))
-
- @classmethod
- def pow(cls, value: IntoExpression, exponent: IntoExpression, /) -> Expression:
- """
- Returns ``value`` raised to the given ``exponent``.
-
- Same as JavaScript's `Math.pow`_.
-
- .. _Math.pow:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow
- """
- return FunctionExpression("pow", (value, exponent))
-
- @classmethod
- def round(cls, value: IntoExpression, /) -> Expression:
- """
- Rounds ``value`` to the nearest integer.
-
- Same as JavaScript's `Math.round`_.
-
- .. _Math.round:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
- """
- return FunctionExpression("round", (value,))
-
- @classmethod
- def sin(cls, value: IntoExpression, /) -> Expression:
- """
- Trigonometric sine.
-
- Same as JavaScript's `Math.sin`_.
-
- .. _Math.sin:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin
- """
- return FunctionExpression("sin", (value,))
-
- @classmethod
- def sqrt(cls, value: IntoExpression, /) -> Expression:
- """
- Square root function.
-
- Same as JavaScript's `Math.sqrt`_.
-
- .. _Math.sqrt:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt
- """
- return FunctionExpression("sqrt", (value,))
-
- @classmethod
- def tan(cls, value: IntoExpression, /) -> Expression:
- """
- Trigonometric tangent.
-
- Same as JavaScript's `Math.tan`_.
-
- .. _Math.tan:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan
- """
- return FunctionExpression("tan", (value,))
-
- @classmethod
- def sampleNormal(
- cls, mean: IntoExpression = None, stdev: IntoExpression = None, /
- ) -> Expression:
- """
- Returns a sample from a univariate `normal (Gaussian) probability distribution`_ with specified ``mean`` and standard deviation ``stdev``.
-
- If unspecified, the mean defaults to ``0`` and the standard deviation defaults to ``1``.
-
- .. _normal (Gaussian) probability distribution:
- https://en.wikipedia.org/wiki/Normal_distribution
- """
- return FunctionExpression("sampleNormal", (mean, stdev))
-
- @classmethod
- def sampleLogNormal(
- cls, mean: IntoExpression = None, stdev: IntoExpression = None, /
- ) -> Expression:
- """
- Returns a sample from a univariate `log-normal probability distribution`_ with specified log ``mean`` and log standard deviation ``stdev``.
-
- If unspecified, the log mean defaults to ``0`` and the log standard deviation defaults to
- ``1``.
-
- .. _log-normal probability distribution:
- https://en.wikipedia.org/wiki/Log-normal_distribution
- """
- return FunctionExpression("sampleLogNormal", (mean, stdev))
-
- @classmethod
- def sampleUniform(
- cls, min: IntoExpression = None, max: IntoExpression = None, /
- ) -> Expression:
- """
- Returns a sample from a univariate `continuous uniform probability distribution`_) over the interval [``min``, ``max``).
-
- If unspecified, ``min`` defaults to ``0`` and ``max`` defaults to ``1``. If only one
- argument is provided, it is interpreted as the ``max`` value.
-
- .. _continuous uniform probability distribution:
- https://en.wikipedia.org/wiki/Uniform_distribution_(continuous
- """
- return FunctionExpression("sampleUniform", (min, max))
-
- @classmethod
- def datetime(
- cls,
- year: IntoExpression,
- month: IntoExpression,
- day: IntoExpression = None,
- hour: IntoExpression = None,
- min: IntoExpression = None,
- sec: IntoExpression = None,
- millisec: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Returns a new ``Date`` instance.
-
- The ``month`` is 0-based, such that ``1`` represents February.
- """
- return FunctionExpression(
- "datetime", (year, month, day, hour, min, sec, millisec)
- )
-
- @classmethod
- def date(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the day of the month for the given ``datetime`` value, in local time."""
- return FunctionExpression("date", (datetime,))
-
- @classmethod
- def day(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the day of the week for the given ``datetime`` value, in local time."""
- return FunctionExpression("day", (datetime,))
-
- @classmethod
- def dayofyear(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the one-based day of the year for the given ``datetime`` value, in local time."""
- return FunctionExpression("dayofyear", (datetime,))
-
- @classmethod
- def year(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the year for the given ``datetime`` value, in local time."""
- return FunctionExpression("year", (datetime,))
-
- @classmethod
- def quarter(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the quarter of the year (0-3) for the given ``datetime`` value, in local time."""
- return FunctionExpression("quarter", (datetime,))
-
- @classmethod
- def month(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the (zero-based) month for the given ``datetime`` value, in local time."""
- return FunctionExpression("month", (datetime,))
-
- @classmethod
- def week(cls, date: IntoExpression, /) -> Expression:
- """
- Returns the week number of the year for the given *datetime*, in local time.
-
- This function assumes Sunday-based weeks. Days before the first Sunday of the year are
- considered to be in week 0, the first Sunday of the year is the start of week 1, the second
- Sunday week 2, *etc.*.
- """
- return FunctionExpression("week", (date,))
-
- @classmethod
- def hours(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the hours component for the given ``datetime`` value, in local time."""
- return FunctionExpression("hours", (datetime,))
-
- @classmethod
- def minutes(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the minutes component for the given ``datetime`` value, in local time."""
- return FunctionExpression("minutes", (datetime,))
-
- @classmethod
- def seconds(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the seconds component for the given ``datetime`` value, in local time."""
- return FunctionExpression("seconds", (datetime,))
-
- @classmethod
- def milliseconds(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the milliseconds component for the given ``datetime`` value, in local time."""
- return FunctionExpression("milliseconds", (datetime,))
-
- @classmethod
- def time(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the epoch-based timestamp for the given ``datetime`` value."""
- return FunctionExpression("time", (datetime,))
-
- @classmethod
- def timezoneoffset(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the timezone offset from the local timezone to UTC for the given ``datetime`` value."""
- return FunctionExpression("timezoneoffset", (datetime,))
-
- @classmethod
- def timeOffset(
- cls, unit: IntoExpression, date: IntoExpression, step: IntoExpression = None, /
- ) -> Expression:
- """
- Returns a new ``Date`` instance that offsets the given ``date`` by the specified time `*unit*`_ in the local timezone.
-
- The optional ``step`` argument indicates the number of time unit steps to offset by (default
- 1).
-
- .. _*unit*:
- https://vega.github.io/vega/docs/api/time/#time-units
- """
- return FunctionExpression("timeOffset", (unit, date, step))
-
- @classmethod
- def timeSequence(
- cls,
- unit: IntoExpression,
- start: IntoExpression,
- stop: IntoExpression,
- step: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Returns an array of ``Date`` instances from ``start`` (inclusive) to ``stop`` (exclusive), with each entry separated by the given time `*unit*`_ in the local timezone.
-
- The optional ``step`` argument indicates the number of time unit steps to take between each
- sequence entry (default 1).
-
- .. _*unit*:
- https://vega.github.io/vega/docs/api/time/#time-units
- """
- return FunctionExpression("timeSequence", (unit, start, stop, step))
-
- @classmethod
- def utc(
- cls,
- year: IntoExpression,
- month: IntoExpression,
- day: IntoExpression = None,
- hour: IntoExpression = None,
- min: IntoExpression = None,
- sec: IntoExpression = None,
- millisec: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Returns a timestamp for the given UTC date.
-
- The ``month`` is 0-based, such that ``1`` represents February.
- """
- return FunctionExpression("utc", (year, month, day, hour, min, sec, millisec))
-
- @classmethod
- def utcdate(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the day of the month for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcdate", (datetime,))
-
- @classmethod
- def utcday(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the day of the week for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcday", (datetime,))
-
- @classmethod
- def utcdayofyear(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the one-based day of the year for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcdayofyear", (datetime,))
-
- @classmethod
- def utcyear(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the year for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcyear", (datetime,))
-
- @classmethod
- def utcquarter(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the quarter of the year (0-3) for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcquarter", (datetime,))
-
- @classmethod
- def utcmonth(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the (zero-based) month for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcmonth", (datetime,))
-
- @classmethod
- def utcweek(cls, date: IntoExpression, /) -> Expression:
- """
- Returns the week number of the year for the given *datetime*, in UTC time.
-
- This function assumes Sunday-based weeks. Days before the first Sunday of the year are
- considered to be in week 0, the first Sunday of the year is the start of week 1, the second
- Sunday week 2, *etc.*.
- """
- return FunctionExpression("utcweek", (date,))
-
- @classmethod
- def utchours(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the hours component for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utchours", (datetime,))
-
- @classmethod
- def utcminutes(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the minutes component for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcminutes", (datetime,))
-
- @classmethod
- def utcseconds(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the seconds component for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcseconds", (datetime,))
-
- @classmethod
- def utcmilliseconds(cls, datetime: IntoExpression, /) -> Expression:
- """Returns the milliseconds component for the given ``datetime`` value, in UTC time."""
- return FunctionExpression("utcmilliseconds", (datetime,))
-
- @classmethod
- def utcOffset(
- cls, unit: IntoExpression, date: IntoExpression, step: IntoExpression = None, /
- ) -> Expression:
- """
- Returns a new ``Date`` instance that offsets the given ``date`` by the specified time `*unit*`_ in UTC time.
-
- The optional ``step`` argument indicates the number of time unit steps to offset by (default
- 1).
-
- .. _*unit*:
- https://vega.github.io/vega/docs/api/time/#time-units
- """
- return FunctionExpression("utcOffset", (unit, date, step))
-
- @classmethod
- def utcSequence(
- cls,
- unit: IntoExpression,
- start: IntoExpression,
- stop: IntoExpression,
- step: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Returns an array of ``Date`` instances from ``start`` (inclusive) to ``stop`` (exclusive), with each entry separated by the given time `*unit*`_ in UTC time.
-
- The optional ``step`` argument indicates the number of time unit steps to take between each
- sequence entry (default 1).
-
- .. _*unit*:
- https://vega.github.io/vega/docs/api/time/#time-units
- """
- return FunctionExpression("utcSequence", (unit, start, stop, step))
-
- @classmethod
- def extent(cls, array: IntoExpression, /) -> Expression:
- """Returns a new *[min, max]* array with the minimum and maximum values of the input array, ignoring ``null``, ``undefined``, and ``NaN`` values."""
- return FunctionExpression("extent", (array,))
-
- @classmethod
- def clampRange(
- cls, range: IntoExpression, min: IntoExpression, max: IntoExpression, /
- ) -> Expression:
- """
- Clamps a two-element ``range`` array in a span-preserving manner.
-
- If the span of the input ``range`` is less than *(max - min)* and an endpoint exceeds either
- the ``min`` or ``max`` value, the range is translated such that the span is preserved and
- one endpoint touches the boundary of the *[min, max]* range. If the span exceeds *(max -
- min)*, the range *[min, max]* is returned.
- """
- return FunctionExpression("clampRange", (range, min, max))
-
- @classmethod
- def indexof(cls, array: IntoExpression, value: IntoExpression, /) -> Expression:
- """Returns the first index of ``value`` in the input ``array``."""
- return FunctionExpression("indexof", (array, value))
-
- @classmethod
- def inrange(cls, value: IntoExpression, range: IntoExpression, /) -> Expression:
- """Tests whether ``value`` lies within (or is equal to either) the first and last values of the ``range`` array."""
- return FunctionExpression("inrange", (value, range))
-
- @classmethod
- def join(
- cls, array: IntoExpression, separator: IntoExpression = None, /
- ) -> Expression:
- """Returns a new string by concatenating all of the elements of the input ``array``, separated by commas or a specified ``separator`` string."""
- return FunctionExpression("join", (array, separator))
-
- @classmethod
- def lastindexof(cls, array: IntoExpression, value: IntoExpression, /) -> Expression:
- """Returns the last index of ``value`` in the input ``array``."""
- return FunctionExpression("lastindexof", (array, value))
-
- @classmethod
- def length(cls, array: IntoExpression, /) -> Expression:
- """Returns the length of the input ``array``."""
- return FunctionExpression("length", (array,))
-
- @classmethod
- def lerp(cls, array: IntoExpression, fraction: IntoExpression, /) -> Expression:
- """
- Returns the linearly interpolated value between the first and last entries in the ``array`` for the provided interpolation ``fraction`` (typically between 0 and 1).
-
- For example, ``alt.expr.lerp([0, 50], 0.5)`` returns 25.
- """
- return FunctionExpression("lerp", (array, fraction))
-
- @classmethod
- def peek(cls, array: IntoExpression, /) -> Expression:
- """
- Returns the last element in the input ``array``.
-
- Similar to the built-in ``Array.pop`` method, except that it does not remove the last
- element. This method is a convenient shorthand for ``array[array.length - 1]``.
- """
- return FunctionExpression("peek", (array,))
-
- @classmethod
- def pluck(cls, array: IntoExpression, field: IntoExpression, /) -> Expression:
- """
- Retrieves the value for the specified ``field`` from a given ``array`` of objects.
-
- The input ``field`` string may include nested properties (e.g., ``foo.bar.bz``).
- """
- return FunctionExpression("pluck", (array, field))
-
- @classmethod
- def reverse(cls, array: IntoExpression, /) -> Expression:
- """
- Returns a new array with elements in a reverse order of the input ``array``.
-
- The first array element becomes the last, and the last array element becomes the first.
- """
- return FunctionExpression("reverse", (array,))
-
- @classmethod
- def sequence(cls, *args: Any) -> Expression:
- """
- Returns an array containing an arithmetic sequence of numbers.
-
- If ``step`` is omitted, it defaults to 1. If ``start`` is omitted, it defaults to 0. The
- ``stop`` value is exclusive; it is not included in the result. If ``step`` is positive, the
- last element is the largest *start + i * step* less than ``stop``; if ``step`` is negative,
- the last element is the smallest *start + i * step* greater than ``stop``. If the returned
- array would contain an infinite number of values, an empty range is returned. The arguments
- are not required to be integers.
- """
- return FunctionExpression("sequence", args)
-
- @classmethod
- def slice(
- cls, array: IntoExpression, start: IntoExpression, end: IntoExpression = None, /
- ) -> Expression:
- """
- Returns a section of ``array`` between the ``start`` and ``end`` indices.
-
- If the ``end`` argument is negative, it is treated as an offset from the end of the array
- (*alt.expr.length(array) + end*).
- """
- return FunctionExpression("slice", (array, start, end))
-
- @classmethod
- def span(cls, array: IntoExpression, /) -> Expression:
- """Returns the span of ``array``: the difference between the last and first elements, or *array[array.length-1] - array[0]*."""
- return FunctionExpression("span", (array,))
-
- @classmethod
- def lower(cls, string: IntoExpression, /) -> Expression:
- """Transforms ``string`` to lower-case letters."""
- return FunctionExpression("lower", (string,))
-
- @classmethod
- def pad(
- cls,
- string: IntoExpression,
- length: IntoExpression,
- character: IntoExpression = None,
- align: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Pads a ``string`` value with repeated instances of a ``character`` up to a specified ``length``.
-
- If ``character`` is not specified, a space (' ') is used. By default, padding is added to
- the end of a string. An optional ``align`` parameter specifies if padding should be added to
- the ``'left'`` (beginning), ``'center'``, or ``'right'`` (end) of the input string.
- """
- return FunctionExpression("pad", (string, length, character, align))
-
- @classmethod
- def parseFloat(cls, string: IntoExpression, /) -> Expression:
- """
- Parses the input ``string`` to a floating-point value.
-
- Same as JavaScript's ``parseFloat``.
- """
- return FunctionExpression("parseFloat", (string,))
-
- @classmethod
- def parseInt(cls, string: IntoExpression, /) -> Expression:
- """
- Parses the input ``string`` to an integer value.
-
- Same as JavaScript's ``parseInt``.
- """
- return FunctionExpression("parseInt", (string,))
-
- @classmethod
- def replace(
- cls,
- string: IntoExpression,
- pattern: IntoExpression,
- replacement: IntoExpression,
- /,
- ) -> Expression:
- """
- Returns a new string with some or all matches of ``pattern`` replaced by a ``replacement`` string.
-
- The ``pattern`` can be a string or a regular expression. If ``pattern`` is a string, only
- the first instance will be replaced. Same as `JavaScript's String.replace`_.
-
- .. _JavaScript's String.replace:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
- """
- return FunctionExpression("replace", (string, pattern, replacement))
-
- @classmethod
- def substring(
- cls,
- string: IntoExpression,
- start: IntoExpression,
- end: IntoExpression = None,
- /,
- ) -> Expression:
- """Returns a section of ``string`` between the ``start`` and ``end`` indices."""
- return FunctionExpression("substring", (string, start, end))
-
- @classmethod
- def trim(cls, string: IntoExpression, /) -> Expression:
- """Returns a trimmed string with preceding and trailing whitespace removed."""
- return FunctionExpression("trim", (string,))
-
- @classmethod
- def truncate(
- cls,
- string: IntoExpression,
- length: IntoExpression,
- align: IntoExpression = None,
- ellipsis: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Truncates an input ``string`` to a target ``length``.
-
- The optional ``align`` argument indicates what part of the string should be truncated:
- ``'left'`` (the beginning), ``'center'``, or ``'right'`` (the end). By default, the
- ``'right'`` end of the string is truncated. The optional ``ellipsis`` argument indicates the
- string to use to indicate truncated content; by default the ellipsis character ``…``
- (``\u2026``) is used.
- """
- return FunctionExpression("truncate", (string, length, align, ellipsis))
-
- @classmethod
- def upper(cls, string: IntoExpression, /) -> Expression:
- """Transforms ``string`` to upper-case letters."""
- return FunctionExpression("upper", (string,))
-
- @classmethod
- def merge(
- cls, object1: IntoExpression, object2: IntoExpression = None, *args: Any
- ) -> Expression:
- """
- Merges the input objects ``object1``, ``object2``, etc into a new output object.
-
- Inputs are visited in sequential order, such that key values from later arguments can
- overwrite those from earlier arguments. Example: ``alt.expr.merge({a:1, b:2}, {a:3}) ->
- {a:3, b:2}``.
- """
- return FunctionExpression("merge", (object1, object2, *args))
-
- @classmethod
- def dayFormat(cls, day: IntoExpression, /) -> Expression:
- """
- Formats a (0-6) *weekday* number as a full week day name, according to the current locale.
-
- For example: ``alt.expr.dayFormat(0) -> "Sunday"``.
- """
- return FunctionExpression("dayFormat", (day,))
-
- @classmethod
- def dayAbbrevFormat(cls, day: IntoExpression, /) -> Expression:
- """
- Formats a (0-6) *weekday* number as an abbreviated week day name, according to the current locale.
-
- For example: ``alt.expr.dayAbbrevFormat(0) -> "Sun"``.
- """
- return FunctionExpression("dayAbbrevFormat", (day,))
-
- @classmethod
- def format(cls, value: IntoExpression, specifier: IntoExpression, /) -> Expression:
- """
- Formats a numeric ``value`` as a string.
-
- The ``specifier`` must be a valid `d3-format specifier`_ (e.g., ``alt.expr.format(value,
- ',.2f')``. Null values are formatted as ``"null"``.
-
- .. _d3-format specifier:
- https://github.com/d3/d3-format/
- """
- return FunctionExpression("format", (value, specifier))
-
- @classmethod
- def monthFormat(cls, month: IntoExpression, /) -> Expression:
- """
- Formats a (zero-based) ``month`` number as a full month name, according to the current locale.
-
- For example: ``alt.expr.monthFormat(0) -> "January"``.
- """
- return FunctionExpression("monthFormat", (month,))
-
- @classmethod
- def monthAbbrevFormat(cls, month: IntoExpression, /) -> Expression:
- """
- Formats a (zero-based) ``month`` number as an abbreviated month name, according to the current locale.
-
- For example: ``alt.expr.monthAbbrevFormat(0) -> "Jan"``.
- """
- return FunctionExpression("monthAbbrevFormat", (month,))
-
- @classmethod
- def timeUnitSpecifier(
- cls, units: IntoExpression, specifiers: IntoExpression = None, /
- ) -> Expression:
- """
- Returns a time format specifier string for the given time `*units*`_.
-
- The optional ``specifiers`` object provides a set of specifier sub-strings for customizing
- the format; for more, see the `timeUnitSpecifier API documentation`_. The resulting
- specifier string can then be used as input to the `timeFormat`_ or `utcFormat`_ functions,
- or as the *format* parameter of an axis or legend. For example: ``alt.expr.timeFormat(date,
- alt.expr.timeUnitSpecifier('year'))`` or ``alt.expr.timeFormat(date,
- alt.expr.timeUnitSpecifier(['hours', 'minutes']))``.
-
- .. _*units*:
- https://vega.github.io/vega/docs/api/time/#time-units
- .. _timeUnitSpecifier API documentation:
- https://vega.github.io/vega/docs/api/time/#timeUnitSpecifier
- .. _timeFormat:
- https://vega.github.io/vega/docs/expressions/#timeFormat
- .. _utcFormat:
- https://vega.github.io/vega/docs/expressions/#utcFormat
- """
- return FunctionExpression("timeUnitSpecifier", (units, specifiers))
-
- @classmethod
- def timeFormat(
- cls, value: IntoExpression, specifier: IntoExpression, /
- ) -> Expression:
- """
- Formats a datetime ``value`` (either a ``Date`` object or timestamp) as a string, according to the local time.
-
- The ``specifier`` must be a valid `d3-time-format specifier`_ or `TimeMultiFormat object`_.
- For example: ``alt.expr.timeFormat(timestamp, '%A')``. Null values are formatted as
- ``"null"``.
-
- .. _d3-time-format specifier:
- https://github.com/d3/d3-time-format/
- .. _TimeMultiFormat object:
- https://vega.github.io/vega/docs/types/#TimeMultiFormat
- """
- return FunctionExpression("timeFormat", (value, specifier))
-
- @classmethod
- def timeParse(
- cls, string: IntoExpression, specifier: IntoExpression, /
- ) -> Expression:
- """
- Parses a ``string`` value to a Date object, according to the local time.
-
- The ``specifier`` must be a valid `d3-time-format specifier`_. For example:
- ``alt.expr.timeParse('June 30, 2015', '%B %d, %Y')``.
-
- .. _d3-time-format specifier:
- https://github.com/d3/d3-time-format/
- """
- return FunctionExpression("timeParse", (string, specifier))
-
- @classmethod
- def utcFormat(
- cls, value: IntoExpression, specifier: IntoExpression, /
- ) -> Expression:
- """
- Formats a datetime ``value`` (either a ``Date`` object or timestamp) as a string, according to `UTC`_ time.
-
- The ``specifier`` must be a valid `d3-time-format specifier`_ or `TimeMultiFormat object`_.
- For example: ``alt.expr.utcFormat(timestamp, '%A')``. Null values are formatted as
- ``"null"``.
-
- .. _UTC:
- https://en.wikipedia.org/wiki/Coordinated_Universal_Time
- .. _d3-time-format specifier:
- https://github.com/d3/d3-time-format/
- .. _TimeMultiFormat object:
- https://vega.github.io/vega/docs/types/#TimeMultiFormat
- """
- return FunctionExpression("utcFormat", (value, specifier))
-
- @classmethod
- def utcParse(
- cls, value: IntoExpression, specifier: IntoExpression, /
- ) -> Expression:
- """
- Parses a *string* value to a Date object, according to `UTC`_ time.
-
- The ``specifier`` must be a valid `d3-time-format specifier`_. For example:
- ``alt.expr.utcParse('June 30, 2015', '%B %d, %Y')``.
-
- .. _UTC:
- https://en.wikipedia.org/wiki/Coordinated_Universal_Time
- .. _d3-time-format specifier:
- https://github.com/d3/d3-time-format/
- """
- return FunctionExpression("utcParse", (value, specifier))
-
- @classmethod
- def regexp(
- cls, pattern: IntoExpression, flags: IntoExpression = None, /
- ) -> Expression:
- """
- Creates a regular expression instance from an input ``pattern`` string and optional ``flags``.
-
- Same as `JavaScript's RegExp`_.
-
- .. _JavaScript's RegExp:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
- """
- return FunctionExpression("regexp", (pattern, flags))
-
- @classmethod
- def test(
- cls, regexp: IntoExpression, string: IntoExpression = None, /
- ) -> Expression:
- r"""
- Evaluates a regular expression ``regexp`` against the input ``string``, returning ``true`` if the string matches the pattern, ``false`` otherwise.
-
- For example: ``alt.expr.test(/\\d{3}/, "32-21-9483") -> true``.
- """
- return FunctionExpression("test", (regexp, string))
-
- @classmethod
- def rgb(cls, *args: Any) -> Expression:
- """
- Constructs a new `RGB`_ color.
-
- If ``r``, ``g`` and ``b`` are specified, these represent the channel values of the returned
- color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
- string is specified, it is parsed and then converted to the RGB color space. Uses
- `d3-color's rgb function`_.
-
- .. _RGB:
- https://en.wikipedia.org/wiki/RGB_color_model
- .. _d3-color's rgb function:
- https://github.com/d3/d3-color#rgb
- """
- return FunctionExpression("rgb", args)
-
- @classmethod
- def hsl(cls, *args: Any) -> Expression:
- """
- Constructs a new `HSL`_ color.
-
- If ``h``, ``s`` and ``l`` are specified, these represent the channel values of the returned
- color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
- string is specified, it is parsed and then converted to the HSL color space. Uses
- `d3-color's hsl function`_.
-
- .. _HSL:
- https://en.wikipedia.org/wiki/HSL_and_HSV
- .. _d3-color's hsl function:
- https://github.com/d3/d3-color#hsl
- """
- return FunctionExpression("hsl", args)
-
- @classmethod
- def lab(cls, *args: Any) -> Expression:
- """
- Constructs a new `CIE LAB`_ color.
-
- If ``l``, ``a`` and ``b`` are specified, these represent the channel values of the returned
- color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
- string is specified, it is parsed and then converted to the LAB color space. Uses
- `d3-color's lab function`_.
-
- .. _CIE LAB:
- https://en.wikipedia.org/wiki/Lab_color_space#CIELAB
- .. _d3-color's lab function:
- https://github.com/d3/d3-color#lab
- """
- return FunctionExpression("lab", args)
-
- @classmethod
- def hcl(cls, *args: Any) -> Expression:
- """
- Constructs a new `HCL`_ (hue, chroma, luminance) color.
-
- If ``h``, ``c`` and ``l`` are specified, these represent the channel values of the returned
- color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
- string is specified, it is parsed and then converted to the HCL color space. Uses
- `d3-color's hcl function`_.
-
- .. _HCL:
- https://en.wikipedia.org/wiki/Lab_color_space#CIELAB
- .. _d3-color's hcl function:
- https://github.com/d3/d3-color#hcl
- """
- return FunctionExpression("hcl", args)
-
- @classmethod
- def group(cls, name: IntoExpression = None, /) -> Expression:
- """
- Returns the scenegraph group mark item in which the current event has occurred.
-
- If no arguments are provided, the immediate parent group is returned. If a group name is
- provided, the matching ancestor group item is returned.
- """
- return FunctionExpression("group", (name,))
-
- @classmethod
- def xy(cls, item: IntoExpression = None, /) -> Expression:
- """
- Returns the x- and y-coordinates for the current event as a two-element array.
-
- If no arguments are provided, the top-level coordinate space of the view is used. If a
- scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
- item is used.
- """
- return FunctionExpression("xy", (item,))
-
- @classmethod
- def x(cls, item: IntoExpression = None, /) -> Expression:
- """
- Returns the x coordinate for the current event.
-
- If no arguments are provided, the top-level coordinate space of the view is used. If a
- scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
- item is used.
- """
- return FunctionExpression("x", (item,))
-
- @classmethod
- def y(cls, item: IntoExpression = None, /) -> Expression:
- """
- Returns the y coordinate for the current event.
-
- If no arguments are provided, the top-level coordinate space of the view is used. If a
- scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
- item is used.
- """
- return FunctionExpression("y", (item,))
-
- @classmethod
- def pinchDistance(cls, event: IntoExpression, /) -> Expression:
- """Returns the pixel distance between the first two touch points of a multi-touch event."""
- return FunctionExpression("pinchDistance", (event,))
-
- @classmethod
- def pinchAngle(cls, event: IntoExpression, /) -> Expression:
- """Returns the angle of the line connecting the first two touch points of a multi-touch event."""
- return FunctionExpression("pinchAngle", (event,))
-
- @classmethod
- def inScope(cls, item: IntoExpression, /) -> Expression:
- """Returns true if the given scenegraph ``item`` is a descendant of the group mark in which the event handler was defined, false otherwise."""
- return FunctionExpression("inScope", (item,))
-
- @classmethod
- def data(cls, name: IntoExpression, /) -> Expression:
- """
- Returns the array of data objects for the Vega data set with the given ``name``.
-
- If the data set is not found, returns an empty array.
- """
- return FunctionExpression("data", (name,))
-
- @classmethod
- def indata(
- cls, name: IntoExpression, field: IntoExpression, value: IntoExpression, /
- ) -> Expression:
- """
- Tests if the data set with a given ``name`` contains a datum with a ``field`` value that matches the input ``value``.
-
- For example: ``alt.expr.indata('table', 'category', value)``.
- """
- return FunctionExpression("indata", (name, field, value))
-
- @classmethod
- def scale(
- cls,
- name: IntoExpression,
- value: IntoExpression,
- group: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Applies the named scale transform (or projection) to the specified ``value``.
-
- The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
- scope in which to look up the scale or projection.
- """
- return FunctionExpression("scale", (name, value, group))
-
- @classmethod
- def invert(
- cls,
- name: IntoExpression,
- value: IntoExpression,
- group: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Inverts the named scale transform (or projection) for the specified ``value``.
-
- The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
- scope in which to look up the scale or projection.
- """
- return FunctionExpression("invert", (name, value, group))
-
- @classmethod
- def copy(cls, name: IntoExpression, group: IntoExpression = None, /) -> Expression: # type: ignore[override]
- """
- Returns a copy (a new cloned instance) of the named scale transform of projection, or ``undefined`` if no scale or projection is found.
-
- The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
- scope in which to look up the scale or projection.
- """
- return FunctionExpression("copy", (name, group))
-
- @classmethod
- def domain(
- cls, name: IntoExpression, group: IntoExpression = None, /
- ) -> Expression:
- """
- Returns the scale domain array for the named scale transform, or an empty array if the scale is not found.
-
- The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
- scope in which to look up the scale.
- """
- return FunctionExpression("domain", (name, group))
-
- @classmethod
- def range(cls, name: IntoExpression, group: IntoExpression = None, /) -> Expression:
- """
- Returns the scale range array for the named scale transform, or an empty array if the scale is not found.
-
- The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
- scope in which to look up the scale.
- """
- return FunctionExpression("range", (name, group))
-
- @classmethod
- def bandwidth(
- cls, name: IntoExpression, group: IntoExpression = None, /
- ) -> Expression:
- """
- Returns the current band width for the named band scale transform, or zero if the scale is not found or is not a band scale.
-
- The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
- scope in which to look up the scale.
- """
- return FunctionExpression("bandwidth", (name, group))
-
- @classmethod
- def bandspace(
- cls,
- count: IntoExpression,
- paddingInner: IntoExpression = None,
- paddingOuter: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Returns the number of steps needed within a band scale, based on the ``count`` of domain elements and the inner and outer padding values.
-
- While normally calculated within the scale itself, this function can be helpful for
- determining the size of a chart's layout.
- """
- return FunctionExpression("bandspace", (count, paddingInner, paddingOuter))
-
- @classmethod
- def gradient(
- cls,
- scale: IntoExpression,
- p0: IntoExpression,
- p1: IntoExpression,
- count: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Returns a linear color gradient for the ``scale`` (whose range must be a `continuous color scheme`_) and starting and ending points ``p0`` and ``p1``, each an *[x, y]* array.
-
- The points ``p0`` and ``p1`` should be expressed in normalized coordinates in the domain [0,
- 1], relative to the bounds of the item being colored. If unspecified, ``p0`` defaults to
- ``[0, 0]`` and ``p1`` defaults to ``[1, 0]``, for a horizontal gradient that spans the full
- bounds of an item. The optional ``count`` argument indicates a desired target number of
- sample points to take from the color scale.
-
- .. _continuous color scheme:
- https://vega.github.io/vega/docs/schemes
- """
- return FunctionExpression("gradient", (scale, p0, p1, count))
-
- @classmethod
- def panLinear(cls, domain: IntoExpression, delta: IntoExpression, /) -> Expression:
- """
- Given a linear scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
-
- The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
- indicates panning the scale domain to the right by half the scale range.
- """
- return FunctionExpression("panLinear", (domain, delta))
-
- @classmethod
- def panLog(cls, domain: IntoExpression, delta: IntoExpression, /) -> Expression:
- """
- Given a log scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
-
- The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
- indicates panning the scale domain to the right by half the scale range.
- """
- return FunctionExpression("panLog", (domain, delta))
-
- @classmethod
- def panPow(
- cls, domain: IntoExpression, delta: IntoExpression, exponent: IntoExpression, /
- ) -> Expression:
- """
- Given a power scale ``domain`` array with numeric or datetime values and the given ``exponent``, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
-
- The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
- indicates panning the scale domain to the right by half the scale range.
- """
- return FunctionExpression("panPow", (domain, delta, exponent))
-
- @classmethod
- def panSymlog(
- cls, domain: IntoExpression, delta: IntoExpression, constant: IntoExpression, /
- ) -> Expression:
- """
- Given a symmetric log scale ``domain`` array with numeric or datetime values parameterized by the given ``constant``, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
-
- The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
- indicates panning the scale domain to the right by half the scale range.
- """
- return FunctionExpression("panSymlog", (domain, delta, constant))
-
- @classmethod
- def zoomLinear(
- cls,
- domain: IntoExpression,
- anchor: IntoExpression,
- scaleFactor: IntoExpression,
- /,
- ) -> Expression:
- """
- Given a linear scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
-
- The ``anchor`` value represents the zoom position in terms of fractional units of the scale
- range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
- """
- return FunctionExpression("zoomLinear", (domain, anchor, scaleFactor))
-
- @classmethod
- def zoomLog(
- cls,
- domain: IntoExpression,
- anchor: IntoExpression,
- scaleFactor: IntoExpression,
- /,
- ) -> Expression:
- """
- Given a log scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
-
- The ``anchor`` value represents the zoom position in terms of fractional units of the scale
- range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
- """
- return FunctionExpression("zoomLog", (domain, anchor, scaleFactor))
-
- @classmethod
- def zoomPow(
- cls,
- domain: IntoExpression,
- anchor: IntoExpression,
- scaleFactor: IntoExpression,
- exponent: IntoExpression,
- /,
- ) -> Expression:
- """
- Given a power scale ``domain`` array with numeric or datetime values and the given ``exponent``, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
-
- The ``anchor`` value represents the zoom position in terms of fractional units of the scale
- range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
- """
- return FunctionExpression("zoomPow", (domain, anchor, scaleFactor, exponent))
-
- @classmethod
- def zoomSymlog(
- cls,
- domain: IntoExpression,
- anchor: IntoExpression,
- scaleFactor: IntoExpression,
- constant: IntoExpression,
- /,
- ) -> Expression:
- """
- Given a symmetric log scale ``domain`` array with numeric or datetime values parameterized by the given ``constant``, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
-
- The ``anchor`` value represents the zoom position in terms of fractional units of the scale
- range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
- """
- return FunctionExpression("zoomSymlog", (domain, anchor, scaleFactor, constant))
-
- @classmethod
- def geoArea(
- cls,
- projection: IntoExpression,
- feature: IntoExpression,
- group: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Returns the projected planar area (typically in square pixels) of a GeoJSON ``feature`` according to the named ``projection``.
-
- If the ``projection`` argument is ``null``, computes the spherical area in steradians using
- unprojected longitude, latitude coordinates. The optional ``group`` argument takes a
- scenegraph group mark item to indicate the specific scope in which to look up the
- projection. Uses d3-geo's `geoArea`_ and `path.area`_ methods.
-
- .. _geoArea:
- https://github.com/d3/d3-geo#geoArea
- .. _path.area:
- https://github.com/d3/d3-geo#path_area
- """
- return FunctionExpression("geoArea", (projection, feature, group))
-
- @classmethod
- def geoBounds(
- cls,
- projection: IntoExpression,
- feature: IntoExpression,
- group: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Returns the projected planar bounding box (typically in pixels) for the specified GeoJSON ``feature``, according to the named ``projection``.
-
- The bounding box is represented by a two-dimensional array: [[*x₀*, *y₀*], [*x₁*, *y₁*]],
- where *x₀* is the minimum x-coordinate, *y₀* is the minimum y-coordinate, *x₁* is the
- maximum x-coordinate, and *y₁* is the maximum y-coordinate. If the ``projection`` argument
- is ``null``, computes the spherical bounding box using unprojected longitude, latitude
- coordinates. The optional ``group`` argument takes a scenegraph group mark item to indicate
- the specific scope in which to look up the projection. Uses d3-geo's `geoBounds`_ and
- `path.bounds`_ methods.
-
- .. _geoBounds:
- https://github.com/d3/d3-geo#geoBounds
- .. _path.bounds:
- https://github.com/d3/d3-geo#path_bounds
- """
- return FunctionExpression("geoBounds", (projection, feature, group))
-
- @classmethod
- def geoCentroid(
- cls,
- projection: IntoExpression,
- feature: IntoExpression,
- group: IntoExpression = None,
- /,
- ) -> Expression:
- """
- Returns the projected planar centroid (typically in pixels) for the specified GeoJSON ``feature``, according to the named ``projection``.
-
- If the ``projection`` argument is ``null``, computes the spherical centroid using
- unprojected longitude, latitude coordinates. The optional ``group`` argument takes a
- scenegraph group mark item to indicate the specific scope in which to look up the
- projection. Uses d3-geo's `geoCentroid`_ and `path.centroid`_ methods.
-
- .. _geoCentroid:
- https://github.com/d3/d3-geo#geoCentroid
- .. _path.centroid:
- https://github.com/d3/d3-geo#path_centroid
- """
- return FunctionExpression("geoCentroid", (projection, feature, group))
-
- @classmethod
- def geoScale(
- cls, projection: IntoExpression, group: IntoExpression = None, /
- ) -> Expression:
- """
- Returns the scale value for the named ``projection``.
-
- The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
- scope in which to look up the projection.
- """
- return FunctionExpression("geoScale", (projection, group))
-
- @classmethod
- def treePath(
- cls, name: IntoExpression, source: IntoExpression, target: IntoExpression, /
- ) -> Expression:
- """
- For the hierarchy data set with the given ``name``, returns the shortest path through from the ``source`` node id to the ``target`` node id.
-
- The path starts at the ``source`` node, ascends to the least common ancestor of the
- ``source`` node and the ``target`` node, and then descends to the ``target`` node.
- """
- return FunctionExpression("treePath", (name, source, target))
-
- @classmethod
- def treeAncestors(cls, name: IntoExpression, node: IntoExpression, /) -> Expression:
- """For the hierarchy data set with the given ``name``, returns the array of ancestors nodes, starting with the input ``node``, then followed by each parent up to the root."""
- return FunctionExpression("treeAncestors", (name, node))
-
- @classmethod
- def warn(
- cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
- ) -> Expression:
- """
- Logs a warning message and returns the last argument.
-
- For the message to appear in the console, the visualization view must have the appropriate
- logging level set.
- """
- return FunctionExpression("warn", (value1, value2, *args))
-
- @classmethod
- def info(
- cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
- ) -> Expression:
- """
- Logs an informative message and returns the last argument.
-
- For the message to appear in the console, the visualization view must have the appropriate
- logging level set.
- """
- return FunctionExpression("info", (value1, value2, *args))
-
- @classmethod
- def debug(
- cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
- ) -> Expression:
- """
- Logs a debugging message and returns the last argument.
-
- For the message to appear in the console, the visualization view must have the appropriate
- logging level set.
- """
- return FunctionExpression("debug", (value1, value2, *args))
-
-
-_ExprType = expr
-# NOTE: Compatibility alias for previous type of `alt.expr`.
-# `_ExprType` was not referenced in any internal imports/tests.
diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py
index 65935f739..68462523b 100644
--- a/tests/expr/test_expr.py
+++ b/tests/expr/test_expr.py
@@ -10,7 +10,6 @@
from altair import datum, expr, ExprRef
from altair.expr import _ConstExpressionType
-from altair.expr import dummy as dummy
from altair.expr.core import Expression, GetAttrExpression
if TYPE_CHECKING:
From 16a92a45721c33c83240954327ea15914bc15cb6 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 11:16:52 +0100
Subject: [PATCH 48/77] test: Remove old `test_expr` functions
---
tests/expr/test_expr.py | 26 --------------------------
1 file changed, 26 deletions(-)
diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py
index 68462523b..d0523cc33 100644
--- a/tests/expr/test_expr.py
+++ b/tests/expr/test_expr.py
@@ -25,17 +25,6 @@ def _is_property(obj: Any, /) -> bool:
return isinstance(obj, property)
-def _get_classmethod_names(tp: type[Any], /) -> Iterator[str]:
- for m in classify_class_attrs(tp):
- if m.kind == "class method" and m.defining_class is tp:
- yield m.name
-
-
-def _remap_classmethod_names(tp: type[Any], /) -> Iterator[tuple[str, str]]:
- for name in _get_classmethod_names(tp):
- yield VEGA_REMAP.get(name, name), name
-
-
def _get_property_names(tp: type[Any], /) -> Iterator[str]:
for nm, _ in getmembers(tp, _is_property):
yield nm
@@ -123,21 +112,6 @@ def test_expr_methods(
assert repr(fn_call) == f"{veganame}({datum_args})"
-@pytest.mark.parametrize(("veganame", "methodname"), _remap_classmethod_names(expr))
-def test_expr_funcs(veganame: str, methodname: str):
- """
- Test all functions defined in expr.funcs.
-
- # FIXME: These tests are no longer suitable
- They only work for functions with a **single** argument:
-
- TypeError: expr.if_() missing 2 required positional arguments: 'thenValue' and 'elseValue'.
- """
- func = getattr(expr, methodname)
- z = func(datum.xxx)
- assert repr(z) == f"{veganame}(datum.xxx)"
-
-
@pytest.mark.parametrize("constname", _get_property_names(_ConstExpressionType))
def test_expr_consts(constname: str):
"""Test all constants defined in expr.consts."""
From 17da9d0a6dce122803ac1cf7fd915200dfcf9877 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 11:17:51 +0100
Subject: [PATCH 49/77] build: Generate new `alt.expr.__init__.py`
https://github.com/vega/altair/pull/3600#discussion_r1774050096
---
altair/expr/__init__.py | 1664 +++++++++++++++++-------------
tools/generate_schema_wrapper.py | 2 +-
2 files changed, 955 insertions(+), 711 deletions(-)
diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py
index 1f93ac2b7..c61dddbfc 100644
--- a/altair/expr/__init__.py
+++ b/altair/expr/__init__.py
@@ -3,6 +3,7 @@
from __future__ import annotations
import sys
+from typing import TYPE_CHECKING, Any
from altair.expr.core import ConstExpression, FunctionExpression
from altair.vegalite.v5.schema.core import ExprRef as _ExprRef
@@ -12,58 +13,61 @@
else:
from typing_extensions import override
+if TYPE_CHECKING:
+ from altair.expr.core import Expression, IntoExpression
+
class _ConstExpressionType(type):
"""Metaclass providing read-only class properties for :class:`expr`."""
@property
- def NaN(cls) -> ConstExpression:
+ def NaN(cls) -> Expression:
"""Not a number (same as JavaScript literal NaN)."""
return ConstExpression("NaN")
@property
- def LN10(cls) -> ConstExpression:
+ def LN10(cls) -> Expression:
"""The natural log of 10 (alias to Math.LN10)."""
return ConstExpression("LN10")
@property
- def E(cls) -> ConstExpression:
+ def E(cls) -> Expression:
"""The transcendental number e (alias to Math.E)."""
return ConstExpression("E")
@property
- def LOG10E(cls) -> ConstExpression:
+ def LOG10E(cls) -> Expression:
"""The base 10 logarithm e (alias to Math.LOG10E)."""
return ConstExpression("LOG10E")
@property
- def LOG2E(cls) -> ConstExpression:
+ def LOG2E(cls) -> Expression:
"""The base 2 logarithm of e (alias to Math.LOG2E)."""
return ConstExpression("LOG2E")
@property
- def SQRT1_2(cls) -> ConstExpression:
+ def SQRT1_2(cls) -> Expression:
"""The square root of 0.5 (alias to Math.SQRT1_2)."""
return ConstExpression("SQRT1_2")
@property
- def LN2(cls) -> ConstExpression:
+ def LN2(cls) -> Expression:
"""The natural log of 2 (alias to Math.LN2)."""
return ConstExpression("LN2")
@property
- def SQRT2(cls) -> ConstExpression:
+ def SQRT2(cls) -> Expression:
"""The square root of 2 (alias to Math.SQRT1_2)."""
return ConstExpression("SQRT2")
@property
- def PI(cls) -> ConstExpression:
+ def PI(cls) -> Expression:
"""The transcendental number pi (alias to Math.PI)."""
return ConstExpression("PI")
class expr(_ExprRef, metaclass=_ConstExpressionType):
- r"""
+ """
Utility providing *constants* and *classmethods* to construct expressions.
`Expressions`_ can be used to write basic formulas that enable custom interactions.
@@ -110,1321 +114,1561 @@ class expr(_ExprRef, metaclass=_ConstExpressionType):
@override
def __new__(cls: type[_ExprRef], expr: str) -> _ExprRef: # type: ignore[misc]
- # NOTE: `mypy<=1.10.1` is not consistent with typing spec
- # https://github.com/python/mypy/issues/1020
- # https://docs.python.org/3/reference/datamodel.html#object.__new__
- # https://typing.readthedocs.io/en/latest/spec/constructors.html#new-method
return _ExprRef(expr=expr)
@classmethod
- def if_(cls, *args) -> FunctionExpression:
- """
- If *test* is truthy, returns *thenValue*. Otherwise, returns *elseValue*.
-
- The *if* function is equivalent to the ternary operator `a ? b : c`.
- """
- return FunctionExpression("if", args)
-
- @classmethod
- def isArray(cls, *args) -> FunctionExpression:
- """Returns true if *value* is an array, false otherwise."""
- return FunctionExpression("isArray", args)
+ def isArray(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is an array, false otherwise."""
+ return FunctionExpression("isArray", (value,))
@classmethod
- def isBoolean(cls, *args) -> FunctionExpression:
- """Returns true if *value* is a boolean (`true` or `false`), false otherwise."""
- return FunctionExpression("isBoolean", args)
+ def isBoolean(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is a boolean (``true`` or ``false``), false otherwise."""
+ return FunctionExpression("isBoolean", (value,))
@classmethod
- def isDate(cls, *args) -> FunctionExpression:
+ def isDate(cls, value: IntoExpression, /) -> Expression:
"""
- Returns true if *value* is a Date object, false otherwise.
+ Returns true if ``value`` is a Date object, false otherwise.
- This method will return false for timestamp numbers or date-formatted strings; it recognizes Date objects only.
+ This method will return false for timestamp numbers or date-formatted strings; it recognizes
+ Date objects only.
"""
- return FunctionExpression("isDate", args)
+ return FunctionExpression("isDate", (value,))
@classmethod
- def isDefined(cls, *args) -> FunctionExpression:
+ def isDefined(cls, value: IntoExpression, /) -> Expression:
"""
- Returns true if *value* is a defined value, false if *value* equals `undefined`.
+ Returns true if ``value`` is a defined value, false if ``value`` equals ``undefined``.
- This method will return true for `null` and `NaN` values.
+ This method will return true for ``null`` and ``NaN`` values.
"""
- return FunctionExpression("isDefined", args)
+ return FunctionExpression("isDefined", (value,))
@classmethod
- def isNumber(cls, *args) -> FunctionExpression:
+ def isNumber(cls, value: IntoExpression, /) -> Expression:
"""
- Returns true if *value* is a number, false otherwise.
+ Returns true if ``value`` is a number, false otherwise.
- `NaN` and `Infinity` are considered numbers.
+ ``NaN`` and ``Infinity`` are considered numbers.
"""
- return FunctionExpression("isNumber", args)
+ return FunctionExpression("isNumber", (value,))
+
+ @classmethod
+ def isObject(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is an object (including arrays and Dates), false otherwise."""
+ return FunctionExpression("isObject", (value,))
@classmethod
- def isObject(cls, *args) -> FunctionExpression:
- """Returns true if *value* is an object (including arrays and Dates), false otherwise."""
- return FunctionExpression("isObject", args)
+ def isRegExp(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is a RegExp (regular expression) object, false otherwise."""
+ return FunctionExpression("isRegExp", (value,))
@classmethod
- def isRegExp(cls, *args) -> FunctionExpression:
- """Returns true if *value* is a RegExp (regular expression) object, false otherwise."""
- return FunctionExpression("isRegExp", args)
+ def isString(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is a string, false otherwise."""
+ return FunctionExpression("isString", (value,))
@classmethod
- def isString(cls, *args) -> FunctionExpression:
- """Returns true if *value* is a string, false otherwise."""
- return FunctionExpression("isString", args)
+ def isValid(cls, value: IntoExpression, /) -> Expression:
+ """Returns true if ``value`` is not ``null``, ``undefined``, or ``NaN``, false otherwise."""
+ return FunctionExpression("isValid", (value,))
@classmethod
- def isValid(cls, *args) -> FunctionExpression:
- """Returns true if *value* is not `null`, `undefined`, or `NaN`, false otherwise."""
- return FunctionExpression("isValid", args)
+ def toBoolean(cls, value: IntoExpression, /) -> Expression:
+ """
+ Coerces the input ``value`` to a string.
+
+ Null values and empty strings are mapped to ``null``.
+ """
+ return FunctionExpression("toBoolean", (value,))
@classmethod
- def toBoolean(cls, *args) -> FunctionExpression:
+ def toDate(cls, value: IntoExpression, /) -> Expression:
"""
- Coerces the input *value* to a string.
+ Coerces the input ``value`` to a Date instance.
- Null values and empty strings are mapped to `null`.
+ Null values and empty strings are mapped to ``null``. If an optional *parser* function is
+ provided, it is used to perform date parsing, otherwise ``Date.parse`` is used. Be aware
+ that ``Date.parse`` has different implementations across browsers!
"""
- return FunctionExpression("toBoolean", args)
+ return FunctionExpression("toDate", (value,))
@classmethod
- def toDate(cls, *args) -> FunctionExpression:
+ def toNumber(cls, value: IntoExpression, /) -> Expression:
"""
- Coerces the input *value* to a Date instance.
+ Coerces the input ``value`` to a number.
- Null values and empty strings are mapped to `null`.
- If an optional *parser* function is provided, it is used to perform date parsing, otherwise `Date.parse` is used.
- Be aware that `Date.parse` has different implementations across browsers!
+ Null values and empty strings are mapped to ``null``.
"""
- return FunctionExpression("toDate", args)
+ return FunctionExpression("toNumber", (value,))
@classmethod
- def toNumber(cls, *args) -> FunctionExpression:
+ def toString(cls, value: IntoExpression, /) -> Expression:
"""
- Coerces the input *value* to a number.
+ Coerces the input ``value`` to a string.
- Null values and empty strings are mapped to `null`.
+ Null values and empty strings are mapped to ``null``.
"""
- return FunctionExpression("toNumber", args)
+ return FunctionExpression("toString", (value,))
@classmethod
- def toString(cls, *args) -> FunctionExpression:
+ def if_(
+ cls,
+ test: IntoExpression,
+ thenValue: IntoExpression,
+ elseValue: IntoExpression,
+ /,
+ ) -> Expression:
"""
- Coerces the input *value* to a string.
+ If ``test`` is truthy, returns ``thenValue``.
- Null values and empty strings are mapped to `null`.
+ Otherwise, returns ``elseValue``. The *if* function is equivalent to the ternary operator
+ ``a ? b : c``.
"""
- return FunctionExpression("toString", args)
+ return FunctionExpression("if", (test, thenValue, elseValue))
@classmethod
- def isNaN(cls, *args) -> FunctionExpression:
+ def isNaN(cls, value: IntoExpression, /) -> Expression:
"""
- Returns true if *value* is not a number.
+ Returns true if ``value`` is not a number.
- Same as JavaScript's `isNaN`.
+ Same as JavaScript's `Number.isNaN`_.
+
+ .. _Number.isNaN:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNan
"""
- return FunctionExpression("isNaN", args)
+ return FunctionExpression("isNaN", (value,))
@classmethod
- def isFinite(cls, *args) -> FunctionExpression:
+ def isFinite(cls, value: IntoExpression, /) -> Expression:
"""
- Returns true if *value* is a finite number.
+ Returns true if ``value`` is a finite number.
+
+ Same as JavaScript's `Number.isFinite`_.
- Same as JavaScript's `isFinite`.
+ .. _Number.isFinite:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite
"""
- return FunctionExpression("isFinite", args)
+ return FunctionExpression("isFinite", (value,))
@classmethod
- def abs(cls, *args) -> FunctionExpression:
+ def abs(cls, value: IntoExpression, /) -> Expression:
"""
- Returns the absolute value of *value*.
+ Returns the absolute value of ``value``.
- Same as JavaScript's `Math.abs`.
+ Same as JavaScript's `Math.abs`_.
+
+ .. _Math.abs:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs
"""
- return FunctionExpression("abs", args)
+ return FunctionExpression("abs", (value,))
@classmethod
- def acos(cls, *args) -> FunctionExpression:
+ def acos(cls, value: IntoExpression, /) -> Expression:
"""
Trigonometric arccosine.
- Same as JavaScript's `Math.acos`.
+ Same as JavaScript's `Math.acos`_.
+
+ .. _Math.acos:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos
"""
- return FunctionExpression("acos", args)
+ return FunctionExpression("acos", (value,))
@classmethod
- def asin(cls, *args) -> FunctionExpression:
+ def asin(cls, value: IntoExpression, /) -> Expression:
"""
Trigonometric arcsine.
- Same as JavaScript's `Math.asin`.
+ Same as JavaScript's `Math.asin`_.
+
+ .. _Math.asin:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin
"""
- return FunctionExpression("asin", args)
+ return FunctionExpression("asin", (value,))
@classmethod
- def atan(cls, *args) -> FunctionExpression:
+ def atan(cls, value: IntoExpression, /) -> Expression:
"""
Trigonometric arctangent.
- Same as JavaScript's `Math.atan`.
+ Same as JavaScript's `Math.atan`_.
+
+ .. _Math.atan:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan
"""
- return FunctionExpression("atan", args)
+ return FunctionExpression("atan", (value,))
@classmethod
- def atan2(cls, *args) -> FunctionExpression:
+ def atan2(cls, dy: IntoExpression, dx: IntoExpression, /) -> Expression:
"""
Returns the arctangent of *dy / dx*.
- Same as JavaScript's `Math.atan2`.
+ Same as JavaScript's `Math.atan2`_.
+
+ .. _Math.atan2:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2
"""
- return FunctionExpression("atan2", args)
+ return FunctionExpression("atan2", (dy, dx))
@classmethod
- def ceil(cls, *args) -> FunctionExpression:
+ def ceil(cls, value: IntoExpression, /) -> Expression:
"""
- Rounds *value* to the nearest integer of equal or greater value.
+ Rounds ``value`` to the nearest integer of equal or greater value.
+
+ Same as JavaScript's `Math.ceil`_.
- Same as JavaScript's `Math.ceil`.
+ .. _Math.ceil:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil
"""
- return FunctionExpression("ceil", args)
+ return FunctionExpression("ceil", (value,))
@classmethod
- def clamp(cls, *args) -> FunctionExpression:
- """Restricts *value* to be between the specified *min* and *max*."""
- return FunctionExpression("clamp", args)
+ def clamp(
+ cls, value: IntoExpression, min: IntoExpression, max: IntoExpression, /
+ ) -> Expression:
+ """Restricts ``value`` to be between the specified ``min`` and ``max``."""
+ return FunctionExpression("clamp", (value, min, max))
@classmethod
- def cos(cls, *args) -> FunctionExpression:
+ def cos(cls, value: IntoExpression, /) -> Expression:
"""
Trigonometric cosine.
- Same as JavaScript's `Math.cos`.
+ Same as JavaScript's `Math.cos`_.
+
+ .. _Math.cos:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos
"""
- return FunctionExpression("cos", args)
+ return FunctionExpression("cos", (value,))
@classmethod
- def exp(cls, *args) -> FunctionExpression:
+ def exp(cls, exponent: IntoExpression, /) -> Expression:
"""
- Returns the value of *e* raised to the provided *exponent*.
+ Returns the value of *e* raised to the provided ``exponent``.
+
+ Same as JavaScript's `Math.exp`_.
- Same as JavaScript's `Math.exp`.
+ .. _Math.exp:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp
"""
- return FunctionExpression("exp", args)
+ return FunctionExpression("exp", (exponent,))
@classmethod
- def floor(cls, *args) -> FunctionExpression:
+ def floor(cls, value: IntoExpression, /) -> Expression:
"""
- Rounds *value* to the nearest integer of equal or lower value.
+ Rounds ``value`` to the nearest integer of equal or lower value.
- Same as JavaScript's `Math.floor`.
+ Same as JavaScript's `Math.floor`_.
+
+ .. _Math.floor:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor
"""
- return FunctionExpression("floor", args)
+ return FunctionExpression("floor", (value,))
@classmethod
- def hypot(cls, *args) -> FunctionExpression:
+ def hypot(cls, value: IntoExpression, /) -> Expression:
"""
Returns the square root of the sum of squares of its arguments.
- Same as JavaScript's `Math.hypot`.
+ Same as JavaScript's `Math.hypot`_.
+
+ .. _Math.hypot:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot
"""
- return FunctionExpression("hypot", args)
+ return FunctionExpression("hypot", (value,))
@classmethod
- def log(cls, *args) -> FunctionExpression:
+ def log(cls, value: IntoExpression, /) -> Expression:
"""
- Returns the natural logarithm of *value*.
+ Returns the natural logarithm of ``value``.
- Same as JavaScript's `Math.log`.
+ Same as JavaScript's `Math.log`_.
+
+ .. _Math.log:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log
"""
- return FunctionExpression("log", args)
+ return FunctionExpression("log", (value,))
@classmethod
- def max(cls, *args) -> FunctionExpression:
+ def max(
+ cls, value1: IntoExpression, value2: IntoExpression, *args: Any
+ ) -> Expression:
"""
Returns the maximum argument value.
- Same as JavaScript's `Math.max`.
+ Same as JavaScript's `Math.max`_.
+
+ .. _Math.max:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max
"""
- return FunctionExpression("max", args)
+ return FunctionExpression("max", (value1, value2, *args))
@classmethod
- def min(cls, *args) -> FunctionExpression:
+ def min(
+ cls, value1: IntoExpression, value2: IntoExpression, *args: Any
+ ) -> Expression:
"""
Returns the minimum argument value.
- Same as JavaScript's `Math.min`.
- """
- return FunctionExpression("min", args)
-
- @classmethod
- def pow(cls, *args) -> FunctionExpression:
- """
- Returns *value* raised to the given *exponent*.
+ Same as JavaScript's `Math.min`_.
- Same as JavaScript's `Math.pow`.
+ .. _Math.min:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min
"""
- return FunctionExpression("pow", args)
+ return FunctionExpression("min", (value1, value2, *args))
@classmethod
- def random(cls, *args) -> FunctionExpression:
+ def pow(cls, value: IntoExpression, exponent: IntoExpression, /) -> Expression:
"""
- Returns a pseudo-random number in the range `[0, 1]`.
+ Returns ``value`` raised to the given ``exponent``.
- Same as JavaScript's `Math.random`.
- """
- return FunctionExpression("random", args)
+ Same as JavaScript's `Math.pow`_.
- @classmethod
- def round(cls, *args) -> FunctionExpression:
+ .. _Math.pow:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow
"""
- Rounds *value* to the nearest integer.
-
- Same as JavaScript's `Math.round`.
- """
- return FunctionExpression("round", args)
+ return FunctionExpression("pow", (value, exponent))
@classmethod
- def sin(cls, *args) -> FunctionExpression:
- """
- Trigonometric sine.
-
- Same as JavaScript's `Math.sin`.
+ def round(cls, value: IntoExpression, /) -> Expression:
"""
- return FunctionExpression("sin", args)
+ Rounds ``value`` to the nearest integer.
- @classmethod
- def sqrt(cls, *args) -> FunctionExpression:
- """
- Square root function.
+ Same as JavaScript's `Math.round`_.
- Same as JavaScript's `Math.sqrt`.
+ .. _Math.round:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
"""
- return FunctionExpression("sqrt", args)
+ return FunctionExpression("round", (value,))
@classmethod
- def tan(cls, *args) -> FunctionExpression:
+ def sin(cls, value: IntoExpression, /) -> Expression:
"""
- Trigonometric tangent.
-
- Same as JavaScript's `Math.tan`.
- """
- return FunctionExpression("tan", args)
+ Trigonometric sine.
- @classmethod
- def sampleNormal(cls, *args) -> FunctionExpression:
- """
- Returns a sample from a univariate `normal (Gaussian) probability distribution `__ with specified *mean* and standard deviation *stdev*.
+ Same as JavaScript's `Math.sin`_.
- If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.
+ .. _Math.sin:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin
"""
- return FunctionExpression("sampleNormal", args)
+ return FunctionExpression("sin", (value,))
@classmethod
- def cumulativeNormal(cls, *args) -> FunctionExpression:
- """
- Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a normal distribution with specified *mean* and standard deviation *stdev*.
-
- If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.
+ def sqrt(cls, value: IntoExpression, /) -> Expression:
"""
- return FunctionExpression("cumulativeNormal", args)
+ Square root function.
- @classmethod
- def densityNormal(cls, *args) -> FunctionExpression:
- """
- Returns the value of the `probability density function `__ at the given input domain *value*, for a normal distribution with specified *mean* and standard deviation *stdev*.
+ Same as JavaScript's `Math.sqrt`_.
- If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.
+ .. _Math.sqrt:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt
"""
- return FunctionExpression("densityNormal", args)
+ return FunctionExpression("sqrt", (value,))
@classmethod
- def quantileNormal(cls, *args) -> FunctionExpression:
- """
- Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a normal distribution with specified *mean* and standard deviation *stdev*.
-
- If unspecified, the mean defaults to `0` and the standard deviation defaults to `1`.
+ def tan(cls, value: IntoExpression, /) -> Expression:
"""
- return FunctionExpression("quantileNormal", args)
+ Trigonometric tangent.
- @classmethod
- def sampleLogNormal(cls, *args) -> FunctionExpression:
- """
- Returns a sample from a univariate `log-normal probability distribution `__ with specified log *mean* and log standard deviation *stdev*.
+ Same as JavaScript's `Math.tan`_.
- If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.
+ .. _Math.tan:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan
"""
- return FunctionExpression("sampleLogNormal", args)
+ return FunctionExpression("tan", (value,))
@classmethod
- def cumulativeLogNormal(cls, *args) -> FunctionExpression:
+ def sampleNormal(
+ cls, mean: IntoExpression = None, stdev: IntoExpression = None, /
+ ) -> Expression:
"""
- Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a log-normal distribution with specified log *mean* and log standard deviation *stdev*.
+ Returns a sample from a univariate `normal (Gaussian) probability distribution`_ with specified ``mean`` and standard deviation ``stdev``.
- If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.
- """
- return FunctionExpression("cumulativeLogNormal", args)
-
- @classmethod
- def densityLogNormal(cls, *args) -> FunctionExpression:
- """
- Returns the value of the `probability density function `__ at the given input domain *value*, for a log-normal distribution with specified log *mean* and log standard deviation *stdev*.
+ If unspecified, the mean defaults to ``0`` and the standard deviation defaults to ``1``.
- If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.
+ .. _normal (Gaussian) probability distribution:
+ https://en.wikipedia.org/wiki/Normal_distribution
"""
- return FunctionExpression("densityLogNormal", args)
+ return FunctionExpression("sampleNormal", (mean, stdev))
@classmethod
- def quantileLogNormal(cls, *args) -> FunctionExpression:
+ def sampleLogNormal(
+ cls, mean: IntoExpression = None, stdev: IntoExpression = None, /
+ ) -> Expression:
"""
- Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a log-normal distribution with specified log *mean* and log standard deviation *stdev*.
+ Returns a sample from a univariate `log-normal probability distribution`_ with specified log ``mean`` and log standard deviation ``stdev``.
- If unspecified, the log mean defaults to `0` and the log standard deviation defaults to `1`.
- """
- return FunctionExpression("quantileLogNormal", args)
+ If unspecified, the log mean defaults to ``0`` and the log standard deviation defaults to
+ ``1``.
- @classmethod
- def sampleUniform(cls, *args) -> FunctionExpression:
+ .. _log-normal probability distribution:
+ https://en.wikipedia.org/wiki/Log-normal_distribution
"""
- Returns a sample from a univariate `continuous uniform probability distribution `__ over the interval `[min, max]`.
-
- If unspecified, *min* defaults to `0` and *max* defaults to `1`.
- If only one argument is provided, it is interpreted as the *max* value.
- """
- return FunctionExpression("sampleUniform", args)
-
- @classmethod
- def cumulativeUniform(cls, *args) -> FunctionExpression:
- """
- Returns the value of the `cumulative distribution function `__ at the given input domain *value* for a uniform distribution over the interval `[min, max]`.
-
- If unspecified, *min* defaults to `0` and *max* defaults to `1`.
- If only one argument is provided, it is interpreted as the *max* value.
- """
- return FunctionExpression("cumulativeUniform", args)
+ return FunctionExpression("sampleLogNormal", (mean, stdev))
@classmethod
- def densityUniform(cls, *args) -> FunctionExpression:
- """
- Returns the value of the `probability density function `__ at the given input domain *value*, for a uniform distribution over the interval `[min, max]`.
-
- If unspecified, *min* defaults to `0` and *max* defaults to `1`.
- If only one argument is provided, it is interpreted as the *max* value.
+ def sampleUniform(
+ cls, min: IntoExpression = None, max: IntoExpression = None, /
+ ) -> Expression:
"""
- return FunctionExpression("densityUniform", args)
+ Returns a sample from a univariate `continuous uniform probability distribution`_) over the interval [``min``, ``max``).
- @classmethod
- def quantileUniform(cls, *args) -> FunctionExpression:
- """
- Returns the quantile value (the inverse of the `cumulative distribution function `__ for the given input *probability*, for a uniform distribution over the interval `[min, max]`.
+ If unspecified, ``min`` defaults to ``0`` and ``max`` defaults to ``1``. If only one
+ argument is provided, it is interpreted as the ``max`` value.
- If unspecified, *min* defaults to `0` and *max* defaults to `1`.
- If only one argument is provided, it is interpreted as the *max* value.
+ .. _continuous uniform probability distribution:
+ https://en.wikipedia.org/wiki/Uniform_distribution_(continuous
"""
- return FunctionExpression("quantileUniform", args)
-
- @classmethod
- def now(cls, *args) -> FunctionExpression:
- """Returns the timestamp for the current time."""
- return FunctionExpression("now", args)
+ return FunctionExpression("sampleUniform", (min, max))
@classmethod
- def datetime(cls, *args) -> FunctionExpression:
+ def datetime(
+ cls,
+ year: IntoExpression,
+ month: IntoExpression,
+ day: IntoExpression = None,
+ hour: IntoExpression = None,
+ min: IntoExpression = None,
+ sec: IntoExpression = None,
+ millisec: IntoExpression = None,
+ /,
+ ) -> Expression:
"""
- Returns a new `Date` instance.
+ Returns a new ``Date`` instance.
- The *month* is 0-based, such that `1` represents February.
+ The ``month`` is 0-based, such that ``1`` represents February.
"""
- return FunctionExpression("datetime", args)
+ return FunctionExpression(
+ "datetime", (year, month, day, hour, min, sec, millisec)
+ )
@classmethod
- def date(cls, *args) -> FunctionExpression:
- """Returns the day of the month for the given *datetime* value, in local time."""
- return FunctionExpression("date", args)
+ def date(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the day of the month for the given ``datetime`` value, in local time."""
+ return FunctionExpression("date", (datetime,))
@classmethod
- def day(cls, *args) -> FunctionExpression:
- """Returns the day of the week for the given *datetime* value, in local time."""
- return FunctionExpression("day", args)
+ def day(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the day of the week for the given ``datetime`` value, in local time."""
+ return FunctionExpression("day", (datetime,))
@classmethod
- def dayofyear(cls, *args) -> FunctionExpression:
- """Returns the one-based day of the year for the given *datetime* value, in local time."""
- return FunctionExpression("dayofyear", args)
+ def dayofyear(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the one-based day of the year for the given ``datetime`` value, in local time."""
+ return FunctionExpression("dayofyear", (datetime,))
@classmethod
- def year(cls, *args) -> FunctionExpression:
- """Returns the year for the given *datetime* value, in local time."""
- return FunctionExpression("year", args)
+ def year(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the year for the given ``datetime`` value, in local time."""
+ return FunctionExpression("year", (datetime,))
@classmethod
- def quarter(cls, *args) -> FunctionExpression:
- """Returns the quarter of the year (0-3) for the given *datetime* value, in local time."""
- return FunctionExpression("quarter", args)
+ def quarter(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the quarter of the year (0-3) for the given ``datetime`` value, in local time."""
+ return FunctionExpression("quarter", (datetime,))
@classmethod
- def month(cls, *args) -> FunctionExpression:
- """Returns the (zero-based) month for the given *datetime* value, in local time."""
- return FunctionExpression("month", args)
+ def month(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the (zero-based) month for the given ``datetime`` value, in local time."""
+ return FunctionExpression("month", (datetime,))
@classmethod
- def week(cls, *args) -> FunctionExpression:
+ def week(cls, date: IntoExpression, /) -> Expression:
"""
Returns the week number of the year for the given *datetime*, in local time.
- This function assumes Sunday-based weeks.
- Days before the first Sunday of the year are considered to be in week 0,
- the first Sunday of the year is the start of week 1,
- the second Sunday week 2, etc.
+ This function assumes Sunday-based weeks. Days before the first Sunday of the year are
+ considered to be in week 0, the first Sunday of the year is the start of week 1, the second
+ Sunday week 2, *etc.*.
"""
- return FunctionExpression("week", args)
+ return FunctionExpression("week", (date,))
@classmethod
- def hours(cls, *args) -> FunctionExpression:
- """Returns the hours component for the given *datetime* value, in local time."""
- return FunctionExpression("hours", args)
+ def hours(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the hours component for the given ``datetime`` value, in local time."""
+ return FunctionExpression("hours", (datetime,))
@classmethod
- def minutes(cls, *args) -> FunctionExpression:
- """Returns the minutes component for the given *datetime* value, in local time."""
- return FunctionExpression("minutes", args)
+ def minutes(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the minutes component for the given ``datetime`` value, in local time."""
+ return FunctionExpression("minutes", (datetime,))
@classmethod
- def seconds(cls, *args) -> FunctionExpression:
- """Returns the seconds component for the given *datetime* value, in local time."""
- return FunctionExpression("seconds", args)
+ def seconds(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the seconds component for the given ``datetime`` value, in local time."""
+ return FunctionExpression("seconds", (datetime,))
@classmethod
- def milliseconds(cls, *args) -> FunctionExpression:
- """Returns the milliseconds component for the given *datetime* value, in local time."""
- return FunctionExpression("milliseconds", args)
+ def milliseconds(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the milliseconds component for the given ``datetime`` value, in local time."""
+ return FunctionExpression("milliseconds", (datetime,))
@classmethod
- def time(cls, *args) -> FunctionExpression:
- """Returns the epoch-based timestamp for the given *datetime* value."""
- return FunctionExpression("time", args)
+ def time(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the epoch-based timestamp for the given ``datetime`` value."""
+ return FunctionExpression("time", (datetime,))
@classmethod
- def timezoneoffset(cls, *args) -> FunctionExpression:
- """Returns the timezone offset from the local timezone to UTC for the given *datetime* value."""
- return FunctionExpression("timezoneoffset", args)
+ def timezoneoffset(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the timezone offset from the local timezone to UTC for the given ``datetime`` value."""
+ return FunctionExpression("timezoneoffset", (datetime,))
@classmethod
- def timeOffset(cls, *args) -> FunctionExpression:
+ def timeOffset(
+ cls, unit: IntoExpression, date: IntoExpression, step: IntoExpression = None, /
+ ) -> Expression:
"""
- Returns a new `Date` instance that offsets the given *date* by the specified time `unit `__ in the local timezone.
+ Returns a new ``Date`` instance that offsets the given ``date`` by the specified time `*unit*`_ in the local timezone.
- The optional *step* argument indicates the number of time unit steps to offset by (default 1).
+ The optional ``step`` argument indicates the number of time unit steps to offset by (default
+ 1).
+
+ .. _*unit*:
+ https://vega.github.io/vega/docs/api/time/#time-units
"""
- return FunctionExpression("timeOffset", args)
+ return FunctionExpression("timeOffset", (unit, date, step))
@classmethod
- def timeSequence(cls, *args) -> FunctionExpression:
+ def timeSequence(
+ cls,
+ unit: IntoExpression,
+ start: IntoExpression,
+ stop: IntoExpression,
+ step: IntoExpression = None,
+ /,
+ ) -> Expression:
"""
- Returns an array of `Date` instances from *start* (inclusive) to *stop* (exclusive), with each entry separated by the given time `unit `__ in the local timezone.
+ Returns an array of ``Date`` instances from ``start`` (inclusive) to ``stop`` (exclusive), with each entry separated by the given time `*unit*`_ in the local timezone.
+
+ The optional ``step`` argument indicates the number of time unit steps to take between each
+ sequence entry (default 1).
- The optional *step* argument indicates the number of time unit steps to take between each sequence entry (default 1).
+ .. _*unit*:
+ https://vega.github.io/vega/docs/api/time/#time-units
"""
- return FunctionExpression("timeSequence", args)
+ return FunctionExpression("timeSequence", (unit, start, stop, step))
@classmethod
- def utc(cls, *args) -> FunctionExpression:
- """Returns a timestamp for the given UTC date. The *month* is 0-based, such that `1` represents February."""
- return FunctionExpression("utc", args)
+ def utc(
+ cls,
+ year: IntoExpression,
+ month: IntoExpression,
+ day: IntoExpression = None,
+ hour: IntoExpression = None,
+ min: IntoExpression = None,
+ sec: IntoExpression = None,
+ millisec: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns a timestamp for the given UTC date.
+
+ The ``month`` is 0-based, such that ``1`` represents February.
+ """
+ return FunctionExpression("utc", (year, month, day, hour, min, sec, millisec))
@classmethod
- def utcdate(cls, *args) -> FunctionExpression:
- """Returns the day of the month for the given *datetime* value, in UTC time."""
- return FunctionExpression("utcdate", args)
+ def utcdate(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the day of the month for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcdate", (datetime,))
@classmethod
- def utcday(cls, *args) -> FunctionExpression:
- """Returns the day of the week for the given *datetime* value, in UTC time."""
- return FunctionExpression("utcday", args)
+ def utcday(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the day of the week for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcday", (datetime,))
@classmethod
- def utcdayofyear(cls, *args) -> FunctionExpression:
- """Returns the one-based day of the year for the given *datetime* value, in UTC time."""
- return FunctionExpression("utcdayofyear", args)
+ def utcdayofyear(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the one-based day of the year for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcdayofyear", (datetime,))
@classmethod
- def utcyear(cls, *args) -> FunctionExpression:
- """Returns the year for the given *datetime* value, in UTC time."""
- return FunctionExpression("utcyear", args)
+ def utcyear(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the year for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcyear", (datetime,))
@classmethod
- def utcquarter(cls, *args) -> FunctionExpression:
- """Returns the quarter of the year (0-3) for the given *datetime* value, in UTC time."""
- return FunctionExpression("utcquarter", args)
+ def utcquarter(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the quarter of the year (0-3) for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcquarter", (datetime,))
@classmethod
- def utcmonth(cls, *args) -> FunctionExpression:
- """Returns the (zero-based) month for the given *datetime* value, in UTC time."""
- return FunctionExpression("utcmonth", args)
+ def utcmonth(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the (zero-based) month for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcmonth", (datetime,))
@classmethod
- def utcweek(cls, *args) -> FunctionExpression:
+ def utcweek(cls, date: IntoExpression, /) -> Expression:
"""
Returns the week number of the year for the given *datetime*, in UTC time.
- This function assumes Sunday-based weeks.
- Days before the first Sunday of the year are considered to be in week 0,
- the first Sunday of the year is the start of week 1,
- the second Sunday week 2, etc.
+ This function assumes Sunday-based weeks. Days before the first Sunday of the year are
+ considered to be in week 0, the first Sunday of the year is the start of week 1, the second
+ Sunday week 2, *etc.*.
"""
- return FunctionExpression("utcweek", args)
+ return FunctionExpression("utcweek", (date,))
@classmethod
- def utchours(cls, *args) -> FunctionExpression:
- """Returns the hours component for the given *datetime* value, in UTC time."""
- return FunctionExpression("utchours", args)
+ def utchours(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the hours component for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utchours", (datetime,))
@classmethod
- def utcminutes(cls, *args) -> FunctionExpression:
- """Returns the minutes component for the given *datetime* value, in UTC time."""
- return FunctionExpression("utcminutes", args)
+ def utcminutes(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the minutes component for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcminutes", (datetime,))
@classmethod
- def utcseconds(cls, *args) -> FunctionExpression:
- """Returns the seconds component for the given *datetime* value, in UTC time."""
- return FunctionExpression("utcseconds", args)
+ def utcseconds(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the seconds component for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcseconds", (datetime,))
@classmethod
- def utcmilliseconds(cls, *args) -> FunctionExpression:
- """Returns the milliseconds component for the given *datetime* value, in UTC time."""
- return FunctionExpression("utcmilliseconds", args)
+ def utcmilliseconds(cls, datetime: IntoExpression, /) -> Expression:
+ """Returns the milliseconds component for the given ``datetime`` value, in UTC time."""
+ return FunctionExpression("utcmilliseconds", (datetime,))
@classmethod
- def utcOffset(cls, *args) -> FunctionExpression:
+ def utcOffset(
+ cls, unit: IntoExpression, date: IntoExpression, step: IntoExpression = None, /
+ ) -> Expression:
"""
- Returns a new `Date` instance that offsets the given *date* by the specified time `unit `__ in UTC time.
+ Returns a new ``Date`` instance that offsets the given ``date`` by the specified time `*unit*`_ in UTC time.
+
+ The optional ``step`` argument indicates the number of time unit steps to offset by (default
+ 1).
- The optional *step* argument indicates the number of time unit steps to offset by (default 1).
+ .. _*unit*:
+ https://vega.github.io/vega/docs/api/time/#time-units
"""
- return FunctionExpression("utcOffset", args)
+ return FunctionExpression("utcOffset", (unit, date, step))
@classmethod
- def utcSequence(cls, *args) -> FunctionExpression:
+ def utcSequence(
+ cls,
+ unit: IntoExpression,
+ start: IntoExpression,
+ stop: IntoExpression,
+ step: IntoExpression = None,
+ /,
+ ) -> Expression:
"""
- Returns an array of `Date` instances from *start* (inclusive) to *stop* (exclusive), with each entry separated by the given time `unit `__ in UTC time.
+ Returns an array of ``Date`` instances from ``start`` (inclusive) to ``stop`` (exclusive), with each entry separated by the given time `*unit*`_ in UTC time.
- The optional *step* argument indicates the number of time unit steps to take between each sequence entry (default 1).
+ The optional ``step`` argument indicates the number of time unit steps to take between each
+ sequence entry (default 1).
+
+ .. _*unit*:
+ https://vega.github.io/vega/docs/api/time/#time-units
"""
- return FunctionExpression("utcSequence", args)
+ return FunctionExpression("utcSequence", (unit, start, stop, step))
@classmethod
- def extent(cls, *args) -> FunctionExpression:
- """Returns a new `[min, max]` array with the minimum and maximum values of the input array, ignoring `null`, `undefined`, and `NaN` values."""
- return FunctionExpression("extent", args)
+ def extent(cls, array: IntoExpression, /) -> Expression:
+ """Returns a new *[min, max]* array with the minimum and maximum values of the input array, ignoring ``null``, ``undefined``, and ``NaN`` values."""
+ return FunctionExpression("extent", (array,))
@classmethod
- def clampRange(cls, *args) -> FunctionExpression:
+ def clampRange(
+ cls, range: IntoExpression, min: IntoExpression, max: IntoExpression, /
+ ) -> Expression:
"""
- Clamps a two-element *range* array in a span-preserving manner.
+ Clamps a two-element ``range`` array in a span-preserving manner.
- If the span of the input *range* is less than `(max - min)` and an endpoint exceeds either the *min* or *max* value,
- the range is translated such that the span is preserved and one endpoint touches the boundary of the `[min, max]` range.
- If the span exceeds `(max - min)`, the range `[min, max]` is returned.
+ If the span of the input ``range`` is less than *(max - min)* and an endpoint exceeds either
+ the ``min`` or ``max`` value, the range is translated such that the span is preserved and
+ one endpoint touches the boundary of the *[min, max]* range. If the span exceeds *(max -
+ min)*, the range *[min, max]* is returned.
"""
- return FunctionExpression("clampRange", args)
+ return FunctionExpression("clampRange", (range, min, max))
@classmethod
- def indexof(cls, *args) -> FunctionExpression:
- """Returns the first index of *value* in the input *array*, or the first index of *substring* in the input *string*."""
- return FunctionExpression("indexof", args)
+ def indexof(cls, array: IntoExpression, value: IntoExpression, /) -> Expression:
+ """Returns the first index of ``value`` in the input ``array``."""
+ return FunctionExpression("indexof", (array, value))
@classmethod
- def inrange(cls, *args) -> FunctionExpression:
- """Tests whether *value* lies within (or is equal to either) the first and last values of the *range* array."""
- return FunctionExpression("inrange", args)
+ def inrange(cls, value: IntoExpression, range: IntoExpression, /) -> Expression:
+ """Tests whether ``value`` lies within (or is equal to either) the first and last values of the ``range`` array."""
+ return FunctionExpression("inrange", (value, range))
@classmethod
- def join(cls, *args) -> FunctionExpression:
- """Returns a new string by concatenating all of the elements of the input *array*, separated by commas or a specified *separator* string."""
- return FunctionExpression("join", args)
+ def join(
+ cls, array: IntoExpression, separator: IntoExpression = None, /
+ ) -> Expression:
+ """Returns a new string by concatenating all of the elements of the input ``array``, separated by commas or a specified ``separator`` string."""
+ return FunctionExpression("join", (array, separator))
@classmethod
- def lastindexof(cls, *args) -> FunctionExpression:
- """Returns the last index of *value* in the input *array*, or the last index of *substring* in the input *string*."""
- return FunctionExpression("lastindexof", args)
+ def lastindexof(cls, array: IntoExpression, value: IntoExpression, /) -> Expression:
+ """Returns the last index of ``value`` in the input ``array``."""
+ return FunctionExpression("lastindexof", (array, value))
@classmethod
- def length(cls, *args) -> FunctionExpression:
- """Returns the length of the input *array*, or the length of the input *string*."""
- return FunctionExpression("length", args)
+ def length(cls, array: IntoExpression, /) -> Expression:
+ """Returns the length of the input ``array``."""
+ return FunctionExpression("length", (array,))
@classmethod
- def lerp(cls, *args) -> FunctionExpression:
+ def lerp(cls, array: IntoExpression, fraction: IntoExpression, /) -> Expression:
"""
- Returns the linearly interpolated value between the first and last entries in the *array* for the provided interpolation *fraction* (typically between 0 and 1).
+ Returns the linearly interpolated value between the first and last entries in the ``array`` for the provided interpolation ``fraction`` (typically between 0 and 1).
- For example, `lerp([0, 50], 0.5)` returns 25.
+ For example, ``alt.expr.lerp([0, 50], 0.5)`` returns 25.
"""
- return FunctionExpression("lerp", args)
+ return FunctionExpression("lerp", (array, fraction))
@classmethod
- def peek(cls, *args) -> FunctionExpression:
+ def peek(cls, array: IntoExpression, /) -> Expression:
"""
- Returns the last element in the input *array*.
+ Returns the last element in the input ``array``.
- Similar to the built-in `Array.pop` method, except that it does not remove the last element.
- This method is a convenient shorthand for `array[array.length - 1]`.
+ Similar to the built-in ``Array.pop`` method, except that it does not remove the last
+ element. This method is a convenient shorthand for ``array[array.length - 1]``.
"""
- return FunctionExpression("peek", args)
+ return FunctionExpression("peek", (array,))
@classmethod
- def pluck(cls, *args) -> FunctionExpression:
+ def pluck(cls, array: IntoExpression, field: IntoExpression, /) -> Expression:
"""
- Retrieves the value for the specified *field* from a given *array* of objects.
+ Retrieves the value for the specified ``field`` from a given ``array`` of objects.
- The input *field* string may include nested properties (e.g., `foo.bar.bz`).
+ The input ``field`` string may include nested properties (e.g., ``foo.bar.bz``).
"""
- return FunctionExpression("pluck", args)
+ return FunctionExpression("pluck", (array, field))
@classmethod
- def reverse(cls, *args) -> FunctionExpression:
+ def reverse(cls, array: IntoExpression, /) -> Expression:
"""
- Returns a new array with elements in a reverse order of the input *array*.
+ Returns a new array with elements in a reverse order of the input ``array``.
The first array element becomes the last, and the last array element becomes the first.
"""
- return FunctionExpression("reverse", args)
+ return FunctionExpression("reverse", (array,))
@classmethod
- def sequence(cls, *args) -> FunctionExpression:
- r"""
+ def sequence(cls, *args: Any) -> Expression:
+ """
Returns an array containing an arithmetic sequence of numbers.
- If *step* is omitted, it defaults to 1.
- If *start* is omitted, it defaults to 0.
-
- The *stop* value is exclusive; it is not included in the result.
- If *step* is positive, the last element is the largest `start + i * step` less than *stop*;
- if *step* is negative, the last element is the smallest `start + i * step` greater than *stop*.
-
- If the returned array would contain an infinite number of values, an empty range is returned.
- The arguments are not required to be integers.
+ If ``step`` is omitted, it defaults to 1. If ``start`` is omitted, it defaults to 0. The
+ ``stop`` value is exclusive; it is not included in the result. If ``step`` is positive, the
+ last element is the largest *start + i * step* less than ``stop``; if ``step`` is negative,
+ the last element is the smallest *start + i * step* greater than ``stop``. If the returned
+ array would contain an infinite number of values, an empty range is returned. The arguments
+ are not required to be integers.
"""
return FunctionExpression("sequence", args)
@classmethod
- def slice(cls, *args) -> FunctionExpression:
+ def slice(
+ cls, array: IntoExpression, start: IntoExpression, end: IntoExpression = None, /
+ ) -> Expression:
"""
- Returns a section of *array* between the *start* and *end* indices.
+ Returns a section of ``array`` between the ``start`` and ``end`` indices.
- If the *end* argument is negative, it is treated as an offset from the end of the array `length(array) + end`.
+ If the ``end`` argument is negative, it is treated as an offset from the end of the array
+ (*alt.expr.length(array) + end*).
"""
- return FunctionExpression("slice", args)
+ return FunctionExpression("slice", (array, start, end))
@classmethod
- def span(cls, *args) -> FunctionExpression:
- """
- Returns the span of *array*: the difference between the last and first elements, or `array[array.length-1] - array[0]`.
-
- Or if input is a string: a section of *string* between the *start* and *end* indices.
- If the *end* argument is negative, it is treated as an offset from the end of the string `length(string) + end`.
- """
- return FunctionExpression("span", args)
+ def span(cls, array: IntoExpression, /) -> Expression:
+ """Returns the span of ``array``: the difference between the last and first elements, or *array[array.length-1] - array[0]*."""
+ return FunctionExpression("span", (array,))
@classmethod
- def lower(cls, *args) -> FunctionExpression:
- """Transforms *string* to lower-case letters."""
- return FunctionExpression("lower", args)
+ def lower(cls, string: IntoExpression, /) -> Expression:
+ """Transforms ``string`` to lower-case letters."""
+ return FunctionExpression("lower", (string,))
@classmethod
- def pad(cls, *args) -> FunctionExpression:
+ def pad(
+ cls,
+ string: IntoExpression,
+ length: IntoExpression,
+ character: IntoExpression = None,
+ align: IntoExpression = None,
+ /,
+ ) -> Expression:
"""
- Pads a *string* value with repeated instances of a *character* up to a specified *length*.
+ Pads a ``string`` value with repeated instances of a ``character`` up to a specified ``length``.
- If *character* is not specified, a space (' ') is used.
- By default, padding is added to the end of a string.
- An optional *align* parameter specifies if padding should be added to the `'left'` (beginning), `'center'`, or `'right'` (end) of the input string.
+ If ``character`` is not specified, a space (' ') is used. By default, padding is added to
+ the end of a string. An optional ``align`` parameter specifies if padding should be added to
+ the ``'left'`` (beginning), ``'center'``, or ``'right'`` (end) of the input string.
"""
- return FunctionExpression("pad", args)
+ return FunctionExpression("pad", (string, length, character, align))
@classmethod
- def parseFloat(cls, *args) -> FunctionExpression:
+ def parseFloat(cls, string: IntoExpression, /) -> Expression:
"""
- Parses the input *string* to a floating-point value.
+ Parses the input ``string`` to a floating-point value.
- Same as JavaScript's `parseFloat`.
+ Same as JavaScript's ``parseFloat``.
"""
- return FunctionExpression("parseFloat", args)
+ return FunctionExpression("parseFloat", (string,))
@classmethod
- def parseInt(cls, *args) -> FunctionExpression:
+ def parseInt(cls, string: IntoExpression, /) -> Expression:
"""
- Parses the input *string* to an integer value.
+ Parses the input ``string`` to an integer value.
- Same as JavaScript's `parseInt`.
+ Same as JavaScript's ``parseInt``.
"""
- return FunctionExpression("parseInt", args)
+ return FunctionExpression("parseInt", (string,))
@classmethod
- def replace(cls, *args) -> FunctionExpression:
+ def replace(
+ cls,
+ string: IntoExpression,
+ pattern: IntoExpression,
+ replacement: IntoExpression,
+ /,
+ ) -> Expression:
"""
- Returns a new string with some or all matches of *pattern* replaced by a *replacement* string.
+ Returns a new string with some or all matches of ``pattern`` replaced by a ``replacement`` string.
- The *pattern* can be a string or a regular expression.
- If *pattern* is a string, only the first instance will be replaced.
- Same as `JavaScript's String.replace `__.
- """
- return FunctionExpression("replace", args)
+ The ``pattern`` can be a string or a regular expression. If ``pattern`` is a string, only
+ the first instance will be replaced. Same as `JavaScript's String.replace`_.
- @classmethod
- def split(cls, *args) -> FunctionExpression:
+ .. _JavaScript's String.replace:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
"""
- Returns an array of tokens created by splitting the input *string* according to a provided *separator* pattern.
-
- The result can optionally be constrained to return at most *limit* tokens.
- """
- return FunctionExpression("split", args)
+ return FunctionExpression("replace", (string, pattern, replacement))
@classmethod
- def substring(cls, *args) -> FunctionExpression:
- """Returns a section of *string* between the *start* and *end* indices."""
- return FunctionExpression("substring", args)
+ def substring(
+ cls,
+ string: IntoExpression,
+ start: IntoExpression,
+ end: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """Returns a section of ``string`` between the ``start`` and ``end`` indices."""
+ return FunctionExpression("substring", (string, start, end))
@classmethod
- def trim(cls, *args) -> FunctionExpression:
+ def trim(cls, string: IntoExpression, /) -> Expression:
"""Returns a trimmed string with preceding and trailing whitespace removed."""
- return FunctionExpression("trim", args)
+ return FunctionExpression("trim", (string,))
@classmethod
- def truncate(cls, *args) -> FunctionExpression:
- r"""
- Truncates an input *string* to a target *length*.
+ def truncate(
+ cls,
+ string: IntoExpression,
+ length: IntoExpression,
+ align: IntoExpression = None,
+ ellipsis: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Truncates an input ``string`` to a target ``length``.
- The optional *align* argument indicates what part of the string should be truncated: `'left'` (the beginning), `'center'`, or `'right'` (the end).
- By default, the `'right'` end of the string is truncated.
- The optional *ellipsis* argument indicates the string to use to indicate truncated content;
- by default the ellipsis character `...` (`\\u2026`) is used.
+ The optional ``align`` argument indicates what part of the string should be truncated:
+ ``'left'`` (the beginning), ``'center'``, or ``'right'`` (the end). By default, the
+ ``'right'`` end of the string is truncated. The optional ``ellipsis`` argument indicates the
+ string to use to indicate truncated content; by default the ellipsis character ``…``
+ (``\u2026``) is used.
"""
- return FunctionExpression("truncate", args)
+ return FunctionExpression("truncate", (string, length, align, ellipsis))
@classmethod
- def upper(cls, *args) -> FunctionExpression:
- """Transforms *string* to upper-case letters."""
- return FunctionExpression("upper", args)
+ def upper(cls, string: IntoExpression, /) -> Expression:
+ """Transforms ``string`` to upper-case letters."""
+ return FunctionExpression("upper", (string,))
@classmethod
- def merge(cls, *args) -> FunctionExpression:
+ def merge(
+ cls, object1: IntoExpression, object2: IntoExpression = None, *args: Any
+ ) -> Expression:
"""
- Merges the input objects *object1*, *object2*, etc into a new output object.
-
- Inputs are visited in sequential order, such that key values from later arguments can overwrite those from earlier arguments.
+ Merges the input objects ``object1``, ``object2``, etc into a new output object.
- Example: `merge({a:1, b:2}, {a:3}) -> {a:3, b:2}`.
+ Inputs are visited in sequential order, such that key values from later arguments can
+ overwrite those from earlier arguments. Example: ``alt.expr.merge({a:1, b:2}, {a:3}) ->
+ {a:3, b:2}``.
"""
- return FunctionExpression("merge", args)
+ return FunctionExpression("merge", (object1, object2, *args))
@classmethod
- def dayFormat(cls, *args) -> FunctionExpression:
+ def dayFormat(cls, day: IntoExpression, /) -> Expression:
"""
Formats a (0-6) *weekday* number as a full week day name, according to the current locale.
- For example: `dayFormat(0) -> "Sunday"`.
+ For example: ``alt.expr.dayFormat(0) -> "Sunday"``.
"""
- return FunctionExpression("dayFormat", args)
+ return FunctionExpression("dayFormat", (day,))
@classmethod
- def dayAbbrevFormat(cls, *args) -> FunctionExpression:
+ def dayAbbrevFormat(cls, day: IntoExpression, /) -> Expression:
"""
Formats a (0-6) *weekday* number as an abbreviated week day name, according to the current locale.
- For example: `dayAbbrevFormat(0) -> "Sun"`.
+ For example: ``alt.expr.dayAbbrevFormat(0) -> "Sun"``.
"""
- return FunctionExpression("dayAbbrevFormat", args)
+ return FunctionExpression("dayAbbrevFormat", (day,))
@classmethod
- def format(cls, *args) -> FunctionExpression:
+ def format(cls, value: IntoExpression, specifier: IntoExpression, /) -> Expression:
"""
- Formats a numeric *value* as a string.
+ Formats a numeric ``value`` as a string.
- The *specifier* must be a valid `d3-format specifier `__ (e.g., `format(value, ',.2f')`.
+ The ``specifier`` must be a valid `d3-format specifier`_ (e.g., ``alt.expr.format(value,
+ ',.2f')``. Null values are formatted as ``"null"``.
+
+ .. _d3-format specifier:
+ https://github.com/d3/d3-format/
"""
- return FunctionExpression("format", args)
+ return FunctionExpression("format", (value, specifier))
@classmethod
- def monthFormat(cls, *args) -> FunctionExpression:
+ def monthFormat(cls, month: IntoExpression, /) -> Expression:
"""
- Formats a (zero-based) *month* number as a full month name, according to the current locale.
+ Formats a (zero-based) ``month`` number as a full month name, according to the current locale.
- For example: `monthFormat(0) -> "January"`.
+ For example: ``alt.expr.monthFormat(0) -> "January"``.
"""
- return FunctionExpression("monthFormat", args)
+ return FunctionExpression("monthFormat", (month,))
@classmethod
- def monthAbbrevFormat(cls, *args) -> FunctionExpression:
+ def monthAbbrevFormat(cls, month: IntoExpression, /) -> Expression:
"""
- Formats a (zero-based) *month* number as an abbreviated month name, according to the current locale.
+ Formats a (zero-based) ``month`` number as an abbreviated month name, according to the current locale.
- For example: `monthAbbrevFormat(0) -> "Jan"`.
+ For example: ``alt.expr.monthAbbrevFormat(0) -> "Jan"``.
"""
- return FunctionExpression("monthAbbrevFormat", args)
+ return FunctionExpression("monthAbbrevFormat", (month,))
@classmethod
- def timeUnitSpecifier(cls, *args) -> FunctionExpression:
+ def timeUnitSpecifier(
+ cls, units: IntoExpression, specifiers: IntoExpression = None, /
+ ) -> Expression:
"""
- Returns a time format specifier string for the given time `unit `__.
-
- The optional *specifiers* object provides a set of specifier sub-strings for customizing the format;
- for more, see the `timeUnitSpecifier API documentation `__.
+ Returns a time format specifier string for the given time `*units*`_.
- The resulting specifier string can then be used as input to the `timeFormat `__ or
- `utcFormat `__ functions, or as the *format* parameter of an axis or legend.
+ The optional ``specifiers`` object provides a set of specifier sub-strings for customizing
+ the format; for more, see the `timeUnitSpecifier API documentation`_. The resulting
+ specifier string can then be used as input to the `timeFormat`_ or `utcFormat`_ functions,
+ or as the *format* parameter of an axis or legend. For example: ``alt.expr.timeFormat(date,
+ alt.expr.timeUnitSpecifier('year'))`` or ``alt.expr.timeFormat(date,
+ alt.expr.timeUnitSpecifier(['hours', 'minutes']))``.
- For example: `timeFormat(date, timeUnitSpecifier('year'))` or `timeFormat(date, timeUnitSpecifier(['hours', 'minutes']))`.
+ .. _*units*:
+ https://vega.github.io/vega/docs/api/time/#time-units
+ .. _timeUnitSpecifier API documentation:
+ https://vega.github.io/vega/docs/api/time/#timeUnitSpecifier
+ .. _timeFormat:
+ https://vega.github.io/vega/docs/expressions/#timeFormat
+ .. _utcFormat:
+ https://vega.github.io/vega/docs/expressions/#utcFormat
"""
- return FunctionExpression("timeUnitSpecifier", args)
+ return FunctionExpression("timeUnitSpecifier", (units, specifiers))
@classmethod
- def timeFormat(cls, *args) -> FunctionExpression:
+ def timeFormat(
+ cls, value: IntoExpression, specifier: IntoExpression, /
+ ) -> Expression:
"""
- Formats a datetime *value* (either a `Date` object or timestamp) as a string, according to the local time.
+ Formats a datetime ``value`` (either a ``Date`` object or timestamp) as a string, according to the local time.
- The *specifier* must be a valid `d3-time-format specifier `__.
- For example: `timeFormat(timestamp, '%A')`.
+ The ``specifier`` must be a valid `d3-time-format specifier`_ or `TimeMultiFormat object`_.
+ For example: ``alt.expr.timeFormat(timestamp, '%A')``. Null values are formatted as
+ ``"null"``.
+
+ .. _d3-time-format specifier:
+ https://github.com/d3/d3-time-format/
+ .. _TimeMultiFormat object:
+ https://vega.github.io/vega/docs/types/#TimeMultiFormat
"""
- return FunctionExpression("timeFormat", args)
+ return FunctionExpression("timeFormat", (value, specifier))
@classmethod
- def timeParse(cls, *args) -> FunctionExpression:
+ def timeParse(
+ cls, string: IntoExpression, specifier: IntoExpression, /
+ ) -> Expression:
"""
- Parses a *string* value to a Date object, according to the local time.
+ Parses a ``string`` value to a Date object, according to the local time.
+
+ The ``specifier`` must be a valid `d3-time-format specifier`_. For example:
+ ``alt.expr.timeParse('June 30, 2015', '%B %d, %Y')``.
- The *specifier* must be a valid `d3-time-format specifier `__.
- For example: `timeParse('June 30, 2015', '%B %d, %Y')`.
+ .. _d3-time-format specifier:
+ https://github.com/d3/d3-time-format/
"""
- return FunctionExpression("timeParse", args)
+ return FunctionExpression("timeParse", (string, specifier))
@classmethod
- def utcFormat(cls, *args) -> FunctionExpression:
+ def utcFormat(
+ cls, value: IntoExpression, specifier: IntoExpression, /
+ ) -> Expression:
"""
- Formats a datetime *value* (either a `Date` object or timestamp) as a string, according to `UTC `__ time.
+ Formats a datetime ``value`` (either a ``Date`` object or timestamp) as a string, according to `UTC`_ time.
- The *specifier* must be a valid `d3-time-format specifier `__.
- For example: `utcFormat(timestamp, '%A')`.
+ The ``specifier`` must be a valid `d3-time-format specifier`_ or `TimeMultiFormat object`_.
+ For example: ``alt.expr.utcFormat(timestamp, '%A')``. Null values are formatted as
+ ``"null"``.
+
+ .. _UTC:
+ https://en.wikipedia.org/wiki/Coordinated_Universal_Time
+ .. _d3-time-format specifier:
+ https://github.com/d3/d3-time-format/
+ .. _TimeMultiFormat object:
+ https://vega.github.io/vega/docs/types/#TimeMultiFormat
"""
- return FunctionExpression("utcFormat", args)
+ return FunctionExpression("utcFormat", (value, specifier))
@classmethod
- def utcParse(cls, *args) -> FunctionExpression:
+ def utcParse(
+ cls, value: IntoExpression, specifier: IntoExpression, /
+ ) -> Expression:
"""
- Parses a *string* value to a Date object, according to `UTC `__ time.
+ Parses a *string* value to a Date object, according to `UTC`_ time.
+
+ The ``specifier`` must be a valid `d3-time-format specifier`_. For example:
+ ``alt.expr.utcParse('June 30, 2015', '%B %d, %Y')``.
- The *specifier* must be a valid `d3-time-format specifier `__.
- For example: `utcParse('June 30, 2015', '%B %d, %Y')`.
+ .. _UTC:
+ https://en.wikipedia.org/wiki/Coordinated_Universal_Time
+ .. _d3-time-format specifier:
+ https://github.com/d3/d3-time-format/
"""
- return FunctionExpression("utcParse", args)
+ return FunctionExpression("utcParse", (value, specifier))
@classmethod
- def regexp(cls, *args) -> FunctionExpression:
+ def regexp(
+ cls, pattern: IntoExpression, flags: IntoExpression = None, /
+ ) -> Expression:
"""
- Creates a regular expression instance from an input *pattern* string and optional *flags*.
+ Creates a regular expression instance from an input ``pattern`` string and optional ``flags``.
- Same as `JavaScript's `RegExp` `__.
+ Same as `JavaScript's RegExp`_.
+
+ .. _JavaScript's RegExp:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
"""
- return FunctionExpression("regexp", args)
+ return FunctionExpression("regexp", (pattern, flags))
@classmethod
- def test(cls, *args) -> FunctionExpression:
+ def test(
+ cls, regexp: IntoExpression, string: IntoExpression = None, /
+ ) -> Expression:
r"""
- Evaluates a regular expression *regexp* against the input *string*, returning `true` if the string matches the pattern, `false` otherwise.
+ Evaluates a regular expression ``regexp`` against the input ``string``, returning ``true`` if the string matches the pattern, ``false`` otherwise.
- For example: `test(\d{3}, "32-21-9483") -> true`.
+ For example: ``alt.expr.test(/\\d{3}/, "32-21-9483") -> true``.
"""
- return FunctionExpression("test", args)
+ return FunctionExpression("test", (regexp, string))
@classmethod
- def rgb(cls, *args) -> FunctionExpression:
+ def rgb(cls, *args: Any) -> Expression:
"""
- Constructs a new `RGB `__ color.
+ Constructs a new `RGB`_ color.
+
+ If ``r``, ``g`` and ``b`` are specified, these represent the channel values of the returned
+ color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+ string is specified, it is parsed and then converted to the RGB color space. Uses
+ `d3-color's rgb function`_.
- If *r*, *g* and *b* are specified, these represent the channel values of the returned color; an *opacity* may also be specified.
- If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the RGB color space. Uses `d3-color's rgb function `__.
+ .. _RGB:
+ https://en.wikipedia.org/wiki/RGB_color_model
+ .. _d3-color's rgb function:
+ https://github.com/d3/d3-color#rgb
"""
return FunctionExpression("rgb", args)
@classmethod
- def hsl(cls, *args) -> FunctionExpression:
+ def hsl(cls, *args: Any) -> Expression:
"""
- Constructs a new `HSL `__ color.
+ Constructs a new `HSL`_ color.
- If *h*, *s* and *l* are specified, these represent the channel values of the returned color; an *opacity* may also be specified.
- If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the HSL color space.
- Uses `d3-color's hsl function `__.
+ If ``h``, ``s`` and ``l`` are specified, these represent the channel values of the returned
+ color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+ string is specified, it is parsed and then converted to the HSL color space. Uses
+ `d3-color's hsl function`_.
+
+ .. _HSL:
+ https://en.wikipedia.org/wiki/HSL_and_HSV
+ .. _d3-color's hsl function:
+ https://github.com/d3/d3-color#hsl
"""
return FunctionExpression("hsl", args)
@classmethod
- def lab(cls, *args) -> FunctionExpression:
- """
- Constructs a new `CIE LAB `__ color.
-
- If *l*, *a* and *b* are specified, these represent the channel values of the returned color; an *opacity* may also be specified.
- If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the LAB color space.
- Uses `d3-color's lab function `__.
+ def lab(cls, *args: Any) -> Expression:
"""
- return FunctionExpression("lab", args)
+ Constructs a new `CIE LAB`_ color.
- @classmethod
- def hcl(cls, *args) -> FunctionExpression:
- """
- Constructs a new `HCL `__ (hue, chroma, luminance) color.
+ If ``l``, ``a`` and ``b`` are specified, these represent the channel values of the returned
+ color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+ string is specified, it is parsed and then converted to the LAB color space. Uses
+ `d3-color's lab function`_.
- If *h*, *c* and *l* are specified, these represent the channel values of the returned color; an *opacity* may also be specified.
- If a CSS Color Module Level 3 *specifier* string is specified, it is parsed and then converted to the HCL color space.
- Uses `d3-color's hcl function `__.
+ .. _CIE LAB:
+ https://en.wikipedia.org/wiki/Lab_color_space#CIELAB
+ .. _d3-color's lab function:
+ https://github.com/d3/d3-color#lab
"""
- return FunctionExpression("hcl", args)
+ return FunctionExpression("lab", args)
@classmethod
- def luminance(cls, *args) -> FunctionExpression:
- """
- Returns the luminance for the given color *specifier* (compatible with `d3-color's rgb function `__.
-
- The luminance is calculated according to the `W3C Web Content Accessibility Guidelines `__.
+ def hcl(cls, *args: Any) -> Expression:
"""
- return FunctionExpression("luminance", args)
+ Constructs a new `HCL`_ (hue, chroma, luminance) color.
- @classmethod
- def contrast(cls, *args) -> FunctionExpression:
- """
- Returns the contrast ratio between the input color specifiers as a float between 1 and 21.
+ If ``h``, ``c`` and ``l`` are specified, these represent the channel values of the returned
+ color; an ``opacity`` may also be specified. If a CSS Color Module Level 3 *specifier*
+ string is specified, it is parsed and then converted to the HCL color space. Uses
+ `d3-color's hcl function`_.
- The contrast is calculated according to the `W3C Web Content Accessibility Guidelines `__.
+ .. _HCL:
+ https://en.wikipedia.org/wiki/Lab_color_space#CIELAB
+ .. _d3-color's hcl function:
+ https://github.com/d3/d3-color#hcl
"""
- return FunctionExpression("contrast", args)
-
- @classmethod
- def item(cls, *args) -> FunctionExpression:
- """Returns the current scenegraph item that is the target of the event."""
- return FunctionExpression("item", args)
+ return FunctionExpression("hcl", args)
@classmethod
- def group(cls, *args) -> FunctionExpression:
+ def group(cls, name: IntoExpression = None, /) -> Expression:
"""
Returns the scenegraph group mark item in which the current event has occurred.
- If no arguments are provided, the immediate parent group is returned.
- If a group name is provided, the matching ancestor group item is returned.
+ If no arguments are provided, the immediate parent group is returned. If a group name is
+ provided, the matching ancestor group item is returned.
"""
- return FunctionExpression("group", args)
+ return FunctionExpression("group", (name,))
@classmethod
- def xy(cls, *args) -> FunctionExpression:
+ def xy(cls, item: IntoExpression = None, /) -> Expression:
"""
Returns the x- and y-coordinates for the current event as a two-element array.
- If no arguments are provided, the top-level coordinate space of the view is used.
- If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.
+ If no arguments are provided, the top-level coordinate space of the view is used. If a
+ scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
+ item is used.
"""
- return FunctionExpression("xy", args)
+ return FunctionExpression("xy", (item,))
@classmethod
- def x(cls, *args) -> FunctionExpression:
+ def x(cls, item: IntoExpression = None, /) -> Expression:
"""
Returns the x coordinate for the current event.
- If no arguments are provided, the top-level coordinate space of the view is used.
- If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.
+ If no arguments are provided, the top-level coordinate space of the view is used. If a
+ scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
+ item is used.
"""
- return FunctionExpression("x", args)
+ return FunctionExpression("x", (item,))
@classmethod
- def y(cls, *args) -> FunctionExpression:
+ def y(cls, item: IntoExpression = None, /) -> Expression:
"""
Returns the y coordinate for the current event.
- If no arguments are provided, the top-level coordinate space of the view is used.
- If a scenegraph *item* (or string group name) is provided, the coordinate space of the group item is used.
+ If no arguments are provided, the top-level coordinate space of the view is used. If a
+ scenegraph ``item`` (or string group name) is provided, the coordinate space of the group
+ item is used.
"""
- return FunctionExpression("y", args)
+ return FunctionExpression("y", (item,))
@classmethod
- def pinchDistance(cls, *args) -> FunctionExpression:
+ def pinchDistance(cls, event: IntoExpression, /) -> Expression:
"""Returns the pixel distance between the first two touch points of a multi-touch event."""
- return FunctionExpression("pinchDistance", args)
+ return FunctionExpression("pinchDistance", (event,))
@classmethod
- def pinchAngle(cls, *args) -> FunctionExpression:
+ def pinchAngle(cls, event: IntoExpression, /) -> Expression:
"""Returns the angle of the line connecting the first two touch points of a multi-touch event."""
- return FunctionExpression("pinchAngle", args)
+ return FunctionExpression("pinchAngle", (event,))
@classmethod
- def inScope(cls, *args) -> FunctionExpression:
- """Returns true if the given scenegraph *item* is a descendant of the group mark in which the event handler was defined, false otherwise."""
- return FunctionExpression("inScope", args)
+ def inScope(cls, item: IntoExpression, /) -> Expression:
+ """Returns true if the given scenegraph ``item`` is a descendant of the group mark in which the event handler was defined, false otherwise."""
+ return FunctionExpression("inScope", (item,))
@classmethod
- def data(cls, *args) -> FunctionExpression:
+ def data(cls, name: IntoExpression, /) -> Expression:
"""
- Returns the array of data objects for the Vega data set with the given *name*.
+ Returns the array of data objects for the Vega data set with the given ``name``.
If the data set is not found, returns an empty array.
"""
- return FunctionExpression("data", args)
+ return FunctionExpression("data", (name,))
@classmethod
- def indata(cls, *args) -> FunctionExpression:
+ def indata(
+ cls, name: IntoExpression, field: IntoExpression, value: IntoExpression, /
+ ) -> Expression:
"""
- Tests if the data set with a given *name* contains a datum with a *field* value that matches the input *value*.
+ Tests if the data set with a given ``name`` contains a datum with a ``field`` value that matches the input ``value``.
- For example: `indata('table', 'category', value)`.
+ For example: ``alt.expr.indata('table', 'category', value)``.
"""
- return FunctionExpression("indata", args)
+ return FunctionExpression("indata", (name, field, value))
@classmethod
- def scale(cls, *args) -> FunctionExpression:
+ def scale(
+ cls,
+ name: IntoExpression,
+ value: IntoExpression,
+ group: IntoExpression = None,
+ /,
+ ) -> Expression:
"""
- Applies the named scale transform (or projection) to the specified *value*.
+ Applies the named scale transform (or projection) to the specified ``value``.
- The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale or projection.
"""
- return FunctionExpression("scale", args)
+ return FunctionExpression("scale", (name, value, group))
@classmethod
- def invert(cls, *args) -> FunctionExpression:
+ def invert(
+ cls,
+ name: IntoExpression,
+ value: IntoExpression,
+ group: IntoExpression = None,
+ /,
+ ) -> Expression:
"""
- Inverts the named scale transform (or projection) for the specified *value*.
+ Inverts the named scale transform (or projection) for the specified ``value``.
- The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale or projection.
"""
- return FunctionExpression("invert", args)
+ return FunctionExpression("invert", (name, value, group))
@classmethod
- def copy(cls, *args) -> FunctionExpression: # type: ignore[override]
+ def copy(cls, name: IntoExpression, group: IntoExpression = None, /) -> Expression: # type: ignore[override]
"""
- Returns a copy (a new cloned instance) of the named scale transform of projection, or `undefined` if no scale or projection is found.
+ Returns a copy (a new cloned instance) of the named scale transform of projection, or ``undefined`` if no scale or projection is found.
- The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale or projection.
"""
- # error: Signature of "copy" incompatible with supertype "SchemaBase" [override]
- # note: def copy(self, deep: bool | Iterable[Any] = ..., ignore: list[str] | None = ...) -> expr
- # NOTE: Not relevant as `expr() -> ExprRef`
- # this method is only accesible via `expr.copy()`
- return FunctionExpression("copy", args)
+ return FunctionExpression("copy", (name, group))
@classmethod
- def domain(cls, *args) -> FunctionExpression:
+ def domain(
+ cls, name: IntoExpression, group: IntoExpression = None, /
+ ) -> Expression:
"""
Returns the scale domain array for the named scale transform, or an empty array if the scale is not found.
- The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale.
"""
- return FunctionExpression("domain", args)
+ return FunctionExpression("domain", (name, group))
@classmethod
- def range(cls, *args) -> FunctionExpression:
+ def range(cls, name: IntoExpression, group: IntoExpression = None, /) -> Expression:
"""
Returns the scale range array for the named scale transform, or an empty array if the scale is not found.
- The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale.
"""
- return FunctionExpression("range", args)
+ return FunctionExpression("range", (name, group))
@classmethod
- def bandwidth(cls, *args) -> FunctionExpression:
+ def bandwidth(
+ cls, name: IntoExpression, group: IntoExpression = None, /
+ ) -> Expression:
"""
Returns the current band width for the named band scale transform, or zero if the scale is not found or is not a band scale.
- The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale.
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the scale.
"""
- return FunctionExpression("bandwidth", args)
+ return FunctionExpression("bandwidth", (name, group))
@classmethod
- def bandspace(cls, *args) -> FunctionExpression:
+ def bandspace(
+ cls,
+ count: IntoExpression,
+ paddingInner: IntoExpression = None,
+ paddingOuter: IntoExpression = None,
+ /,
+ ) -> Expression:
"""
- Returns the number of steps needed within a band scale, based on the *count* of domain elements and the inner and outer padding values.
+ Returns the number of steps needed within a band scale, based on the ``count`` of domain elements and the inner and outer padding values.
- While normally calculated within the scale itself, this function can be helpful for determining the size of a chart's layout.
+ While normally calculated within the scale itself, this function can be helpful for
+ determining the size of a chart's layout.
"""
- return FunctionExpression("bandspace", args)
+ return FunctionExpression("bandspace", (count, paddingInner, paddingOuter))
@classmethod
- def gradient(cls, *args) -> FunctionExpression:
+ def gradient(
+ cls,
+ scale: IntoExpression,
+ p0: IntoExpression,
+ p1: IntoExpression,
+ count: IntoExpression = None,
+ /,
+ ) -> Expression:
"""
- Returns a linear color gradient for the *scale* (whose range must be a `continuous color scheme `__ and starting and ending points *p0* and *p1*, each an `[x, y]` array.
+ Returns a linear color gradient for the ``scale`` (whose range must be a `continuous color scheme`_) and starting and ending points ``p0`` and ``p1``, each an *[x, y]* array.
- The points *p0* and *p1* should be expressed in normalized coordinates in the domain `[0, 1]`, relative to the bounds of the item being colored.
+ The points ``p0`` and ``p1`` should be expressed in normalized coordinates in the domain [0,
+ 1], relative to the bounds of the item being colored. If unspecified, ``p0`` defaults to
+ ``[0, 0]`` and ``p1`` defaults to ``[1, 0]``, for a horizontal gradient that spans the full
+ bounds of an item. The optional ``count`` argument indicates a desired target number of
+ sample points to take from the color scale.
- If unspecified, *p0* defaults to `[0, 0]` and *p1* defaults to `[1, 0]`, for a horizontal gradient that spans the full bounds of an item.
- The optional *count* argument indicates a desired target number of sample points to take from the color scale.
+ .. _continuous color scheme:
+ https://vega.github.io/vega/docs/schemes
"""
- return FunctionExpression("gradient", args)
+ return FunctionExpression("gradient", (scale, p0, p1, count))
@classmethod
- def panLinear(cls, *args) -> FunctionExpression:
+ def panLinear(cls, domain: IntoExpression, delta: IntoExpression, /) -> Expression:
"""
- Given a linear scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*.
+ Given a linear scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
- The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.
+ The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+ indicates panning the scale domain to the right by half the scale range.
"""
- return FunctionExpression("panLinear", args)
+ return FunctionExpression("panLinear", (domain, delta))
@classmethod
- def panLog(cls, *args) -> FunctionExpression:
+ def panLog(cls, domain: IntoExpression, delta: IntoExpression, /) -> Expression:
"""
- Given a log scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*.
+ Given a log scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
- The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.
+ The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+ indicates panning the scale domain to the right by half the scale range.
"""
- return FunctionExpression("panLog", args)
+ return FunctionExpression("panLog", (domain, delta))
@classmethod
- def panPow(cls, *args) -> FunctionExpression:
+ def panPow(
+ cls, domain: IntoExpression, delta: IntoExpression, exponent: IntoExpression, /
+ ) -> Expression:
"""
- Given a power scale *domain* array with numeric or datetime values and the given *exponent*, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*.
+ Given a power scale ``domain`` array with numeric or datetime values and the given ``exponent``, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
- The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.
+ The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+ indicates panning the scale domain to the right by half the scale range.
"""
- return FunctionExpression("panPow", args)
+ return FunctionExpression("panPow", (domain, delta, exponent))
@classmethod
- def panSymlog(cls, *args) -> FunctionExpression:
+ def panSymlog(
+ cls, domain: IntoExpression, delta: IntoExpression, constant: IntoExpression, /
+ ) -> Expression:
"""
- Given a symmetric log scale *domain* array with numeric or datetime values parameterized by the given *constant*, returns a new two-element domain array that is the result of panning the domain by a fractional *delta*.
+ Given a symmetric log scale ``domain`` array with numeric or datetime values parameterized by the given ``constant``, returns a new two-element domain array that is the result of panning the domain by a fractional ``delta``.
- The *delta* value represents fractional units of the scale range; for example, `0.5` indicates panning the scale domain to the right by half the scale range.
+ The ``delta`` value represents fractional units of the scale range; for example, ``0.5``
+ indicates panning the scale domain to the right by half the scale range.
"""
- return FunctionExpression("panSymlog", args)
+ return FunctionExpression("panSymlog", (domain, delta, constant))
@classmethod
- def zoomLinear(cls, *args) -> FunctionExpression:
+ def zoomLinear(
+ cls,
+ domain: IntoExpression,
+ anchor: IntoExpression,
+ scaleFactor: IntoExpression,
+ /,
+ ) -> Expression:
"""
- Given a linear scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*.
+ Given a linear scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
- The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.
+ The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+ range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
"""
- return FunctionExpression("zoomLinear", args)
+ return FunctionExpression("zoomLinear", (domain, anchor, scaleFactor))
@classmethod
- def zoomLog(cls, *args) -> FunctionExpression:
+ def zoomLog(
+ cls,
+ domain: IntoExpression,
+ anchor: IntoExpression,
+ scaleFactor: IntoExpression,
+ /,
+ ) -> Expression:
"""
- Given a log scale *domain* array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*.
+ Given a log scale ``domain`` array with numeric or datetime values, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
- The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.
+ The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+ range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
"""
- return FunctionExpression("zoomLog", args)
+ return FunctionExpression("zoomLog", (domain, anchor, scaleFactor))
@classmethod
- def zoomPow(cls, *args) -> FunctionExpression:
+ def zoomPow(
+ cls,
+ domain: IntoExpression,
+ anchor: IntoExpression,
+ scaleFactor: IntoExpression,
+ exponent: IntoExpression,
+ /,
+ ) -> Expression:
"""
- Given a power scale *domain* array with numeric or datetime values and the given *exponent*, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*.
+ Given a power scale ``domain`` array with numeric or datetime values and the given ``exponent``, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
- The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.
+ The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+ range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
"""
- return FunctionExpression("zoomPow", args)
+ return FunctionExpression("zoomPow", (domain, anchor, scaleFactor, exponent))
@classmethod
- def zoomSymlog(cls, *args) -> FunctionExpression:
+ def zoomSymlog(
+ cls,
+ domain: IntoExpression,
+ anchor: IntoExpression,
+ scaleFactor: IntoExpression,
+ constant: IntoExpression,
+ /,
+ ) -> Expression:
"""
- Given a symmetric log scale *domain* array with numeric or datetime values parameterized by the given *constant*, returns a new two-element domain array that is the result of zooming the domain by a *scaleFactor*, centered at the provided fractional *anchor*.
+ Given a symmetric log scale ``domain`` array with numeric or datetime values parameterized by the given ``constant``, returns a new two-element domain array that is the result of zooming the domain by a ``scaleFactor``, centered at the provided fractional ``anchor``.
- The *anchor* value represents the zoom position in terms of fractional units of the scale range; for example, `0.5` indicates a zoom centered on the mid-point of the scale range.
+ The ``anchor`` value represents the zoom position in terms of fractional units of the scale
+ range; for example, ``0.5`` indicates a zoom centered on the mid-point of the scale range.
"""
- return FunctionExpression("zoomSymlog", args)
+ return FunctionExpression("zoomSymlog", (domain, anchor, scaleFactor, constant))
@classmethod
- def geoArea(cls, *args) -> FunctionExpression:
+ def geoArea(
+ cls,
+ projection: IntoExpression,
+ feature: IntoExpression,
+ group: IntoExpression = None,
+ /,
+ ) -> Expression:
"""
- Returns the projected planar area (typically in square pixels) of a GeoJSON *feature* according to the named *projection*.
+ Returns the projected planar area (typically in square pixels) of a GeoJSON ``feature`` according to the named ``projection``.
+
+ If the ``projection`` argument is ``null``, computes the spherical area in steradians using
+ unprojected longitude, latitude coordinates. The optional ``group`` argument takes a
+ scenegraph group mark item to indicate the specific scope in which to look up the
+ projection. Uses d3-geo's `geoArea`_ and `path.area`_ methods.
- If the *projection* argument is `null`, computes the spherical area in steradians using unprojected longitude, latitude coordinates.
- The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection.
- Uses d3-geo's `geoArea `__ and `path.area `__ methods.
+ .. _geoArea:
+ https://github.com/d3/d3-geo#geoArea
+ .. _path.area:
+ https://github.com/d3/d3-geo#path_area
"""
- return FunctionExpression("geoArea", args)
+ return FunctionExpression("geoArea", (projection, feature, group))
@classmethod
- def geoBounds(cls, *args) -> FunctionExpression:
+ def geoBounds(
+ cls,
+ projection: IntoExpression,
+ feature: IntoExpression,
+ group: IntoExpression = None,
+ /,
+ ) -> Expression:
"""
- Returns the projected planar bounding box (typically in pixels) for the specified GeoJSON *feature*, according to the named *projection*.
+ Returns the projected planar bounding box (typically in pixels) for the specified GeoJSON ``feature``, according to the named ``projection``.
- The bounding box is represented by a two-dimensional array: `[[x0, y0], [x1, y1]]`,
- where *x0* is the minimum x-coordinate, *y0* is the minimum y-coordinate,
- *x1* is the maximum x-coordinate, and *y1* is the maximum y-coordinate.
+ The bounding box is represented by a two-dimensional array: [[*x₀*, *y₀*], [*x₁*, *y₁*]],
+ where *x₀* is the minimum x-coordinate, *y₀* is the minimum y-coordinate, *x₁* is the
+ maximum x-coordinate, and *y₁* is the maximum y-coordinate. If the ``projection`` argument
+ is ``null``, computes the spherical bounding box using unprojected longitude, latitude
+ coordinates. The optional ``group`` argument takes a scenegraph group mark item to indicate
+ the specific scope in which to look up the projection. Uses d3-geo's `geoBounds`_ and
+ `path.bounds`_ methods.
- If the *projection* argument is `null`, computes the spherical bounding box using unprojected longitude, latitude coordinates.
- The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection.
- Uses d3-geo's `geoBounds `__ and `path.bounds `__ methods.
+ .. _geoBounds:
+ https://github.com/d3/d3-geo#geoBounds
+ .. _path.bounds:
+ https://github.com/d3/d3-geo#path_bounds
"""
- return FunctionExpression("geoBounds", args)
+ return FunctionExpression("geoBounds", (projection, feature, group))
@classmethod
- def geoCentroid(cls, *args) -> FunctionExpression:
+ def geoCentroid(
+ cls,
+ projection: IntoExpression,
+ feature: IntoExpression,
+ group: IntoExpression = None,
+ /,
+ ) -> Expression:
"""
- Returns the projected planar centroid (typically in pixels) for the specified GeoJSON *feature*, according to the named *projection*.
+ Returns the projected planar centroid (typically in pixels) for the specified GeoJSON ``feature``, according to the named ``projection``.
- If the *projection* argument is `null`, computes the spherical centroid using unprojected longitude, latitude coordinates.
- The optional *group* argument takes a scenegraph group mark item to indicate the specific scope in which to look up the projection.
- Uses d3-geo's `geoCentroid `__ and `path.centroid `__ methods.
+ If the ``projection`` argument is ``null``, computes the spherical centroid using
+ unprojected longitude, latitude coordinates. The optional ``group`` argument takes a
+ scenegraph group mark item to indicate the specific scope in which to look up the
+ projection. Uses d3-geo's `geoCentroid`_ and `path.centroid`_ methods.
+
+ .. _geoCentroid:
+ https://github.com/d3/d3-geo#geoCentroid
+ .. _path.centroid:
+ https://github.com/d3/d3-geo#path_centroid
"""
- return FunctionExpression("geoCentroid", args)
+ return FunctionExpression("geoCentroid", (projection, feature, group))
@classmethod
- def treePath(cls, *args) -> FunctionExpression:
+ def geoScale(
+ cls, projection: IntoExpression, group: IntoExpression = None, /
+ ) -> Expression:
"""
- For the hierarchy data set with the given *name*, returns the shortest path through from the *source* node id to the *target* node id.
+ Returns the scale value for the named ``projection``.
- The path starts at the *source* node, ascends to the least common ancestor of the *source* node and the *target* node, and then descends to the *target* node.
+ The optional ``group`` argument takes a scenegraph group mark item to indicate the specific
+ scope in which to look up the projection.
"""
- return FunctionExpression("treePath", args)
+ return FunctionExpression("geoScale", (projection, group))
@classmethod
- def treeAncestors(cls, *args) -> FunctionExpression:
- """For the hierarchy data set with the given *name*, returns the array of ancestors nodes, starting with the input *node*, then followed by each parent up to the root."""
- return FunctionExpression("treeAncestors", args)
-
- @classmethod
- def containerSize(cls, *args) -> FunctionExpression:
+ def treePath(
+ cls, name: IntoExpression, source: IntoExpression, target: IntoExpression, /
+ ) -> Expression:
"""
- Returns the current CSS box size (`[el.clientWidth, el.clientHeight]`) of the parent DOM element that contains the Vega view.
+ For the hierarchy data set with the given ``name``, returns the shortest path through from the ``source`` node id to the ``target`` node id.
- If there is no container element, returns `[undefined, undefined]`.
+ The path starts at the ``source`` node, ascends to the least common ancestor of the
+ ``source`` node and the ``target`` node, and then descends to the ``target`` node.
"""
- return FunctionExpression("containerSize", args)
-
- @classmethod
- def screen(cls, *args) -> FunctionExpression:
- """Returns the `window.screen `__ object, or `{}` if Vega is not running in a browser environment."""
- return FunctionExpression("screen", args)
+ return FunctionExpression("treePath", (name, source, target))
@classmethod
- def windowSize(cls, *args) -> FunctionExpression:
- """Returns the current window size (`[window.innerWidth, window.innerHeight]`) or `[undefined, undefined]` if Vega is not running in a browser environment."""
- return FunctionExpression("windowSize", args)
+ def treeAncestors(cls, name: IntoExpression, node: IntoExpression, /) -> Expression:
+ """For the hierarchy data set with the given ``name``, returns the array of ancestors nodes, starting with the input ``node``, then followed by each parent up to the root."""
+ return FunctionExpression("treeAncestors", (name, node))
@classmethod
- def warn(cls, *args) -> FunctionExpression:
+ def warn(
+ cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
+ ) -> Expression:
"""
Logs a warning message and returns the last argument.
- For the message to appear in the console, the visualization view must have the appropriate logging level set.
+ For the message to appear in the console, the visualization view must have the appropriate
+ logging level set.
"""
- return FunctionExpression("warn", args)
+ return FunctionExpression("warn", (value1, value2, *args))
@classmethod
- def info(cls, *args) -> FunctionExpression:
+ def info(
+ cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
+ ) -> Expression:
"""
Logs an informative message and returns the last argument.
- For the message to appear in the console, the visualization view must have the appropriate logging level set.
+ For the message to appear in the console, the visualization view must have the appropriate
+ logging level set.
"""
- return FunctionExpression("info", args)
+ return FunctionExpression("info", (value1, value2, *args))
@classmethod
- def debug(cls, *args) -> FunctionExpression:
+ def debug(
+ cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
+ ) -> Expression:
"""
Logs a debugging message and returns the last argument.
- For the message to appear in the console, the visualization view must have the appropriate logging level set.
+ For the message to appear in the console, the visualization view must have the appropriate
+ logging level set.
"""
- return FunctionExpression("debug", args)
+ return FunctionExpression("debug", (value1, value2, *args))
_ExprType = expr
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index 6b2e24c51..3be003ae9 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -63,7 +63,7 @@
SCHEMA_FILE = "vega-lite-schema.json"
THEMES_FILE = "vega-themes.json"
EXPR_FILE: Path = (
- Path(__file__).parent / ".." / "altair" / "expr" / "dummy.py"
+ Path(__file__).parent / ".." / "altair" / "expr" / "__init__.py"
).resolve()
CHANNEL_MYPY_IGNORE_STATEMENTS: Final = """\
From 21d13e722a4bb466c572710f9171fc15484d5f7a Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 11:22:35 +0100
Subject: [PATCH 50/77] fix(typing): Resolve some revealed issues
- `IntoExpression` change I'll add in a new fix PR
- `OperatorMixin` todo needs an issue
---
altair/expr/core.py | 2 +-
tests/vegalite/v5/test_api.py | 14 ++++++++++----
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/altair/expr/core.py b/altair/expr/core.py
index e3bc65a52..e7872ada6 100644
--- a/altair/expr/core.py
+++ b/altair/expr/core.py
@@ -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]]
diff --git a/tests/vegalite/v5/test_api.py b/tests/vegalite/v5/test_api.py
index a7d2f1c69..5ca60e1f8 100644
--- a/tests/vegalite/v5/test_api.py
+++ b/tests/vegalite/v5/test_api.py
@@ -557,9 +557,13 @@ def test_when_labels_position_based_on_condition() -> None:
# `mypy` will flag structural errors here
cond = when["condition"][0]
otherwise = when["value"]
- param_color_py_when = alt.param(
- expr=alt.expr.if_(cond["test"], cond["value"], otherwise)
- )
+
+ # TODO: Open an issue on making `OperatorMixin` generic
+ # Something like this would be used as the return type for all `__dunder__` methods:
+ # R = TypeVar("R", Expression, SelectionPredicateComposition)
+ test = cond["test"]
+ assert not isinstance(test, alt.PredicateComposition)
+ param_color_py_when = alt.param(expr=alt.expr.if_(test, cond["value"], otherwise))
assert param_color_py_expr.expr == param_color_py_when.expr
chart = (
@@ -600,7 +604,9 @@ def test_when_expressions_inside_parameters() -> None:
cond = when_then_otherwise["condition"][0]
otherwise = when_then_otherwise["value"]
expected = alt.expr.if_(alt.datum.b >= 0, 10, -20)
- actual = alt.expr.if_(cond["test"], cond["value"], otherwise)
+ test = cond["test"]
+ assert not isinstance(test, alt.PredicateComposition)
+ actual = alt.expr.if_(test, cond["value"], otherwise)
assert expected == actual
text_conditioned = bar.mark_text(
From 7e0db6842a0f1f158033f69d867a08ca43f28989 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 15:15:07 +0100
Subject: [PATCH 51/77] refactor: Move `.md`, `.rst` utils to `tools.markup.py`
Also some rearranging in `vega_expr.py`
---
tools/__init__.py | 9 ++-
tools/generate_schema_wrapper.py | 2 +-
tools/markup.py | 122 +++++++++++++++++++++++++++++++
tools/schemapi/utils.py | 58 +--------------
tools/schemapi/vega_expr.py | 113 +++++++++-------------------
5 files changed, 170 insertions(+), 134 deletions(-)
create mode 100644 tools/markup.py
diff --git a/tools/__init__.py b/tools/__init__.py
index 052b8e9c0..46fc97553 100644
--- a/tools/__init__.py
+++ b/tools/__init__.py
@@ -1,8 +1,15 @@
-from tools import generate_api_docs, generate_schema_wrapper, schemapi, update_init_file
+from tools import (
+ generate_api_docs,
+ generate_schema_wrapper,
+ markup,
+ schemapi,
+ update_init_file,
+)
__all__ = [
"generate_api_docs",
"generate_schema_wrapper",
+ "markup",
"schemapi",
"update_init_file",
]
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index 3be003ae9..09ce43770 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -20,6 +20,7 @@
sys.path.insert(0, str(Path.cwd()))
+from tools.markup import rst_syntax_for_class
from tools.schemapi import ( # noqa: F401
CodeSnippet,
SchemaInfo,
@@ -38,7 +39,6 @@
import_typing_extensions,
indent_docstring,
resolve_references,
- rst_syntax_for_class,
ruff_format_py,
ruff_write_lint_format_str,
spell_literal,
diff --git a/tools/markup.py b/tools/markup.py
new file mode 100644
index 000000000..fc1af9cfc
--- /dev/null
+++ b/tools/markup.py
@@ -0,0 +1,122 @@
+"""Tools for working with formats like ``.md``, ``.rst``."""
+
+from __future__ import annotations
+
+import re
+from html import unescape
+from typing import TYPE_CHECKING, Any, Iterable, Literal
+
+import mistune
+from mistune.renderers.rst import RSTRenderer as _RSTRenderer
+
+if TYPE_CHECKING:
+ import sys
+ from pathlib import Path
+
+ if sys.version_info >= (3, 11):
+ from typing import TypeAlias
+ else:
+ from typing_extensions import TypeAlias
+ from re import Pattern
+
+ from mistune import BaseRenderer, BlockParser, BlockState, InlineParser
+
+Token: TypeAlias = "dict[str, Any]"
+
+_RE_LINK: Pattern[str] = re.compile(r"(?<=\[)([^\]]+)(?=\]\([^\)]+\))", re.MULTILINE)
+_RE_SPECIAL: Pattern[str] = re.compile(r"[*_]{2,3}|`", re.MULTILINE)
+
+
+class RSTRenderer(_RSTRenderer):
+ def __init__(self) -> None:
+ super().__init__()
+
+ def inline_html(self, token: Token, state: BlockState) -> str:
+ html = token["raw"]
+ return rf"\ :raw-html:`{html}`\ "
+
+
+class RSTParse(mistune.Markdown):
+ """
+ Minor extension to support partial `ast`_ conversion.
+
+ Only need to convert the docstring tokens to `.rst`.
+
+ .. _ast:
+ https://mistune.lepture.com/en/latest/guide.html#abstract-syntax-tree
+ """
+
+ def __init__(
+ self,
+ renderer: BaseRenderer | Literal["ast"] | None,
+ block: BlockParser | None = None,
+ inline: InlineParser | None = None,
+ plugins=None,
+ ) -> None:
+ if renderer == "ast":
+ renderer = None
+ super().__init__(renderer, block, inline, plugins)
+
+ def __call__(self, s: str) -> str:
+ s = super().__call__(s) # pyright: ignore[reportAssignmentType]
+ return unescape(s).replace(r"\ ,", ",").replace(r"\ ", " ")
+
+ def render_tokens(self, tokens: Iterable[Token], /) -> str:
+ """
+ Render ast tokens originating from another parser.
+
+ Parameters
+ ----------
+ tokens
+ All tokens will be rendered into a single `.rst` string
+ """
+ if self.renderer is None:
+ msg = "Unable to render tokens without a renderer."
+ raise TypeError(msg)
+ state = self.block.state_cls()
+ return self.renderer(self._iter_render(tokens, state), state)
+
+
+class RSTParseVegaLite(RSTParse):
+ def __init__(
+ self,
+ renderer: RSTRenderer | None = None,
+ block: BlockParser | None = None,
+ inline: InlineParser | None = None,
+ plugins=None,
+ ) -> None:
+ super().__init__(renderer or RSTRenderer(), block, inline, plugins)
+
+ def __call__(self, s: str) -> str:
+ # remove formatting from links
+ description = "".join(
+ _RE_SPECIAL.sub("", d) if i % 2 else d
+ for i, d in enumerate(_RE_LINK.split(s))
+ )
+
+ description = super().__call__(description)
+ # Some entries in the Vega-Lite schema miss the second occurence of '__'
+ description = description.replace("__Default value: ", "__Default value:__ ")
+ # Links to the vega-lite documentation cannot be relative but instead need to
+ # contain the full URL.
+ description = description.replace(
+ "types#datetime", "https://vega.github.io/vega-lite/docs/datetime.html"
+ )
+ # Fixing ambiguous unicode, RUF001 produces RUF002 in docs
+ 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()
+
+
+def read_ast_tokens(source: Path, /) -> list[Token]:
+ """
+ Read from ``source``, drop ``BlockState``.
+
+ Factored out to provide accurate typing.
+ """
+ return mistune.create_markdown(renderer="ast").read(source)[0]
+
+
+def rst_syntax_for_class(class_name: str) -> str:
+ return f":class:`{class_name}`"
diff --git a/tools/schemapi/utils.py b/tools/schemapi/utils.py
index 5c4a84f9c..6bc7b1f4b 100644
--- a/tools/schemapi/utils.py
+++ b/tools/schemapi/utils.py
@@ -8,7 +8,6 @@
import sys
import textwrap
import urllib.parse
-from html import unescape
from itertools import chain
from keyword import iskeyword
from operator import itemgetter
@@ -27,9 +26,7 @@
overload,
)
-import mistune
-from mistune.renderers.rst import RSTRenderer as _RSTRenderer
-
+from tools.markup import RSTParseVegaLite, rst_syntax_for_class
from tools.schemapi.schemapi import _resolve_references as resolve_references
if TYPE_CHECKING:
@@ -37,7 +34,6 @@
from pathlib import Path
from re import Pattern
- from mistune import BlockState
if sys.version_info >= (3, 12):
from typing import TypeAliasType
@@ -76,8 +72,7 @@
}
_VALID_IDENT: Pattern[str] = re.compile(r"^[^\d\W]\w*\Z", re.ASCII)
-_RE_LINK: Pattern[str] = re.compile(r"(?<=\[)([^\]]+)(?=\]\([^\)]+\))", re.MULTILINE)
-_RE_SPECIAL: Pattern[str] = re.compile(r"[*_]{2,3}|`", re.MULTILINE)
+
_RE_LIST_MISSING_ASTERISK: Pattern[str] = re.compile(r"^-(?=[ `\"a-z])", re.MULTILINE)
_RE_LIST_MISSING_WHITESPACE: Pattern[str] = re.compile(r"^\*(?=[`\"a-z])", re.MULTILINE)
@@ -1083,30 +1078,6 @@ def import_typing_extensions(
"""
-class RSTRenderer(_RSTRenderer):
- def __init__(self) -> None:
- super().__init__()
-
- def inline_html(self, token: dict[str, Any], state: BlockState) -> str:
- html = token["raw"]
- return rf"\ :raw-html:`{html}`\ "
-
-
-class RSTParse(mistune.Markdown):
- def __init__(
- self,
- renderer: mistune.BaseRenderer,
- block: mistune.BlockParser | None = None,
- inline: mistune.InlineParser | None = None,
- plugins=None,
- ) -> None:
- super().__init__(renderer, block, inline, plugins)
-
- def __call__(self, s: str) -> str:
- s = super().__call__(s) # pyright: ignore[reportAssignmentType]
- return unescape(s).replace(r"\ ,", ",").replace(r"\ ", " ")
-
-
def indent_docstring( # noqa: C901
lines: Iterable[str], indent_level: int, width: int = 100, lstrip=True
) -> str:
@@ -1192,31 +1163,10 @@ def fix_docstring_issues(docstring: str) -> str:
)
-def rst_syntax_for_class(class_name: str) -> str:
- return f":class:`{class_name}`"
-
-
-rst_parse: RSTParse = RSTParse(RSTRenderer())
+rst_parse: RSTParseVegaLite = RSTParseVegaLite()
# TODO: Investigate `mistune.Markdown.(before|after)_render_hooks`.
def process_description(description: str) -> str:
"""Parse a JSON encoded markdown description into an `RST` string."""
- # remove formatting from links
- description = "".join(
- _RE_SPECIAL.sub("", d) if i % 2 else d
- for i, d in enumerate(_RE_LINK.split(description))
- )
- description = rst_parse(description)
- # Some entries in the Vega-Lite schema miss the second occurence of '__'
- description = description.replace("__Default value: ", "__Default value:__ ")
- # Links to the vega-lite documentation cannot be relative but instead need to
- # contain the full URL.
- description = description.replace(
- "types#datetime", "https://vega.github.io/vega-lite/docs/datetime.html"
- )
- # Fixing ambiguous unicode, RUF001 produces RUF002 in docs
- 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()
+ return rst_parse(description)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index d77ba2433..677cc6547 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -27,9 +27,9 @@
import mistune
import mistune.util
+from tools.markup import RSTParse, Token, read_ast_tokens
+from tools.markup import RSTRenderer as _RSTRenderer
from tools.schemapi.schemapi import SchemaBase as _SchemaBase
-from tools.schemapi.utils import RSTParse as _RSTParse
-from tools.schemapi.utils import RSTRenderer as _RSTRenderer
from tools.schemapi.utils import (
ruff_write_lint_format_str as _ruff_write_lint_format_str,
)
@@ -38,7 +38,7 @@
import sys
from re import Match, Pattern
- from mistune import BlockParser, BlockState, InlineParser
+ from mistune import BlockState
if sys.version_info >= (3, 11):
from typing import LiteralString, Self, TypeAlias
@@ -48,7 +48,7 @@
__all__ = ["render_expr_full", "test_parse", "write_expr_module"]
-Token: TypeAlias = "dict[str, Any]"
+
WorkInProgress: TypeAlias = Any
"""Marker for a type that will not be final."""
@@ -100,6 +100,20 @@ class Source(str, enum.Enum):
IGNORE_MISC = r"# type: ignore[misc]"
+def _override_predicate(obj: Any, /) -> bool:
+ return (
+ callable(obj)
+ and (name := obj.__name__)
+ and isinstance(name, str)
+ and not (name.startswith("_"))
+ )
+
+
+_SCHEMA_BASE_MEMBERS: frozenset[str] = frozenset(
+ nm for nm, _ in getmembers(_SchemaBase, _override_predicate)
+)
+
+
def download_expressions_md(url: str, /) -> Path:
"""Download to a temporary file, return that as a ``pathlib.Path``."""
tmp, _ = request.urlretrieve(url)
@@ -114,15 +128,6 @@ def download_expressions_md(url: str, /) -> Path:
return fp
-def read_tokens(source: Path, /) -> list[Token]:
- """
- Read from ``source``, drop ``BlockState``.
-
- Factored out to provide accurate typing.
- """
- return mistune.create_markdown(renderer="ast").read(source)[0]
-
-
def strip_include_tag(s: str, /) -> str:
"""
Removes `liquid`_ templating markup.
@@ -133,18 +138,12 @@ def strip_include_tag(s: str, /) -> str:
return LIQUID_INCLUDE.sub(r"", s)
-def _override_predicate(obj: Any, /) -> bool:
- return (
- callable(obj)
- and (name := obj.__name__)
- and isinstance(name, str)
- and not (name.startswith("_"))
- )
-
-
-_SCHEMA_BASE_MEMBERS: frozenset[str] = frozenset(
- nm for nm, _ in getmembers(_SchemaBase, _override_predicate)
-)
+def expand_urls(url: str, /) -> str:
+ if url.startswith("#"):
+ url = f"{EXPRESSIONS_DOCS_URL}{url}"
+ else:
+ url = url.replace(r"../", VEGA_DOCS_URL)
+ return url
class RSTRenderer(_RSTRenderer):
@@ -160,11 +159,7 @@ def link(self, token: Token, state: BlockState) -> str:
- Parameterize `"#"`, `"../"` expansion during init
"""
attrs = token["attrs"]
- url: str = attrs["url"]
- if url.startswith("#"):
- url = f"{EXPRESSIONS_DOCS_URL}{url}"
- else:
- url = url.replace(r"../", VEGA_DOCS_URL)
+ url = expand_urls(attrs["url"])
text = self.render_children(token, state)
text = text.replace("`", "")
inline = f"`{text}`_"
@@ -175,55 +170,17 @@ def text(self, token: Token, state: BlockState) -> str:
text = super().text(token, state)
return strip_include_tag(text)
+ def _with_links(self, s: str, links: dict[str, Any] | Any, /) -> str:
+ it = chain.from_iterable(
+ (f".. _{ref_name}:", f" {attrs['url']}")
+ for ref_name, attrs in links.items()
+ )
+ return "\n".join(chain([s], it))
-def _iter_link_lines(ref_links: Any, /) -> Iterator[str]:
- links: dict[str, Any] = ref_links
- for ref_name, attrs in links.items():
- yield from (f".. _{ref_name}:", f" {attrs['url']}")
-
-
-class RSTParse(_RSTParse):
- """
- Minor extension to support partial `ast`_ conversion.
-
- Only need to convert the docstring tokens to `.rst`.
-
- NOTE
- ----
- Once `PR`_ is merged, move this to the parent class and rename
-
- .. _ast:
- https://mistune.lepture.com/en/latest/guide.html#abstract-syntax-tree
- .. _PR:
- https://github.com/vega/altair/pull/3536
- """
-
- def __init__(
- self,
- renderer: RSTRenderer,
- block: BlockParser | None = None,
- inline: InlineParser | None = None,
- plugins=None,
- ) -> None:
- super().__init__(renderer, block, inline, plugins)
- if self.renderer is None:
- msg = "Must provide a renderer, got `None`"
- raise TypeError(msg)
- self.renderer: RSTRenderer
-
- def render_tokens(self, tokens: Iterable[Token], /) -> str:
- """
- Render ast tokens originating from another parser.
-
- Parameters
- ----------
- tokens
- All tokens will be rendered into a single `.rst` string
- """
- state = self.block.state_cls()
- result = self.renderer(self._iter_render(tokens, state), state)
+ def __call__(self, tokens: Iterable[Token], state: BlockState) -> str:
+ result = super().__call__(tokens, state)
if links := state.env.get("ref_links", {}):
- return "\n".join(chain([result], _iter_link_lines(links)))
+ return self._with_links(result, links)
else:
return result
@@ -748,7 +705,7 @@ def iter_params(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
def _parse_expressions(url: str, /) -> Iterator[VegaExprNode]:
"""Download, read markdown and iteratively parse into signature representations."""
- for tok in read_tokens(download_expressions_md(url)):
+ for tok in read_ast_tokens(download_expressions_md(url)):
if (
(children := tok.get(CHILDREN)) is not None
and (child := next(iter(children)).get(RAW)) is not None
From 9aaf86284493afd197d8af6c10dfb475bc671874 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 16:00:10 +0100
Subject: [PATCH 52/77] chore: Remove debugging code
https://github.com/vega/altair/pull/3600#discussion_r1775374246, https://github.com/vega/altair/pull/3600#discussion_r1775380576
---
tools/schemapi/vega_expr.py | 38 +------------------------------------
1 file changed, 1 insertion(+), 37 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 677cc6547..1ed4b5c4c 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -46,7 +46,7 @@
from typing_extensions import LiteralString, Self, TypeAlias
from _typeshed import SupportsKeysAndGetItem
-__all__ = ["render_expr_full", "test_parse", "write_expr_module"]
+__all__ = ["write_expr_module"]
WorkInProgress: TypeAlias = Any
@@ -798,7 +798,6 @@ def PI(cls) -> {return_ann}:
return {const}("PI")
'''
-
EXPR_MODULE_POST = """\
_ExprType = expr
# NOTE: Compatibility alias for previous type of `alt.expr`.
@@ -869,15 +868,6 @@ def __new__(cls: type[{base}], expr: str) -> {base}: {type_ignore}
'''
-def render_expr_cls() -> WorkInProgress:
- return EXPR_CLS_TEMPLATE.format(
- base="_ExprRef",
- metaclass=CONST_META,
- doc=EXPR_CLS_DOC,
- type_ignore=IGNORE_MISC,
- )
-
-
def render_expr_method(node: VegaExprNode, /) -> WorkInProgress:
if node.is_overloaded():
body_params = STAR_ARGS[1:]
@@ -893,32 +883,6 @@ def render_expr_method(node: VegaExprNode, /) -> WorkInProgress:
)
-def test_parse() -> dict[str, VegaExprNode]:
- """Temporary introspection tool."""
- return {node.name: node for node in parse_expressions(Source.LIVE.value)}
-
-
-def render_expr_full() -> str:
- """Temporary sample of **pre-ruff** module."""
- it = (render_expr_method(node) for node in parse_expressions(Source.LIVE.value))
- return "\n".join(
- chain(
- (
- EXPR_MODULE_PRE.format(
- metaclass=CONST_META,
- const=CONST_WRAPPER,
- return_ann=RETURN_ANNOTATION,
- input_ann=INPUT_ANNOTATION,
- func=RETURN_WRAPPER,
- ),
- render_expr_cls(),
- ),
- it,
- [EXPR_MODULE_POST],
- )
- )
-
-
def write_expr_module(
source_url: Literal["live", "static"] | str, output: Path
) -> None:
From 9215d7d31dfb82e906cf9a28eb0c27ccb7a13b0e Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 16:21:26 +0100
Subject: [PATCH 53/77] refactor: Replace `render_expr_method` with a method
Also moved some static content to template and simplified
https://github.com/vega/altair/pull/3600#discussion_r1775356969, https://github.com/vega/altair/pull/3600#discussion_r1775376732, https://github.com/vega/altair/pull/3600#discussion_r1775379947
---
tools/schemapi/vega_expr.py | 37 ++++++++++++++++++++-----------------
1 file changed, 20 insertions(+), 17 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 1ed4b5c4c..efbf030ad 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -416,6 +416,24 @@ def parameter_names(self, *, variadic: bool = True) -> Iterator[str]:
)
raise TypeError(msg)
+ def render(self) -> str:
+ if self.is_overloaded():
+ body_params = STAR_ARGS[1:]
+ else:
+ body_params = (
+ f"({self.parameters[0].name},)"
+ if len(self.parameters) == 1
+ else f"({','.join(self.parameter_names())})"
+ )
+ return EXPR_METHOD_TEMPLATE.format(
+ decorator=DECORATOR,
+ signature=self.signature,
+ doc=self.doc,
+ return_wrapper=RETURN_WRAPPER,
+ name=f"{self.name!r}",
+ body_params=body_params,
+ )
+
@property
def title(self) -> str:
"""
@@ -864,25 +882,10 @@ def __new__(cls: type[{base}], expr: str) -> {base}: {type_ignore}
"""
{doc}
"""
- {body}
+ return {return_wrapper}({name}, {body_params})
'''
-def render_expr_method(node: VegaExprNode, /) -> WorkInProgress:
- if node.is_overloaded():
- body_params = STAR_ARGS[1:]
- else:
- body_params = ", ".join(node.parameter_names())
- if "," not in body_params:
- body_params = f"({body_params}, )"
- else:
- body_params = f"({body_params})"
- body = f"return {RETURN_WRAPPER}({node.name!r}, {body_params})"
- return EXPR_METHOD_TEMPLATE.format(
- decorator=DECORATOR, signature=node.signature, doc=node.doc, body=body
- )
-
-
def write_expr_module(
source_url: Literal["live", "static"] | str, output: Path
) -> None:
@@ -921,7 +924,7 @@ def write_expr_module(
)
contents = chain(
content,
- (render_expr_method(node) for node in parse_expressions(url)),
+ (node.render() for node in parse_expressions(url)),
[EXPR_MODULE_POST],
)
print(f"Generating\n {url!s}\n ->{output!s}")
From 7a10e3da511c23f10800b2d143581dfa49f48c14 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 18:59:20 +0100
Subject: [PATCH 54/77] refactor: Add signature template, rename others
The `EXPR_` prefix is meaningless when all use it
---
tools/schemapi/vega_expr.py | 48 +++++++++++++++++++++----------------
1 file changed, 28 insertions(+), 20 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index efbf030ad..eaf81c12a 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -388,16 +388,20 @@ def with_signature(self) -> Self:
Accessible via ``self.signature``
"""
- pre_params = f"def {self.title}(cls, "
- post_params = ")" if self.is_variadic() else ", /)"
- post_params = f"{post_params} -> {RETURN_ANNOTATION}:"
- if self.is_incompatible_override():
- post_params = f"{post_params} {IGNORE_OVERRIDE}"
- if self.is_overloaded():
- param_list = VegaExprParam.star_args()
- else:
- param_list = ", ".join(p.to_str() for p in self.parameters)
- self.signature = f"{pre_params}{param_list}{post_params}"
+ param_list = (
+ VegaExprParam.star_args()
+ if self.is_overloaded()
+ else ", ".join(p.to_str() for p in self.parameters)
+ )
+ self.signature = METHOD_SIGNATURE.format(
+ title=self.title,
+ param_list=param_list,
+ marker="" if self.is_variadic() else ", /",
+ return_ann=RETURN_ANNOTATION,
+ type_ignore=(
+ f" {IGNORE_OVERRIDE}" if self.is_incompatible_override() else ""
+ ),
+ )
return self
def parameter_names(self, *, variadic: bool = True) -> Iterator[str]:
@@ -425,7 +429,7 @@ def render(self) -> str:
if len(self.parameters) == 1
else f"({','.join(self.parameter_names())})"
)
- return EXPR_METHOD_TEMPLATE.format(
+ return METHOD_TEMPLATE.format(
decorator=DECORATOR,
signature=self.signature,
doc=self.doc,
@@ -747,7 +751,7 @@ def parse_expressions(url: str, /) -> Iterator[VegaExprNode]:
yield node.with_doc()
-EXPR_MODULE_PRE = '''\
+MODULE_PRE = '''\
"""Tools for creating transform & filter expressions with a python syntax."""
from __future__ import annotations
@@ -816,13 +820,13 @@ def PI(cls) -> {return_ann}:
return {const}("PI")
'''
-EXPR_MODULE_POST = """\
+MODULE_POST = """\
_ExprType = expr
# NOTE: Compatibility alias for previous type of `alt.expr`.
# `_ExprType` was not referenced in any internal imports/tests.
"""
-EXPR_CLS_DOC = """
+CLS_DOC = """
Utility providing *constants* and *classmethods* to construct expressions.
`Expressions`_ can be used to write basic formulas that enable custom interactions.
@@ -867,7 +871,7 @@ def PI(cls) -> {return_ann}:
})
"""
-EXPR_CLS_TEMPLATE = '''\
+CLS_TEMPLATE = '''\
class expr({base}, metaclass={metaclass}):
"""{doc}"""
@@ -876,7 +880,11 @@ def __new__(cls: type[{base}], expr: str) -> {base}: {type_ignore}
return {base}(expr=expr)
'''
-EXPR_METHOD_TEMPLATE = '''\
+METHOD_SIGNATURE = (
+ """def {title}(cls, {param_list}{marker}) -> {return_ann}:{type_ignore}"""
+)
+
+METHOD_TEMPLATE = '''\
{decorator}
{signature}
"""
@@ -908,24 +916,24 @@ def write_expr_module(
else:
url = source_url
content = (
- EXPR_MODULE_PRE.format(
+ MODULE_PRE.format(
metaclass=CONST_META,
const=CONST_WRAPPER,
return_ann=RETURN_ANNOTATION,
input_ann=INPUT_ANNOTATION,
func=RETURN_WRAPPER,
),
- EXPR_CLS_TEMPLATE.format(
+ CLS_TEMPLATE.format(
base="_ExprRef",
metaclass=CONST_META,
- doc=EXPR_CLS_DOC,
+ doc=CLS_DOC,
type_ignore=IGNORE_MISC,
),
)
contents = chain(
content,
(node.render() for node in parse_expressions(url)),
- [EXPR_MODULE_POST],
+ [MODULE_POST],
)
print(f"Generating\n {url!s}\n ->{output!s}")
_ruff_write_lint_format_str(output, contents)
From e3273f6eb1e0df07cf3e892d24f5db24a1ee1454 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 19:02:37 +0100
Subject: [PATCH 55/77] docs: Update metaclass description
https://github.com/vega/altair/pull/3600#discussion_r1775392083
---
altair/expr/__init__.py | 6 +++++-
tools/schemapi/vega_expr.py | 12 ++++++++----
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py
index c61dddbfc..e89ef6a3f 100644
--- a/altair/expr/__init__.py
+++ b/altair/expr/__init__.py
@@ -18,7 +18,11 @@
class _ConstExpressionType(type):
- """Metaclass providing read-only class properties for :class:`expr`."""
+ """
+ Metaclass for :class:`expr`.
+
+ Currently providing read-only class properties, representing JavaScript constants.
+ """
@property
def NaN(cls) -> Expression:
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index eaf81c12a..3b80f8948 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -89,7 +89,7 @@ class Source(str, enum.Enum):
# NOTE: No benefit to annotating with the actual wrapper
# - `Expression` is shorter, and has all the functionality/attributes
CONST_WRAPPER = "ConstExpression"
-CONST_META = "_ConstExpressionType"
+CLS_META = "_ConstExpressionType"
INPUT_ANNOTATION = "IntoExpression"
# NOTE: `python`/`mypy` related literals
@@ -772,7 +772,11 @@ def parse_expressions(url: str, /) -> Iterator[VegaExprNode]:
class {metaclass}(type):
- """Metaclass providing read-only class properties for :class:`expr`."""
+ """
+ Metaclass for :class:`expr`.
+
+ Currently providing read-only class properties, representing JavaScript constants.
+ """
@property
def NaN(cls) -> {return_ann}:
@@ -917,7 +921,7 @@ def write_expr_module(
url = source_url
content = (
MODULE_PRE.format(
- metaclass=CONST_META,
+ metaclass=CLS_META,
const=CONST_WRAPPER,
return_ann=RETURN_ANNOTATION,
input_ann=INPUT_ANNOTATION,
@@ -925,7 +929,7 @@ def write_expr_module(
),
CLS_TEMPLATE.format(
base="_ExprRef",
- metaclass=CONST_META,
+ metaclass=CLS_META,
doc=CLS_DOC,
type_ignore=IGNORE_MISC,
),
From 1a7d2417d0a6a45c3f234c4226bbdf783105631a Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 19:06:52 +0100
Subject: [PATCH 56/77] refactor: Rename `_ConstExpressionType` -> `_ExprMeta`
---
altair/expr/__init__.py | 4 ++--
tests/expr/test_expr.py | 6 +++---
tools/schemapi/vega_expr.py | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py
index e89ef6a3f..bdaa54819 100644
--- a/altair/expr/__init__.py
+++ b/altair/expr/__init__.py
@@ -17,7 +17,7 @@
from altair.expr.core import Expression, IntoExpression
-class _ConstExpressionType(type):
+class _ExprMeta(type):
"""
Metaclass for :class:`expr`.
@@ -70,7 +70,7 @@ def PI(cls) -> Expression:
return ConstExpression("PI")
-class expr(_ExprRef, metaclass=_ConstExpressionType):
+class expr(_ExprRef, metaclass=_ExprMeta):
"""
Utility providing *constants* and *classmethods* to construct expressions.
diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py
index d0523cc33..2170c1e36 100644
--- a/tests/expr/test_expr.py
+++ b/tests/expr/test_expr.py
@@ -9,7 +9,7 @@
from jsonschema.exceptions import ValidationError
from altair import datum, expr, ExprRef
-from altair.expr import _ConstExpressionType
+from altair.expr import _ExprMeta
from altair.expr.core import Expression, GetAttrExpression
if TYPE_CHECKING:
@@ -112,7 +112,7 @@ def test_expr_methods(
assert repr(fn_call) == f"{veganame}({datum_args})"
-@pytest.mark.parametrize("constname", _get_property_names(_ConstExpressionType))
+@pytest.mark.parametrize("constname", _get_property_names(_ExprMeta))
def test_expr_consts(constname: str):
"""Test all constants defined in expr.consts."""
const = getattr(expr, constname)
@@ -120,7 +120,7 @@ def test_expr_consts(constname: str):
assert repr(z) == f"({constname} * datum.xxx)"
-@pytest.mark.parametrize("constname", _get_property_names(_ConstExpressionType))
+@pytest.mark.parametrize("constname", _get_property_names(_ExprMeta))
def test_expr_consts_immutable(constname: str):
"""Ensure e.g `alt.expr.PI = 2` is prevented."""
if sys.version_info >= (3, 11):
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 3b80f8948..c68b1d30e 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -89,7 +89,7 @@ class Source(str, enum.Enum):
# NOTE: No benefit to annotating with the actual wrapper
# - `Expression` is shorter, and has all the functionality/attributes
CONST_WRAPPER = "ConstExpression"
-CLS_META = "_ConstExpressionType"
+CLS_META = "_ExprMeta"
INPUT_ANNOTATION = "IntoExpression"
# NOTE: `python`/`mypy` related literals
From cfb676f8ee0b939d8e5f0e91160dbeaeae4a8e71 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Wed, 25 Sep 2024 20:59:27 +0100
Subject: [PATCH 57/77] refactor: Align `VegaExpr(Node|Param)` apis
- Both have a `.from_..()` iterator classmethod
- Both output via `.render()`
https://github.com/vega/altair/pull/3600#discussion_r1775371829
---
tools/schemapi/vega_expr.py | 66 +++++++++++++++++++++----------------
1 file changed, 37 insertions(+), 29 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index c68b1d30e..e5a8c9212 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -41,16 +41,12 @@
from mistune import BlockState
if sys.version_info >= (3, 11):
- from typing import LiteralString, Self, TypeAlias
+ from typing import LiteralString, Self
else:
- from typing_extensions import LiteralString, Self, TypeAlias
+ from typing_extensions import LiteralString, Self
from _typeshed import SupportsKeysAndGetItem
-__all__ = ["write_expr_module"]
-
-
-WorkInProgress: TypeAlias = Any
-"""Marker for a type that will not be final."""
+__all__ = ["parse_expressions", "write_expr_module"]
# NOTE: Urls/fragments
@@ -379,7 +375,7 @@ def with_parameters(self) -> Self:
Accessible via ``self.parameters``.
"""
split: Iterator[str] = self._split_signature_tokens(exclude_name=True)
- self.parameters = list(VegaExprParam.iter_params(split))
+ self.parameters = list(VegaExprParam.from_texts(split))
return self
def with_signature(self) -> Self:
@@ -391,7 +387,7 @@ def with_signature(self) -> Self:
param_list = (
VegaExprParam.star_args()
if self.is_overloaded()
- else ", ".join(p.to_str() for p in self.parameters)
+ else ", ".join(p.render() for p in self.parameters)
)
self.signature = METHOD_SIGNATURE.format(
title=self.title,
@@ -685,6 +681,30 @@ def __repr__(self) -> str:
")"
)
+ @classmethod
+ def from_tokens(cls, tokens: Iterable[Token], /) -> Iterator[Self]:
+ """
+ Lazy, filtered partial parser.
+
+ Applies a series of filters before rendering everything but the docs.
+
+ Parameters
+ ----------
+ tokens
+ `ast tokens`_ produced by ``mistune``
+
+ .. _ast tokens:
+ https://mistune.lepture.com/en/latest/guide.html#abstract-syntax-tree
+ """
+ for tok in tokens:
+ if (
+ (children := tok.get(CHILDREN)) is not None
+ and (child := next(iter(children)).get(RAW)) is not None
+ and (match := FUNCTION_DEF_LINE.match(child))
+ and (node := cls(match[1], children)).is_callable()
+ ):
+ yield node.with_parameters().with_signature()
+
@dataclasses.dataclass
class VegaExprParam:
@@ -696,7 +716,7 @@ class VegaExprParam:
def star_args() -> LiteralString:
return f"{STAR_ARGS}: Any"
- def to_str(self) -> str:
+ def render(self) -> str:
"""Return as an annotated parameter, with a default if needed."""
if self.required:
return f"{self.name}: {INPUT_ANNOTATION}"
@@ -706,7 +726,7 @@ def to_str(self) -> str:
return self.star_args()
@classmethod
- def iter_params(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
+ def from_texts(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
"""Yields an ordered parameter list."""
is_required: bool = True
for s in raw_texts:
@@ -725,29 +745,17 @@ def iter_params(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
continue
-def _parse_expressions(url: str, /) -> Iterator[VegaExprNode]:
- """Download, read markdown and iteratively parse into signature representations."""
- for tok in read_ast_tokens(download_expressions_md(url)):
- if (
- (children := tok.get(CHILDREN)) is not None
- and (child := next(iter(children)).get(RAW)) is not None
- and (match := FUNCTION_DEF_LINE.match(child))
- ):
- node = VegaExprNode(match[1], children)
- if node.is_callable():
- yield node.with_parameters().with_signature()
- request.urlcleanup()
-
-
def parse_expressions(url: str, /) -> Iterator[VegaExprNode]:
"""
- Eagerly parse signatures of relevant definitions, then yield with docs.
+ Download, read markdown and eagerly parse signatures of relevant definitions.
- Ensures each doc can use all remapped names, regardless of the order they appear.
+ Yields with docs to ensure each can use all remapped names, regardless of the order they appear.
"""
- eager = tuple(_parse_expressions(url))
+ tokens = read_ast_tokens(download_expressions_md(url))
+ expr_nodes = tuple(VegaExprNode.from_tokens(tokens))
+ request.urlcleanup()
VegaExprNode.remap_title.refresh()
- for node in eager:
+ for node in expr_nodes:
yield node.with_doc()
From 6fd32e9fe6de2e43bd6ac7df2cfd337c2c869c66 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 26 Sep 2024 13:26:14 +0100
Subject: [PATCH 58/77] refactor: Factor out to `italics_to_backticks`
https://github.com/vega/altair/pull/3600#discussion_r1775367672
---
tools/schemapi/vega_expr.py | 40 ++++++++++++++++++++++++++++---------
1 file changed, 31 insertions(+), 9 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index e5a8c9212..8c831bc01 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -340,6 +340,34 @@ def __repr__(self) -> str:
return f"{type(self).__name__}(\n {self._mapping!r}\n)"
+def italics_to_backticks(s: str, names: Iterable[str], /) -> str:
+ """
+ Perform a targeted replacement, considering links.
+
+ Parameters
+ ----------
+ s
+ String containing rendered `.rst`.
+ names
+ Group of names the replacement applies to.
+
+ Notes
+ -----
+ - Avoids adding backticks to parameter names that are also used in a link.
+ - All cases of these are for `unit|units`.
+
+ Examples
+ --------
+ >>> italics_to_backticks(
+ ... "some text and *name* and more text but also *other* text",
+ ... ("name", "other"),
+ ... )
+ "some text and ``name`` and more text but also ``other`` text"
+ """
+ pattern = rf"(?P[^`_])\*(?P{'|'.join(names)})\*(?P[^`])"
+ return re.sub(pattern, r"\g``\g``\g", s)
+
+
class VegaExprNode:
"""
``SchemaInfo``-like, but operates on `expressions.md`_.
@@ -538,20 +566,14 @@ def _doc_tokens(self) -> Sequence[Token]:
)
raise NotImplementedError(msg)
- def _doc_post_process(self, rendered: str, /) -> str:
+ def _doc_post_process(self, s: str, /) -> str:
"""
Utilizing properties found during parsing to improve docs.
Temporarily handling this here.
"""
- # NOTE: Avoids adding backticks to parameter names that are also used in a link
- # - All cases of these are for `unit|units`
- pre, post = "[^`_]", "[^`]"
- pattern = (
- rf"({pre})\*({'|'.join(self.parameter_names(variadic=False))})\*({post})"
- )
- highlight_params = re.sub(pattern, r"\g<1>``\g<2>``\g<3>", rendered)
- with_alt_references = type(self).remap_title(highlight_params)
+ backtick_params = italics_to_backticks(s, self.parameter_names(variadic=False))
+ with_alt_references = type(self).remap_title(backtick_params)
unescaped = mistune.util.unescape(with_alt_references)
numpydoc_style = _doc_fmt(unescaped)
return numpydoc_style
From b2aeecb10008a8d6e57cf479be3b7d0f20097a5b Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 26 Sep 2024 15:41:30 +0100
Subject: [PATCH 59/77] feat: Strip template markup earlier
- Moves a replacement that previously occured in two places (rendering & signatuyre parsing)
- Now the tokens returned by `read_ast_tokens` do not contain this at all
https://github.com/vega/altair/pull/3600#discussion_r1777033906
---
tools/markup.py | 28 ++++++++++++++++++++++------
tools/schemapi/vega_expr.py | 17 +----------------
2 files changed, 23 insertions(+), 22 deletions(-)
diff --git a/tools/markup.py b/tools/markup.py
index fc1af9cfc..c2a01c640 100644
--- a/tools/markup.py
+++ b/tools/markup.py
@@ -6,7 +6,8 @@
from html import unescape
from typing import TYPE_CHECKING, Any, Iterable, Literal
-import mistune
+from mistune import InlineParser as _InlineParser
+from mistune import Markdown as _Markdown
from mistune.renderers.rst import RSTRenderer as _RSTRenderer
if TYPE_CHECKING:
@@ -19,12 +20,13 @@
from typing_extensions import TypeAlias
from re import Pattern
- from mistune import BaseRenderer, BlockParser, BlockState, InlineParser
+ from mistune import BaseRenderer, BlockParser, BlockState, InlineState
Token: TypeAlias = "dict[str, Any]"
_RE_LINK: Pattern[str] = re.compile(r"(?<=\[)([^\]]+)(?=\]\([^\)]+\))", re.MULTILINE)
_RE_SPECIAL: Pattern[str] = re.compile(r"[*_]{2,3}|`", re.MULTILINE)
+_RE_LIQUID_INCLUDE: Pattern[str] = re.compile(r"( \{% include.+%\})")
class RSTRenderer(_RSTRenderer):
@@ -36,7 +38,7 @@ def inline_html(self, token: Token, state: BlockState) -> str:
return rf"\ :raw-html:`{html}`\ "
-class RSTParse(mistune.Markdown):
+class RSTParse(_Markdown):
"""
Minor extension to support partial `ast`_ conversion.
@@ -50,7 +52,7 @@ def __init__(
self,
renderer: BaseRenderer | Literal["ast"] | None,
block: BlockParser | None = None,
- inline: InlineParser | None = None,
+ inline: _InlineParser | None = None,
plugins=None,
) -> None:
if renderer == "ast":
@@ -82,7 +84,7 @@ def __init__(
self,
renderer: RSTRenderer | None = None,
block: BlockParser | None = None,
- inline: InlineParser | None = None,
+ inline: _InlineParser | None = None,
plugins=None,
) -> None:
super().__init__(renderer or RSTRenderer(), block, inline, plugins)
@@ -109,13 +111,27 @@ def __call__(self, s: str) -> str:
return description.strip()
+class InlineParser(_InlineParser):
+ def __init__(self, hard_wrap: bool = False) -> None:
+ super().__init__(hard_wrap)
+
+ def process_text(self, text: str, state: InlineState) -> None:
+ """
+ Removes `liquid`_ templating markup.
+
+ .. _liquid:
+ https://shopify.github.io/liquid/
+ """
+ state.append_token({"type": "text", "raw": _RE_LIQUID_INCLUDE.sub(r"", text)})
+
+
def read_ast_tokens(source: Path, /) -> list[Token]:
"""
Read from ``source``, drop ``BlockState``.
Factored out to provide accurate typing.
"""
- return mistune.create_markdown(renderer="ast").read(source)[0]
+ return _Markdown(renderer=None, inline=InlineParser()).read(source)[0]
def rst_syntax_for_class(class_name: str) -> str:
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 8c831bc01..c10c6c8cb 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -61,7 +61,6 @@ class Source(str, enum.Enum):
# NOTE: Regex patterns
FUNCTION_DEF_LINE: Pattern[str] = re.compile(r"")
-LIQUID_INCLUDE: Pattern[str] = re.compile(r"( \{% include.+%\})")
SENTENCE_BREAK: Pattern[str] = re.compile(r"(? Path:
return fp
-def strip_include_tag(s: str, /) -> str:
- """
- Removes `liquid`_ templating markup.
-
- .. _liquid:
- https://shopify.github.io/liquid/
- """
- return LIQUID_INCLUDE.sub(r"", s)
-
-
def expand_urls(url: str, /) -> str:
if url.startswith("#"):
url = f"{EXPRESSIONS_DOCS_URL}{url}"
@@ -162,10 +151,6 @@ def link(self, token: Token, state: BlockState) -> str:
state.env["ref_links"][text] = {"url": url}
return inline
- def text(self, token: Token, state: BlockState) -> str:
- text = super().text(token, state)
- return strip_include_tag(text)
-
def _with_links(self, s: str, links: dict[str, Any] | Any, /) -> str:
it = chain.from_iterable(
(f".. _{ref_name}:", f" {attrs['url']}")
@@ -518,7 +503,7 @@ def _split_signature_tokens(self, *, exclude_name: bool = False) -> Iterator[str
"""
EXCLUDE: set[str] = {", ", "", self.name} if exclude_name else {", ", ""}
for tok in self._signature_tokens():
- clean = strip_include_tag(tok[RAW]).strip(", -")
+ clean = tok[RAW].strip(", -")
if clean not in EXCLUDE:
yield from VegaExprNode._split_markers(clean)
From fde31e25835f3144515faa8bbb984dfbb7f350ea Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 26 Sep 2024 17:35:32 +0100
Subject: [PATCH 60/77] refactor: Move `unescape` to `render_tokens`
Step is unrelated to parsed attributes of a definition
---
tools/markup.py | 4 +++-
tools/schemapi/vega_expr.py | 7 +------
2 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/tools/markup.py b/tools/markup.py
index c2a01c640..d9e8230ec 100644
--- a/tools/markup.py
+++ b/tools/markup.py
@@ -6,6 +6,7 @@
from html import unescape
from typing import TYPE_CHECKING, Any, Iterable, Literal
+import mistune.util
from mistune import InlineParser as _InlineParser
from mistune import Markdown as _Markdown
from mistune.renderers.rst import RSTRenderer as _RSTRenderer
@@ -76,7 +77,8 @@ def render_tokens(self, tokens: Iterable[Token], /) -> str:
msg = "Unable to render tokens without a renderer."
raise TypeError(msg)
state = self.block.state_cls()
- return self.renderer(self._iter_render(tokens, state), state)
+ s = self.renderer(self._iter_render(tokens, state), state)
+ return mistune.util.unescape(s)
class RSTParseVegaLite(RSTParse):
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index c10c6c8cb..8525996a9 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -24,9 +24,6 @@
)
from urllib import request
-import mistune
-import mistune.util
-
from tools.markup import RSTParse, Token, read_ast_tokens
from tools.markup import RSTRenderer as _RSTRenderer
from tools.schemapi.schemapi import SchemaBase as _SchemaBase
@@ -559,9 +556,7 @@ def _doc_post_process(self, s: str, /) -> str:
"""
backtick_params = italics_to_backticks(s, self.parameter_names(variadic=False))
with_alt_references = type(self).remap_title(backtick_params)
- unescaped = mistune.util.unescape(with_alt_references)
- numpydoc_style = _doc_fmt(unescaped)
- return numpydoc_style
+ return _doc_fmt(with_alt_references)
def is_callable(self) -> bool:
"""
From d4d9145eba5a25d4edfb4fa27cd05c993a6e0e7f Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 26 Sep 2024 17:41:27 +0100
Subject: [PATCH 61/77] refactor: Move `_doc_post_process` -> `with_doc`
This method was always intended to be temorary and is no longer helpful
---
tools/schemapi/vega_expr.py | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 8525996a9..b0680dbca 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -375,7 +375,10 @@ def with_doc(self) -> Self:
Accessible via ``self.doc``
"""
- self.doc = self._doc_post_process(parser.render_tokens(self._doc_tokens()))
+ s: str = parser.render_tokens(self._doc_tokens())
+ s = italics_to_backticks(s, self.parameter_names(variadic=False))
+ s = type(self).remap_title(s)
+ self.doc = _doc_fmt(s)
return self
def with_parameters(self) -> Self:
@@ -548,16 +551,6 @@ def _doc_tokens(self) -> Sequence[Token]:
)
raise NotImplementedError(msg)
- def _doc_post_process(self, s: str, /) -> str:
- """
- Utilizing properties found during parsing to improve docs.
-
- Temporarily handling this here.
- """
- backtick_params = italics_to_backticks(s, self.parameter_names(variadic=False))
- with_alt_references = type(self).remap_title(backtick_params)
- return _doc_fmt(with_alt_references)
-
def is_callable(self) -> bool:
"""
Rough filter for excluding `constants`_.
From 433611bb9016e14b06514a88d031853efc832131 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 26 Sep 2024 17:53:33 +0100
Subject: [PATCH 62/77] refactor: Remove redundant branches in
`_override_predicate`
---
tools/schemapi/vega_expr.py | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index b0680dbca..c68cfe6b8 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -93,12 +93,7 @@ class Source(str, enum.Enum):
def _override_predicate(obj: Any, /) -> bool:
- return (
- callable(obj)
- and (name := obj.__name__)
- and isinstance(name, str)
- and not (name.startswith("_"))
- )
+ return callable(obj) and not (name := obj.__name__).startswith("_") # noqa: F841
_SCHEMA_BASE_MEMBERS: frozenset[str] = frozenset(
From c2af5f47bcf043e6734fd57c2e843bceef312f06 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 26 Sep 2024 18:42:46 +0100
Subject: [PATCH 63/77] refactor: Assign names to literals in `_doc_fmt`
Related to (but doesn't resolve) https://github.com/vega/altair/pull/3600#discussion_r1775348969
---
tools/schemapi/vega_expr.py | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index c68cfe6b8..bc29dae62 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -75,6 +75,9 @@ class Source(str, enum.Enum):
CLOSE_BRACKET: Literal["]"] = "]"
INLINE_OVERLOAD: Literal[" |"] = " |"
+METHOD_INDENT: LiteralString = 8 * " "
+SECTION_BREAK: Literal["\n\n"] = "\n\n"
+
# NOTE: `altair` types (for annotations)
RETURN_WRAPPER = "FunctionExpression"
RETURN_ANNOTATION = "Expression"
@@ -163,8 +166,8 @@ def __call__(self, tokens: Iterable[Token], state: BlockState) -> str:
width=100,
break_long_words=False,
break_on_hyphens=False,
- initial_indent=8 * " ",
- subsequent_indent=8 * " ",
+ initial_indent=METHOD_INDENT,
+ subsequent_indent=METHOD_INDENT,
)
@@ -180,13 +183,13 @@ def _doc_fmt(doc: str, /) -> str:
summary = f"{sentences.popleft()}.\n"
last_line = sentences.pop().strip()
sentences = deque(f"{s}. " for s in sentences)
- if "\n\n.. _" in last_line:
- last_line, references = last_line.split("\n\n", maxsplit=1)
+ if SECTION_BREAK in last_line:
+ last_line, references = last_line.split(SECTION_BREAK, maxsplit=1)
sentences.append(last_line)
sentences = deque(text_wrap.wrap("".join(sentences)))
sentences.appendleft(summary)
if references:
- sentences.extend(("", indent(references, 8 * " ")))
+ sentences.extend(("", indent(references, METHOD_INDENT)))
return "\n".join(sentences)
else:
return sentences.pop().strip()
From e7e79c967933ab9e9a7dd02527a5a34b046bcbec Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 26 Sep 2024 18:43:48 +0100
Subject: [PATCH 64/77] docs(typing): Add missing annotations
---
tools/schemapi/vega_expr.py | 28 ++++++++++++++++------------
1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index bc29dae62..7a4b52295 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -47,8 +47,8 @@
# NOTE: Urls/fragments
-VEGA_DOCS_URL = "https://vega.github.io/vega/docs/"
-EXPRESSIONS_DOCS_URL = f"{VEGA_DOCS_URL}expressions/"
+VEGA_DOCS_URL: LiteralString = "https://vega.github.io/vega/docs/"
+EXPRESSIONS_DOCS_URL: LiteralString = f"{VEGA_DOCS_URL}expressions/"
class Source(str, enum.Enum):
@@ -79,20 +79,24 @@ class Source(str, enum.Enum):
SECTION_BREAK: Literal["\n\n"] = "\n\n"
# NOTE: `altair` types (for annotations)
-RETURN_WRAPPER = "FunctionExpression"
-RETURN_ANNOTATION = "Expression"
-# NOTE: No benefit to annotating with the actual wrapper
-# - `Expression` is shorter, and has all the functionality/attributes
-CONST_WRAPPER = "ConstExpression"
-CLS_META = "_ExprMeta"
-INPUT_ANNOTATION = "IntoExpression"
+RETURN_WRAPPER: LiteralString = "FunctionExpression"
+RETURN_ANNOTATION: LiteralString = "Expression"
+"""
+The annotation is intentionally *less* specific than the real type.
+
+``Expression`` is shorter, while preserving all the user-facing functionality
+"""
+
+CONST_WRAPPER: LiteralString = "ConstExpression"
+CLS_META: LiteralString = "_ExprMeta"
+INPUT_ANNOTATION: LiteralString = "IntoExpression"
# NOTE: `python`/`mypy` related literals
NONE: Literal[r"None"] = r"None"
STAR_ARGS: Literal["*args"] = "*args"
-DECORATOR = r"@classmethod"
-IGNORE_OVERRIDE = r"# type: ignore[override]"
-IGNORE_MISC = r"# type: ignore[misc]"
+DECORATOR: LiteralString = r"@classmethod"
+IGNORE_OVERRIDE: LiteralString = r"# type: ignore[override]"
+IGNORE_MISC: LiteralString = r"# type: ignore[misc]"
def _override_predicate(obj: Any, /) -> bool:
From e20490bf8ed83f34ecb9936aed736b3c71c8d743 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Thu, 26 Sep 2024 19:47:44 +0100
Subject: [PATCH 65/77] refactor: Final tidy up, renaming
---
tools/schemapi/vega_expr.py | 458 ++++++++++++++++++------------------
1 file changed, 233 insertions(+), 225 deletions(-)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 7a4b52295..220243ce6 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -52,6 +52,8 @@
class Source(str, enum.Enum):
+ """Enumerations for ``expressions.md`` source files."""
+
LIVE = "https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md"
STATIC = "https://raw.githubusercontent.com/vega/vega/ff98519cce32b776a98d01dd982467d76fc9ee34/docs/docs/expressions.md"
@@ -98,36 +100,160 @@ class Source(str, enum.Enum):
IGNORE_OVERRIDE: LiteralString = r"# type: ignore[override]"
IGNORE_MISC: LiteralString = r"# type: ignore[misc]"
+MODULE_PRE = '''\
+"""Tools for creating transform & filter expressions with a python syntax."""
-def _override_predicate(obj: Any, /) -> bool:
- return callable(obj) and not (name := obj.__name__).startswith("_") # noqa: F841
+from __future__ import annotations
+import sys
+from typing import Any, TYPE_CHECKING
-_SCHEMA_BASE_MEMBERS: frozenset[str] = frozenset(
- nm for nm, _ in getmembers(_SchemaBase, _override_predicate)
+from altair.expr.core import {const}, {func}
+from altair.vegalite.v5.schema.core import ExprRef as _ExprRef
+
+if sys.version_info >= (3, 12):
+ from typing import override
+else:
+ from typing_extensions import override
+
+if TYPE_CHECKING:
+ from altair.expr.core import {return_ann}, {input_ann}
+
+
+class {metaclass}(type):
+ """
+ Metaclass for :class:`expr`.
+
+ Currently providing read-only class properties, representing JavaScript constants.
+ """
+
+ @property
+ def NaN(cls) -> {return_ann}:
+ """Not a number (same as JavaScript literal NaN)."""
+ return {const}("NaN")
+
+ @property
+ def LN10(cls) -> {return_ann}:
+ """The natural log of 10 (alias to Math.LN10)."""
+ return {const}("LN10")
+
+ @property
+ def E(cls) -> {return_ann}:
+ """The transcendental number e (alias to Math.E)."""
+ return {const}("E")
+
+ @property
+ def LOG10E(cls) -> {return_ann}:
+ """The base 10 logarithm e (alias to Math.LOG10E)."""
+ return {const}("LOG10E")
+
+ @property
+ def LOG2E(cls) -> {return_ann}:
+ """The base 2 logarithm of e (alias to Math.LOG2E)."""
+ return {const}("LOG2E")
+
+ @property
+ def SQRT1_2(cls) -> {return_ann}:
+ """The square root of 0.5 (alias to Math.SQRT1_2)."""
+ return {const}("SQRT1_2")
+
+ @property
+ def LN2(cls) -> {return_ann}:
+ """The natural log of 2 (alias to Math.LN2)."""
+ return {const}("LN2")
+
+ @property
+ def SQRT2(cls) -> {return_ann}:
+ """The square root of 2 (alias to Math.SQRT1_2)."""
+ return {const}("SQRT2")
+
+ @property
+ def PI(cls) -> {return_ann}:
+ """The transcendental number pi (alias to Math.PI)."""
+ return {const}("PI")
+'''
+
+MODULE_POST = """\
+_ExprType = expr
+# NOTE: Compatibility alias for previous type of `alt.expr`.
+# `_ExprType` was not referenced in any internal imports/tests.
+"""
+
+CLS_DOC = """
+ Utility providing *constants* and *classmethods* to construct expressions.
+
+ `Expressions`_ can be used to write basic formulas that enable custom interactions.
+
+ Alternatively, an `inline expression`_ may be defined via :class:`expr()`.
+
+ Parameters
+ ----------
+ expr: str
+ A `vega expression`_ string.
+
+ Returns
+ -------
+ ``ExprRef``
+
+ .. _Expressions:
+ https://altair-viz.github.io/user_guide/interactions.html#expressions
+ .. _inline expression:
+ https://altair-viz.github.io/user_guide/interactions.html#inline-expressions
+ .. _vega expression:
+ https://vega.github.io/vega/docs/expressions/
+
+ Examples
+ --------
+ >>> import altair as alt
+
+ >>> bind_range = alt.binding_range(min=100, max=300, name="Slider value: ")
+ >>> param_width = alt.param(bind=bind_range, name="param_width")
+ >>> param_color = alt.param(
+ ... expr=alt.expr.if_(param_width < 200, "red", "black"),
+ ... name="param_color",
+ ... )
+ >>> y = alt.Y("yval").axis(titleColor=param_color)
+
+ >>> y
+ Y({
+ axis: {'titleColor': Parameter('param_color', VariableParameter({
+ expr: if((param_width < 200),'red','black'),
+ name: 'param_color'
+ }))},
+ shorthand: 'yval'
+ })
+ """
+
+CLS_TEMPLATE = '''\
+class expr({base}, metaclass={metaclass}):
+ """{doc}"""
+
+ @override
+ def __new__(cls: type[{base}], expr: str) -> {base}: {type_ignore}
+ return {base}(expr=expr)
+'''
+
+METHOD_SIGNATURE = (
+ """def {title}(cls, {param_list}{marker}) -> {return_ann}:{type_ignore}"""
)
+METHOD_TEMPLATE = '''\
+ {decorator}
+ {signature}
+ """
+ {doc}
+ """
+ return {return_wrapper}({name}, {body_params})
+'''
-def download_expressions_md(url: str, /) -> Path:
- """Download to a temporary file, return that as a ``pathlib.Path``."""
- tmp, _ = request.urlretrieve(url)
- fp = Path(tmp)
- if not fp.exists():
- msg = (
- f"Expressions download failed: {fp!s}.\n\n"
- f"Try manually accessing resource: {url!r}"
- )
- raise FileNotFoundError(msg)
- else:
- return fp
+def _override_predicate(obj: Any, /) -> bool:
+ return callable(obj) and not (name := obj.__name__).startswith("_") # noqa: F841
-def expand_urls(url: str, /) -> str:
- if url.startswith("#"):
- url = f"{EXPRESSIONS_DOCS_URL}{url}"
- else:
- url = url.replace(r"../", VEGA_DOCS_URL)
- return url
+
+_SCHEMA_BASE_MEMBERS: frozenset[str] = frozenset(
+ nm for nm, _ in getmembers(_SchemaBase, _override_predicate)
+)
class RSTRenderer(_RSTRenderer):
@@ -135,13 +261,7 @@ def __init__(self) -> None:
super().__init__()
def link(self, token: Token, state: BlockState) -> str:
- """
- Store link url, for appending at the end of doc.
-
- TODO
- ----
- - Parameterize `"#"`, `"../"` expansion during init
- """
+ """Store link url, for appending at the end of doc."""
attrs = token["attrs"]
url = expand_urls(attrs["url"])
text = self.render_children(token, state)
@@ -175,30 +295,6 @@ def __call__(self, tokens: Iterable[Token], state: BlockState) -> str:
)
-def _doc_fmt(doc: str, /) -> str:
- """
- FIXME: Currently doing too many things.
-
- Primarily using to exclude summary line + references from ``textwrap``.
- """
- sentences: deque[str] = deque(SENTENCE_BREAK.split(doc))
- if len(sentences) > 1:
- references: str = ""
- summary = f"{sentences.popleft()}.\n"
- last_line = sentences.pop().strip()
- sentences = deque(f"{s}. " for s in sentences)
- if SECTION_BREAK in last_line:
- last_line, references = last_line.split(SECTION_BREAK, maxsplit=1)
- sentences.append(last_line)
- sentences = deque(text_wrap.wrap("".join(sentences)))
- sentences.appendleft(summary)
- if references:
- sentences.extend(("", indent(references, METHOD_INDENT)))
- return "\n".join(sentences)
- else:
- return sentences.pop().strip()
-
-
class ReplaceMany:
"""
Perform many ``1:1`` replacements on a given text.
@@ -324,35 +420,7 @@ def __repr__(self) -> str:
return f"{type(self).__name__}(\n {self._mapping!r}\n)"
-def italics_to_backticks(s: str, names: Iterable[str], /) -> str:
- """
- Perform a targeted replacement, considering links.
-
- Parameters
- ----------
- s
- String containing rendered `.rst`.
- names
- Group of names the replacement applies to.
-
- Notes
- -----
- - Avoids adding backticks to parameter names that are also used in a link.
- - All cases of these are for `unit|units`.
-
- Examples
- --------
- >>> italics_to_backticks(
- ... "some text and *name* and more text but also *other* text",
- ... ("name", "other"),
- ... )
- "some text and ``name`` and more text but also ``other`` text"
- """
- pattern = rf"(?P[^`_])\*(?P{'|'.join(names)})\*(?P[^`])"
- return re.sub(pattern, r"\g``\g``\g", s)
-
-
-class VegaExprNode:
+class VegaExprDef:
"""
``SchemaInfo``-like, but operates on `expressions.md`_.
@@ -380,7 +448,7 @@ def with_doc(self) -> Self:
s: str = parser.render_tokens(self._doc_tokens())
s = italics_to_backticks(s, self.parameter_names(variadic=False))
s = type(self).remap_title(s)
- self.doc = _doc_fmt(s)
+ self.doc = format_doc(s)
return self
def with_parameters(self) -> Self:
@@ -432,6 +500,7 @@ def parameter_names(self, *, variadic: bool = True) -> Iterator[str]:
raise TypeError(msg)
def render(self) -> str:
+ """Return fully parsed method definition."""
if self.is_overloaded():
body_params = STAR_ARGS[1:]
else:
@@ -454,7 +523,7 @@ def title(self) -> str:
"""
Use for the method definition, but not when calling internally.
- Updates ``VegaExprNode.remap_title`` for documentation example substitutions.
+ Updates ``remap_title`` class variable for documentation example substitutions.
"""
title = f"{self.name}_" if self.is_keyword() else self.name
type(self).remap_title.update({self.name: f"alt.expr.{title}"})
@@ -507,7 +576,7 @@ def _split_signature_tokens(self, *, exclude_name: bool = False) -> Iterator[str
for tok in self._signature_tokens():
clean = tok[RAW].strip(", -")
if clean not in EXCLUDE:
- yield from VegaExprNode._split_markers(clean)
+ yield from VegaExprDef._split_markers(clean)
@staticmethod
def _split_markers(s: str, /) -> Iterator[str]:
@@ -537,7 +606,7 @@ def _split_markers(s: str, /) -> Iterator[str]:
if len(s) == 1:
yield s
elif len(s) > 1:
- yield from VegaExprNode._split_markers(s)
+ yield from VegaExprDef._split_markers(s)
yield from end
def _doc_tokens(self) -> Sequence[Token]:
@@ -742,165 +811,104 @@ def from_texts(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
continue
-def parse_expressions(url: str, /) -> Iterator[VegaExprNode]:
- """
- Download, read markdown and eagerly parse signatures of relevant definitions.
-
- Yields with docs to ensure each can use all remapped names, regardless of the order they appear.
- """
- tokens = read_ast_tokens(download_expressions_md(url))
- expr_nodes = tuple(VegaExprNode.from_tokens(tokens))
- request.urlcleanup()
- VegaExprNode.remap_title.refresh()
- for node in expr_nodes:
- yield node.with_doc()
-
-
-MODULE_PRE = '''\
-"""Tools for creating transform & filter expressions with a python syntax."""
-
-from __future__ import annotations
-
-import sys
-from typing import Any, TYPE_CHECKING
-
-from altair.expr.core import {const}, {func}
-from altair.vegalite.v5.schema.core import ExprRef as _ExprRef
-
-if sys.version_info >= (3, 12):
- from typing import override
-else:
- from typing_extensions import override
+def download_expressions_md(url: str, /) -> Path:
+ """Download to a temporary file, return that as a ``pathlib.Path``."""
+ tmp, _ = request.urlretrieve(url)
+ fp = Path(tmp)
+ if not fp.exists():
+ msg = (
+ f"Expressions download failed: {fp!s}.\n\n"
+ f"Try manually accessing resource: {url!r}"
+ )
+ raise FileNotFoundError(msg)
+ else:
+ return fp
-if TYPE_CHECKING:
- from altair.expr.core import {return_ann}, {input_ann}
+def expand_urls(url: str, /) -> str:
+ if url.startswith("#"):
+ url = f"{EXPRESSIONS_DOCS_URL}{url}"
+ else:
+ url = url.replace(r"../", VEGA_DOCS_URL)
+ return url
-class {metaclass}(type):
- """
- Metaclass for :class:`expr`.
- Currently providing read-only class properties, representing JavaScript constants.
+def format_doc(doc: str, /) -> str:
"""
+ Format rendered docstring content.
- @property
- def NaN(cls) -> {return_ann}:
- """Not a number (same as JavaScript literal NaN)."""
- return {const}("NaN")
-
- @property
- def LN10(cls) -> {return_ann}:
- """The natural log of 10 (alias to Math.LN10)."""
- return {const}("LN10")
-
- @property
- def E(cls) -> {return_ann}:
- """The transcendental number e (alias to Math.E)."""
- return {const}("E")
-
- @property
- def LOG10E(cls) -> {return_ann}:
- """The base 10 logarithm e (alias to Math.LOG10E)."""
- return {const}("LOG10E")
-
- @property
- def LOG2E(cls) -> {return_ann}:
- """The base 2 logarithm of e (alias to Math.LOG2E)."""
- return {const}("LOG2E")
-
- @property
- def SQRT1_2(cls) -> {return_ann}:
- """The square root of 0.5 (alias to Math.SQRT1_2)."""
- return {const}("SQRT1_2")
-
- @property
- def LN2(cls) -> {return_ann}:
- """The natural log of 2 (alias to Math.LN2)."""
- return {const}("LN2")
+ Primarily used to prevent wrapping on `summary line`_ and references.
- @property
- def SQRT2(cls) -> {return_ann}:
- """The square root of 2 (alias to Math.SQRT1_2)."""
- return {const}("SQRT2")
-
- @property
- def PI(cls) -> {return_ann}:
- """The transcendental number pi (alias to Math.PI)."""
- return {const}("PI")
-'''
-
-MODULE_POST = """\
-_ExprType = expr
-# NOTE: Compatibility alias for previous type of `alt.expr`.
-# `_ExprType` was not referenced in any internal imports/tests.
-"""
-
-CLS_DOC = """
- Utility providing *constants* and *classmethods* to construct expressions.
+ Notes
+ -----
+ - Source is very different to `vega-lite`
+ - There are no real sections, so these are created here
+ - Single line docs are unchanged
+ - Multi-line have everything following the first line wrappped.
+ - With a double break inserted for a summary line
+ - Reference-like links section (if present) are also ommitted from wrapping
+
+ .. _summary line:
+ https://numpydoc.readthedocs.io/en/latest/format.html#short-summary
+ """
+ sentences: deque[str] = deque(SENTENCE_BREAK.split(doc))
+ if len(sentences) > 1:
+ references: str = ""
+ summary = f"{sentences.popleft()}.\n"
+ last_line = sentences.pop().strip()
+ sentences = deque(f"{s}. " for s in sentences)
+ if SECTION_BREAK in last_line:
+ last_line, references = last_line.split(SECTION_BREAK, maxsplit=1)
+ sentences.append(last_line)
+ sentences = deque(text_wrap.wrap("".join(sentences)))
+ sentences.appendleft(summary)
+ if references:
+ sentences.extend(("", indent(references, METHOD_INDENT)))
+ return "\n".join(sentences)
+ else:
+ return sentences.pop().strip()
- `Expressions`_ can be used to write basic formulas that enable custom interactions.
- Alternatively, an `inline expression`_ may be defined via :class:`expr()`.
+def italics_to_backticks(s: str, names: Iterable[str], /) -> str:
+ """
+ Perform a targeted replacement, considering links.
Parameters
----------
- expr: str
- A `vega expression`_ string.
-
- Returns
- -------
- ``ExprRef``
+ s
+ String containing rendered `.rst`.
+ names
+ Group of names the replacement applies to.
- .. _Expressions:
- https://altair-viz.github.io/user_guide/interactions.html#expressions
- .. _inline expression:
- https://altair-viz.github.io/user_guide/interactions.html#inline-expressions
- .. _vega expression:
- https://vega.github.io/vega/docs/expressions/
+ Notes
+ -----
+ - Avoids adding backticks to parameter names that are also used in a link.
+ - All cases of these are for `unit|units`.
Examples
--------
- >>> import altair as alt
-
- >>> bind_range = alt.binding_range(min=100, max=300, name="Slider value: ")
- >>> param_width = alt.param(bind=bind_range, name="param_width")
- >>> param_color = alt.param(
- ... expr=alt.expr.if_(param_width < 200, "red", "black"),
- ... name="param_color",
+ >>> italics_to_backticks(
+ ... "some text and *name* and more text but also *other* text",
+ ... ("name", "other"),
... )
- >>> y = alt.Y("yval").axis(titleColor=param_color)
-
- >>> y
- Y({
- axis: {'titleColor': Parameter('param_color', VariableParameter({
- expr: if((param_width < 200),'red','black'),
- name: 'param_color'
- }))},
- shorthand: 'yval'
- })
+ "some text and ``name`` and more text but also ``other`` text"
"""
+ pattern = rf"(?P[^`_])\*(?P{'|'.join(names)})\*(?P[^`])"
+ return re.sub(pattern, r"\g``\g``\g", s)
-CLS_TEMPLATE = '''\
-class expr({base}, metaclass={metaclass}):
- """{doc}"""
-
- @override
- def __new__(cls: type[{base}], expr: str) -> {base}: {type_ignore}
- return {base}(expr=expr)
-'''
-METHOD_SIGNATURE = (
- """def {title}(cls, {param_list}{marker}) -> {return_ann}:{type_ignore}"""
-)
+def parse_expressions(url: str, /) -> Iterator[VegaExprDef]:
+ """
+ Download, read markdown and eagerly parse signatures of relevant definitions.
-METHOD_TEMPLATE = '''\
- {decorator}
- {signature}
- """
- {doc}
- """
- return {return_wrapper}({name}, {body_params})
-'''
+ Yields with docs to ensure each can use all remapped names, regardless of the order they appear.
+ """
+ tokens = read_ast_tokens(download_expressions_md(url))
+ expr_defs = tuple(VegaExprDef.from_tokens(tokens))
+ request.urlcleanup()
+ VegaExprDef.remap_title.refresh()
+ for expr_def in expr_defs:
+ yield expr_def.with_doc()
def write_expr_module(
@@ -941,7 +949,7 @@ def write_expr_module(
)
contents = chain(
content,
- (node.render() for node in parse_expressions(url)),
+ (expr_def.render() for expr_def in parse_expressions(url)),
[MODULE_POST],
)
print(f"Generating\n {url!s}\n ->{output!s}")
From 5929077be3a93570729dd13d1ef7b4a3d4d66676 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 27 Sep 2024 14:18:49 +0100
Subject: [PATCH 66/77] fix: Resolve false negatives for `is_callable`
There were 10+ cases of methods not being defined.
These are now all present
https://github.com/vega/altair/pull/3600#discussion_r1778416377
---
altair/expr/__init__.py | 242 ++++++++++++++++++++++++++++++++++++
tools/schemapi/vega_expr.py | 59 +++++++--
2 files changed, 291 insertions(+), 10 deletions(-)
diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py
index bdaa54819..0dd0a8a37 100644
--- a/altair/expr/__init__.py
+++ b/altair/expr/__init__.py
@@ -435,6 +435,18 @@ def pow(cls, value: IntoExpression, exponent: IntoExpression, /) -> Expression:
"""
return FunctionExpression("pow", (value, exponent))
+ @classmethod
+ def random(cls) -> Expression:
+ """
+ Returns a pseudo-random number in the range [0,1).
+
+ Same as JavaScript's `Math.random`_.
+
+ .. _Math.random:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
+ """
+ return FunctionExpression("random", ())
+
@classmethod
def round(cls, value: IntoExpression, /) -> Expression:
"""
@@ -497,6 +509,60 @@ def sampleNormal(
"""
return FunctionExpression("sampleNormal", (mean, stdev))
+ @classmethod
+ def cumulativeNormal(
+ cls,
+ value: IntoExpression,
+ mean: IntoExpression = None,
+ stdev: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the value of the `cumulative distribution function`_ at the given input domain ``value`` for a normal distribution with specified ``mean`` and standard deviation ``stdev``.
+
+ If unspecified, the mean defaults to ``0`` and the standard deviation defaults to ``1``.
+
+ .. _cumulative distribution function:
+ https://en.wikipedia.org/wiki/Cumulative_distribution_function
+ """
+ return FunctionExpression("cumulativeNormal", (value, mean, stdev))
+
+ @classmethod
+ def densityNormal(
+ cls,
+ value: IntoExpression,
+ mean: IntoExpression = None,
+ stdev: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the value of the `probability density function`_ at the given input domain ``value``, for a normal distribution with specified ``mean`` and standard deviation ``stdev``.
+
+ If unspecified, the mean defaults to ``0`` and the standard deviation defaults to ``1``.
+
+ .. _probability density function:
+ https://en.wikipedia.org/wiki/Probability_density_function
+ """
+ return FunctionExpression("densityNormal", (value, mean, stdev))
+
+ @classmethod
+ def quantileNormal(
+ cls,
+ probability: IntoExpression,
+ mean: IntoExpression = None,
+ stdev: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the quantile value (the inverse of the `cumulative distribution function`_) for the given input ``probability``, for a normal distribution with specified ``mean`` and standard deviation ``stdev``.
+
+ If unspecified, the mean defaults to ``0`` and the standard deviation defaults to ``1``.
+
+ .. _cumulative distribution function:
+ https://en.wikipedia.org/wiki/Cumulative_distribution_function
+ """
+ return FunctionExpression("quantileNormal", (probability, mean, stdev))
+
@classmethod
def sampleLogNormal(
cls, mean: IntoExpression = None, stdev: IntoExpression = None, /
@@ -512,6 +578,63 @@ def sampleLogNormal(
"""
return FunctionExpression("sampleLogNormal", (mean, stdev))
+ @classmethod
+ def cumulativeLogNormal(
+ cls,
+ value: IntoExpression,
+ mean: IntoExpression = None,
+ stdev: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the value of the `cumulative distribution function`_ at the given input domain ``value`` for a log-normal distribution with specified log ``mean`` and log standard deviation ``stdev``.
+
+ If unspecified, the log mean defaults to ``0`` and the log standard deviation defaults to
+ ``1``.
+
+ .. _cumulative distribution function:
+ https://en.wikipedia.org/wiki/Cumulative_distribution_function
+ """
+ return FunctionExpression("cumulativeLogNormal", (value, mean, stdev))
+
+ @classmethod
+ def densityLogNormal(
+ cls,
+ value: IntoExpression,
+ mean: IntoExpression = None,
+ stdev: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the value of the `probability density function`_ at the given input domain ``value``, for a log-normal distribution with specified log ``mean`` and log standard deviation ``stdev``.
+
+ If unspecified, the log mean defaults to ``0`` and the log standard deviation defaults to
+ ``1``.
+
+ .. _probability density function:
+ https://en.wikipedia.org/wiki/Probability_density_function
+ """
+ return FunctionExpression("densityLogNormal", (value, mean, stdev))
+
+ @classmethod
+ def quantileLogNormal(
+ cls,
+ probability: IntoExpression,
+ mean: IntoExpression = None,
+ stdev: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the quantile value (the inverse of the `cumulative distribution function`_) for the given input ``probability``, for a log-normal distribution with specified log ``mean`` and log standard deviation ``stdev``.
+
+ If unspecified, the log mean defaults to ``0`` and the log standard deviation defaults to
+ ``1``.
+
+ .. _cumulative distribution function:
+ https://en.wikipedia.org/wiki/Cumulative_distribution_function
+ """
+ return FunctionExpression("quantileLogNormal", (probability, mean, stdev))
+
@classmethod
def sampleUniform(
cls, min: IntoExpression = None, max: IntoExpression = None, /
@@ -527,6 +650,68 @@ def sampleUniform(
"""
return FunctionExpression("sampleUniform", (min, max))
+ @classmethod
+ def cumulativeUniform(
+ cls,
+ value: IntoExpression,
+ min: IntoExpression = None,
+ max: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the value of the `cumulative distribution function`_ at the given input domain ``value`` for a uniform distribution over the interval [``min``, ``max``).
+
+ If unspecified, ``min`` defaults to ``0`` and ``max`` defaults to ``1``. If only one
+ argument is provided, it is interpreted as the ``max`` value.
+
+ .. _cumulative distribution function:
+ https://en.wikipedia.org/wiki/Cumulative_distribution_function
+ """
+ return FunctionExpression("cumulativeUniform", (value, min, max))
+
+ @classmethod
+ def densityUniform(
+ cls,
+ value: IntoExpression,
+ min: IntoExpression = None,
+ max: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the value of the `probability density function`_ at the given input domain ``value``, for a uniform distribution over the interval [``min``, ``max``).
+
+ If unspecified, ``min`` defaults to ``0`` and ``max`` defaults to ``1``. If only one
+ argument is provided, it is interpreted as the ``max`` value.
+
+ .. _probability density function:
+ https://en.wikipedia.org/wiki/Probability_density_function
+ """
+ return FunctionExpression("densityUniform", (value, min, max))
+
+ @classmethod
+ def quantileUniform(
+ cls,
+ probability: IntoExpression,
+ min: IntoExpression = None,
+ max: IntoExpression = None,
+ /,
+ ) -> Expression:
+ """
+ Returns the quantile value (the inverse of the `cumulative distribution function`_) for the given input ``probability``, for a uniform distribution over the interval [``min``, ``max``).
+
+ If unspecified, ``min`` defaults to ``0`` and ``max`` defaults to ``1``. If only one
+ argument is provided, it is interpreted as the ``max`` value.
+
+ .. _cumulative distribution function:
+ https://en.wikipedia.org/wiki/Cumulative_distribution_function
+ """
+ return FunctionExpression("quantileUniform", (probability, min, max))
+
+ @classmethod
+ def now(cls) -> Expression:
+ """Returns the timestamp for the current time."""
+ return FunctionExpression("now", ())
+
@classmethod
def datetime(
cls,
@@ -1234,6 +1419,39 @@ def hcl(cls, *args: Any) -> Expression:
"""
return FunctionExpression("hcl", args)
+ @classmethod
+ def luminance(cls, specifier: IntoExpression, /) -> Expression:
+ """
+ Returns the luminance for the given color ``specifier`` (compatible with `d3-color's rgb function`_).
+
+ The luminance is calculated according to the `W3C Web Content Accessibility Guidelines`_.
+
+ .. _d3-color's rgb function:
+ https://github.com/d3/d3-color#rgb
+ .. _W3C Web Content Accessibility Guidelines:
+ https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
+ """
+ return FunctionExpression("luminance", (specifier,))
+
+ @classmethod
+ def contrast(
+ cls, specifier1: IntoExpression, specifier2: IntoExpression, /
+ ) -> Expression:
+ """
+ Returns the contrast ratio between the input color specifiers as a float between 1 and 21.
+
+ The contrast is calculated according to the `W3C Web Content Accessibility Guidelines`_.
+
+ .. _W3C Web Content Accessibility Guidelines:
+ https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
+ """
+ return FunctionExpression("contrast", (specifier1, specifier2))
+
+ @classmethod
+ def item(cls) -> Expression:
+ """Returns the current scenegraph item that is the target of the event."""
+ return FunctionExpression("item", ())
+
@classmethod
def group(cls, name: IntoExpression = None, /) -> Expression:
"""
@@ -1638,6 +1856,30 @@ def treeAncestors(cls, name: IntoExpression, node: IntoExpression, /) -> Express
"""For the hierarchy data set with the given ``name``, returns the array of ancestors nodes, starting with the input ``node``, then followed by each parent up to the root."""
return FunctionExpression("treeAncestors", (name, node))
+ @classmethod
+ def containerSize(cls) -> Expression:
+ """
+ Returns the current CSS box size (``[el.clientWidth, el.clientHeight]``) of the parent DOM element that contains the Vega view.
+
+ If there is no container element, returns ``[undefined, undefined]``.
+ """
+ return FunctionExpression("containerSize", ())
+
+ @classmethod
+ def screen(cls) -> Expression:
+ """
+ Returns the `window.screen`_ object, or ``{}`` if Vega is not running in a browser environment.
+
+ .. _window.screen:
+ https://developer.mozilla.org/en-US/docs/Web/API/Window/screen
+ """
+ return FunctionExpression("screen", ())
+
+ @classmethod
+ def windowSize(cls) -> Expression:
+ """Returns the current window size (``[window.innerWidth, window.innerHeight]``) or ``[undefined, undefined]`` if Vega is not running in a browser environment."""
+ return FunctionExpression("windowSize", ())
+
@classmethod
def warn(
cls, value1: IntoExpression, value2: IntoExpression = None, *args: Any
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 220243ce6..c3bd99c35 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -59,7 +59,9 @@ class Source(str, enum.Enum):
# NOTE: Regex patterns
-FUNCTION_DEF_LINE: Pattern[str] = re.compile(r"")
+FUNCTION_DEF_LINE: Pattern[str] = re.compile(
+ r".+)\" href=\"#(.+)\">"
+)
SENTENCE_BREAK: Pattern[str] = re.compile(r"(? {base}: {type_ignore}
'''
METHOD_SIGNATURE = (
- """def {title}(cls, {param_list}{marker}) -> {return_ann}:{type_ignore}"""
+ """def {title}(cls{sep}{param_list}{marker}) -> {return_ann}:{type_ignore}"""
)
METHOD_TEMPLATE = '''\
@@ -420,6 +422,16 @@ def __repr__(self) -> str:
return f"{type(self).__name__}(\n {self._mapping!r}\n)"
+class Special(enum.Enum):
+ """
+ Special-case identifiers.
+
+ Representing ``VegaExprDef`` states that may be otherwise ambiguous.
+ """
+
+ NO_PARAMETERS = enum.auto()
+
+
class VegaExprDef:
"""
``SchemaInfo``-like, but operates on `expressions.md`_.
@@ -438,6 +450,7 @@ def __init__(self, name: str, children: Sequence[Token], /) -> None:
self.parameters: list[VegaExprParam] = []
self.doc: str = ""
self.signature: str = ""
+ self._special: set[Special] = set()
def with_doc(self) -> Self:
"""
@@ -459,6 +472,8 @@ def with_parameters(self) -> Self:
"""
split: Iterator[str] = self._split_signature_tokens(exclude_name=True)
self.parameters = list(VegaExprParam.from_texts(split))
+ if not self.parameters:
+ self._special.add(Special.NO_PARAMETERS)
return self
def with_signature(self) -> Self:
@@ -474,8 +489,9 @@ def with_signature(self) -> Self:
)
self.signature = METHOD_SIGNATURE.format(
title=self.title,
+ sep="" if self.is_no_parameters() else ",",
param_list=param_list,
- marker="" if self.is_variadic() else ", /",
+ marker="" if (self.is_variadic() or self.is_no_parameters()) else ", /",
return_ann=RETURN_ANNOTATION,
type_ignore=(
f" {IGNORE_OVERRIDE}" if self.is_incompatible_override() else ""
@@ -492,10 +508,13 @@ def parameter_names(self, *, variadic: bool = True) -> Iterator[str]:
else (p.name for p in self.parameters if not p.variadic)
)
yield from it
+ elif self.is_no_parameters():
+ yield from ()
else:
msg = (
f"Cannot provide `parameter_names` until they have been initialized via:\n"
- f"{type(self).__name__}.with_parameters()"
+ f"{type(self).__name__}.with_parameters()\n\n"
+ f"{self!r}"
)
raise TypeError(msg)
@@ -572,10 +591,18 @@ def _split_signature_tokens(self, *, exclude_name: bool = False) -> Iterator[str
['(', '[', 'start', ']', 'stop', '[', 'step', ']', ')']
"""
- EXCLUDE: set[str] = {", ", "", self.name} if exclude_name else {", ", ""}
- for tok in self._signature_tokens():
- clean = tok[RAW].strip(", -")
- if clean not in EXCLUDE:
+ EXCLUDE_INNER: set[str] = {self.name} if exclude_name else set()
+ EXCLUDE: set[str] = {", "} | EXCLUDE_INNER
+ for token in self._signature_tokens():
+ raw: str = token[RAW]
+ if raw == OPEN_PAREN:
+ yield raw
+ elif raw.startswith(OPEN_PAREN):
+ yield raw[0]
+ for s in raw[1:].split(","):
+ if (clean := s.strip(" -")) not in EXCLUDE_INNER:
+ yield from VegaExprDef._split_markers(clean)
+ elif (clean := raw.strip(", -")) not in EXCLUDE:
yield from VegaExprDef._split_markers(clean)
@staticmethod
@@ -650,7 +677,7 @@ def is_callable(self) -> bool:
if current != self.name:
self.name = current
next(it)
- return next(it).get(RAW, "") == OPEN_PAREN
+ return next(it).get(RAW, "").startswith(OPEN_PAREN)
def is_bound_variable_name(self) -> bool:
"""
@@ -728,6 +755,18 @@ def is_variadic(self) -> bool:
"""Position-only parameter separator `"/"` not allowed after `"*"` parameter."""
return self.is_overloaded() or any(p.variadic for p in self.parameters)
+ def is_no_parameters(self) -> bool:
+ """
+ Signature has been parsed for parameters, but none were present.
+
+ For example the definition for `now`_ would **only** return ``True``
+ after calling ``self.with_parameters()``.
+
+ .. _now:
+ https://vega.github.io/vega/docs/expressions/#now
+ """
+ return bool(self._special) and Special.NO_PARAMETERS in self._special
+
def __iter__(self) -> Iterator[Token]:
yield from self._children
@@ -767,7 +806,7 @@ def from_tokens(cls, tokens: Iterable[Token], /) -> Iterator[Self]:
(children := tok.get(CHILDREN)) is not None
and (child := next(iter(children)).get(RAW)) is not None
and (match := FUNCTION_DEF_LINE.match(child))
- and (node := cls(match[1], children)).is_callable()
+ and (node := cls(match["name"], children)).is_callable()
):
yield node.with_parameters().with_signature()
From 2f1d1994486e23de4e345042de7e96c8543dc384 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 27 Sep 2024 15:37:45 +0100
Subject: [PATCH 67/77] fix: Correct indent for `expr.screen` reference
https://github.com/vega/altair/pull/3600#discussion_r1778727868
---
altair/expr/__init__.py | 2 +-
tools/schemapi/vega_expr.py | 4 ++++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py
index 0dd0a8a37..b8185e49e 100644
--- a/altair/expr/__init__.py
+++ b/altair/expr/__init__.py
@@ -1871,7 +1871,7 @@ def screen(cls) -> Expression:
Returns the `window.screen`_ object, or ``{}`` if Vega is not running in a browser environment.
.. _window.screen:
- https://developer.mozilla.org/en-US/docs/Web/API/Window/screen
+ https://developer.mozilla.org/en-US/docs/Web/API/Window/screen
"""
return FunctionExpression("screen", ())
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index c3bd99c35..a7a230e77 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -904,6 +904,10 @@ def format_doc(doc: str, /) -> str:
if references:
sentences.extend(("", indent(references, METHOD_INDENT)))
return "\n".join(sentences)
+ elif SECTION_BREAK in doc:
+ # NOTE: 2 cases have a single line with a reference
+ summary, references = doc.split(SECTION_BREAK, maxsplit=1)
+ return "\n".join((summary, "", indent(references, METHOD_INDENT)))
else:
return sentences.pop().strip()
From 01e61e33a909417aacc89311f748e8ac3ed5e15b Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 27 Sep 2024 15:40:36 +0100
Subject: [PATCH 68/77] feat: Bump source to add `alt.expr.sort`
See https://github.com/vega/vega/pull/3973
---
altair/expr/__init__.py | 10 ++++++++++
tools/schemapi/vega_expr.py | 3 ++-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py
index b8185e49e..c3589be42 100644
--- a/altair/expr/__init__.py
+++ b/altair/expr/__init__.py
@@ -1063,6 +1063,16 @@ def slice(
"""
return FunctionExpression("slice", (array, start, end))
+ @classmethod
+ def sort(cls, array: IntoExpression, /) -> Expression:
+ """
+ Sorts the array in natural order using `ascending from Vega Utils`_.
+
+ .. _ascending from Vega Utils:
+ https://vega.github.io/vega/docs/api/util/#ascending
+ """
+ return FunctionExpression("sort", (array,))
+
@classmethod
def span(cls, array: IntoExpression, /) -> Expression:
"""Returns the span of ``array``: the difference between the last and first elements, or *array[array.length-1] - array[0]*."""
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index a7a230e77..e125a0086 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -55,7 +55,8 @@ class Source(str, enum.Enum):
"""Enumerations for ``expressions.md`` source files."""
LIVE = "https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md"
- STATIC = "https://raw.githubusercontent.com/vega/vega/ff98519cce32b776a98d01dd982467d76fc9ee34/docs/docs/expressions.md"
+ STATIC = "https://raw.githubusercontent.com/vega/vega/fb2e60274071033b4c427410ef43375b6f314cf2/docs/docs/expressions.md"
+ OLD = "https://raw.githubusercontent.com/vega/vega/ff98519cce32b776a98d01dd982467d76fc9ee34/docs/docs/expressions.md"
# NOTE: Regex patterns
From d75792b15a029f1a1628728ef3403aadc30ab065 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 28 Sep 2024 17:58:06 +0100
Subject: [PATCH 69/77] fix: Pin to `vega` release instead of commit hash
https://github.com/vega/altair/pull/3600#issuecomment-2380711841, https://github.com/vega/altair/pull/3600#discussion_r1779673096
---
altair/expr/__init__.py | 10 ----------
tools/generate_schema_wrapper.py | 3 ++-
tools/schemapi/vega_expr.py | 26 +++++---------------------
3 files changed, 7 insertions(+), 32 deletions(-)
diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py
index c3589be42..b8185e49e 100644
--- a/altair/expr/__init__.py
+++ b/altair/expr/__init__.py
@@ -1063,16 +1063,6 @@ def slice(
"""
return FunctionExpression("slice", (array, start, end))
- @classmethod
- def sort(cls, array: IntoExpression, /) -> Expression:
- """
- Sorts the array in natural order using `ascending from Vega Utils`_.
-
- .. _ascending from Vega Utils:
- https://vega.github.io/vega/docs/api/util/#ascending
- """
- return FunctionExpression("sort", (array,))
-
@classmethod
def span(cls, array: IntoExpression, /) -> Expression:
"""Returns the span of ``array``: the difference between the last and first elements, or *array[array.length-1] - array[0]*."""
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index 09ce43770..623d3cd82 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -48,6 +48,7 @@
from tools.schemapi.codegen import ArgInfo, AttrGetter
SCHEMA_VERSION: Final = "v5.20.1"
+VEGA_VERSION: Final = "v5.30.0"
HEADER_COMMENT = """\
@@ -1210,7 +1211,7 @@ def main() -> None:
args = parser.parse_args()
copy_schemapi_util()
vegalite_main(args.skip_download)
- write_expr_module(source_url="static", output=EXPR_FILE)
+ write_expr_module(VEGA_VERSION, output=EXPR_FILE)
# 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/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index e125a0086..71a8a2da5 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -49,14 +49,7 @@
# NOTE: Urls/fragments
VEGA_DOCS_URL: LiteralString = "https://vega.github.io/vega/docs/"
EXPRESSIONS_DOCS_URL: LiteralString = f"{VEGA_DOCS_URL}expressions/"
-
-
-class Source(str, enum.Enum):
- """Enumerations for ``expressions.md`` source files."""
-
- LIVE = "https://raw.githubusercontent.com/vega/vega/main/docs/docs/expressions.md"
- STATIC = "https://raw.githubusercontent.com/vega/vega/fb2e60274071033b4c427410ef43375b6f314cf2/docs/docs/expressions.md"
- OLD = "https://raw.githubusercontent.com/vega/vega/ff98519cce32b776a98d01dd982467d76fc9ee34/docs/docs/expressions.md"
+EXPRESSIONS_URL_TEMPLATE = "https://raw.githubusercontent.com/vega/vega/refs/tags/{version}/docs/docs/expressions.md"
# NOTE: Regex patterns
@@ -955,27 +948,18 @@ def parse_expressions(url: str, /) -> Iterator[VegaExprDef]:
yield expr_def.with_doc()
-def write_expr_module(
- source_url: Literal["live", "static"] | str, output: Path
-) -> None:
+def write_expr_module(version: str, output: Path) -> None:
"""
Parse an ``expressions.md`` into a ``.py`` module.
Parameters
----------
- source_url
- - ``"live"``: current version
- - ``"static"``: most recent version available during testing
- - Or provide an alternative as a ``str``
+ version
+ Vega release version, e.g. ``"v5.30.0"``.
output
Target path to write to.
"""
- if source_url == "live":
- url = Source.LIVE.value
- elif source_url == "static":
- url = Source.STATIC.value
- else:
- url = source_url
+ url = EXPRESSIONS_URL_TEMPLATE.format(version=version)
content = (
MODULE_PRE.format(
metaclass=CLS_META,
From 968114e5133ac4553727d6f39021b06c48fbe0fb Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 28 Sep 2024 19:36:46 +0100
Subject: [PATCH 70/77] docs: Include header comment
---
altair/expr/__init__.py | 3 +++
tools/generate_schema_wrapper.py | 6 +++++-
tools/schemapi/vega_expr.py | 4 +++-
3 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py
index b8185e49e..38d87f4c5 100644
--- a/altair/expr/__init__.py
+++ b/altair/expr/__init__.py
@@ -1,3 +1,6 @@
+# The contents of this file are automatically written by
+# tools/generate_schema_wrapper.py. Do not modify directly.
+
"""Tools for creating transform & filter expressions with a python syntax."""
from __future__ import annotations
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index 623d3cd82..5185dd174 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -1211,7 +1211,11 @@ def main() -> None:
args = parser.parse_args()
copy_schemapi_util()
vegalite_main(args.skip_download)
- write_expr_module(VEGA_VERSION, output=EXPR_FILE)
+ write_expr_module(
+ VEGA_VERSION,
+ 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/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 71a8a2da5..714dd886b 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -97,6 +97,7 @@
IGNORE_MISC: LiteralString = r"# type: ignore[misc]"
MODULE_PRE = '''\
+{header}
"""Tools for creating transform & filter expressions with a python syntax."""
from __future__ import annotations
@@ -948,7 +949,7 @@ def parse_expressions(url: str, /) -> Iterator[VegaExprDef]:
yield expr_def.with_doc()
-def write_expr_module(version: str, output: Path) -> None:
+def write_expr_module(version: str, output: Path, *, header: str) -> None:
"""
Parse an ``expressions.md`` into a ``.py`` module.
@@ -962,6 +963,7 @@ def write_expr_module(version: str, output: Path) -> None:
url = EXPRESSIONS_URL_TEMPLATE.format(version=version)
content = (
MODULE_PRE.format(
+ header=header,
metaclass=CLS_META,
const=CONST_WRAPPER,
return_ann=RETURN_ANNOTATION,
From 061b066e6a652d53e8cd94301439294c56338463 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 28 Sep 2024 19:45:13 +0100
Subject: [PATCH 71/77] build(DRAFT): Add `vegalite_to_vega_version`
Not fully convinced this is a reliable alterntive to manually maintaining
https://github.com/vega/altair/pull/3600#discussion_r1779718475
---
tools/generate_schema_wrapper.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index 5185dd174..e490d5a19 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -61,6 +61,9 @@
"""
SCHEMA_URL_TEMPLATE: Final = "https://vega.github.io/schema/{library}/{version}.json"
+VL_PACKAGE_TEMPLATE = (
+ "https://raw.githubusercontent.com/vega/vega-lite/refs/tags/{version}/package.json"
+)
SCHEMA_FILE = "vega-lite-schema.json"
THEMES_FILE = "vega-themes.json"
EXPR_FILE: Path = (
@@ -471,6 +474,15 @@ def schema_url(version: str = SCHEMA_VERSION) -> str:
return SCHEMA_URL_TEMPLATE.format(library="vega-lite", version=version)
+def vegalite_to_vega_version(vl_version: str, /) -> str:
+ """Return the minimum supported ``vega`` release for a ``vega-lite`` version."""
+ with request.urlopen(VL_PACKAGE_TEMPLATE.format(version=vl_version)) as response:
+ package_json = json.load(response)
+
+ version_spec = package_json["peerDependencies"]["vega"]
+ return f"v{version_spec.lstrip('^~')}"
+
+
def download_schemafile(
version: str, schemapath: Path, skip_download: bool = False
) -> Path:
From d95a5735c0ae784361529d7d50d558eee5dca7c3 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 30 Sep 2024 12:45:46 +0100
Subject: [PATCH 72/77] feat: Adds `vl_convert_to_vega_version`, remove
`vegalite_to_vega_version`
- Found this to be a much more reliable source
- Won't require manual syncing
- Can easily be replaced in the future with a public function/attribute accessible in `vl_convert` itself
https://github.com/vega/altair/pull/3600#discussion_r1779748538
---
tools/generate_schema_wrapper.py | 79 +++++++++++++++++++++++++++-----
1 file changed, 68 insertions(+), 11 deletions(-)
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index e490d5a19..4ee1aac89 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -5,6 +5,7 @@
import argparse
import copy
import json
+import re
import sys
import textwrap
from collections import deque
@@ -12,9 +13,11 @@
from itertools import chain
from operator import attrgetter
from pathlib import Path
-from typing import TYPE_CHECKING, Any, Final, Iterable, Iterator, Literal
+from typing import TYPE_CHECKING, Any, Final, Iterable, Iterator, Literal, cast
from urllib import request
+from packaging.version import Version
+
import vl_convert as vlc
sys.path.insert(0, str(Path.cwd()))
@@ -45,10 +48,73 @@
)
if TYPE_CHECKING:
+ from http.client import HTTPResponse
+
from tools.schemapi.codegen import ArgInfo, AttrGetter
+
+def vl_convert_to_vega_version(
+ *,
+ version: str | None = None,
+ url_fmt: str = "https://raw.githubusercontent.com/vega/vl-convert/refs/tags/vl-convert-vendor%40{0}/vl-convert-vendor/src/main.rs",
+ prefix: bytes = b"const VEGA_PATH",
+ pattern: str = r".+\/pin\/vega@(?Pv\d\.\d{1,3}\.\d{1,3})",
+) -> str:
+ """
+ Return the minimum supported ``vega`` release for a ``vl_convert`` version.
+
+ Parameters
+ ----------
+ version
+ A target `vl_convert` release.
+ Defaults to currently installed version.
+ url_fmt
+ Format string for source file.
+ prefix
+ Byte string prefix of target line.
+ pattern
+ Matches and extracts vega version, e.g. `"v5.30.0"`.
+
+ Examples
+ --------
+ >>> vl_convert_to_vega_version(version="0.2.0")
+ 'v5.22.1'
+ >>> vl_convert_to_vega_version(version="1.0.0")
+ 'v5.25.0'
+ >>> vl_convert_to_vega_version(version="1.3.0")
+ 'v5.28.0'
+ >>> vl_convert_to_vega_version(version="1.6.1")
+ 'v5.30.0'
+ """
+ MIN_VL_CONVERT = "0.2.0"
+ vlc_version: str = version or vlc.__version__ # pyright: ignore[reportAttributeAccessIssue]
+ if Version(vlc_version) < Version(MIN_VL_CONVERT):
+ msg = (
+ f"Operation requires `vl_convert>={MIN_VL_CONVERT!r}`\n"
+ f"but got: {version!r}"
+ )
+ raise NotImplementedError(msg)
+ with request.urlopen(url_fmt.format(vlc_version)) as response:
+ response = cast("HTTPResponse", response)
+ line = response.readline()
+ while not line.startswith(prefix):
+ line = response.readline()
+ if line.startswith(b"fn main"):
+ msg = f"Failed to find {prefix!r} in {url_fmt.format(vlc_version)}."
+ raise ValueError(msg)
+ src_line = line.decode()
+ if match := re.match(pattern, src_line):
+ return match.group("vega_version")
+ else:
+ msg = (
+ f"Failed to match a vega version specifier.\n"
+ f"{src_line=}\n{pattern=}\n{match=}\n"
+ )
+ raise NotImplementedError(msg)
+
+
SCHEMA_VERSION: Final = "v5.20.1"
-VEGA_VERSION: Final = "v5.30.0"
+VEGA_VERSION: Final[str] = vl_convert_to_vega_version()
HEADER_COMMENT = """\
@@ -474,15 +540,6 @@ def schema_url(version: str = SCHEMA_VERSION) -> str:
return SCHEMA_URL_TEMPLATE.format(library="vega-lite", version=version)
-def vegalite_to_vega_version(vl_version: str, /) -> str:
- """Return the minimum supported ``vega`` release for a ``vega-lite`` version."""
- with request.urlopen(VL_PACKAGE_TEMPLATE.format(version=vl_version)) as response:
- package_json = json.load(response)
-
- version_spec = package_json["peerDependencies"]["vega"]
- return f"v{version_spec.lstrip('^~')}"
-
-
def download_schemafile(
version: str, schemapath: Path, skip_download: bool = False
) -> Path:
From b0f1164f985d33fe8889aa1a4f96a813606d7a7d Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 7 Oct 2024 12:57:36 +0100
Subject: [PATCH 73/77] refactor: Use `vlc.get_vega_version()`
https://github.com/vega/altair/pull/3633, https://github.com/vega/vl-convert/issues/191
---
tools/generate_schema_wrapper.py | 3 +--
tools/schemapi/vega_expr.py | 1 +
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index b25e51b34..5c5f25b07 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -115,7 +115,6 @@ def vl_convert_to_vega_version(
SCHEMA_VERSION: Final = "v5.20.1"
-VEGA_VERSION: Final[str] = vl_convert_to_vega_version()
HEADER_COMMENT = """\
@@ -1282,7 +1281,7 @@ def main() -> None:
copy_schemapi_util()
vegalite_main(args.skip_download)
write_expr_module(
- VEGA_VERSION,
+ vlc.get_vega_version(),
output=EXPR_FILE,
header=HEADER_COMMENT,
)
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index 714dd886b..ce12660f1 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -960,6 +960,7 @@ def write_expr_module(version: str, output: Path, *, header: str) -> None:
output
Target path to write to.
"""
+ version = version if version.startswith("v") else f"v{version}"
url = EXPRESSIONS_URL_TEMPLATE.format(version=version)
content = (
MODULE_PRE.format(
From 5a081891822114e0b7c48e1503f44b2183b49255 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 7 Oct 2024 12:59:59 +0100
Subject: [PATCH 74/77] revert: Remove `vl_convert_to_vega_version`
https://github.com/vega/altair/pull/3600/commits/d95a5735c0ae784361529d7d50d558eee5dca7c3
---
tools/generate_schema_wrapper.py | 67 +-------------------------------
1 file changed, 1 insertion(+), 66 deletions(-)
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index 5c5f25b07..22dbede8d 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -5,7 +5,6 @@
import argparse
import copy
import json
-import re
import sys
import textwrap
from collections import deque
@@ -13,11 +12,9 @@
from itertools import chain
from operator import attrgetter
from pathlib import Path
-from typing import TYPE_CHECKING, Any, Final, Iterable, Iterator, Literal, cast
+from typing import TYPE_CHECKING, Any, Final, Iterable, Iterator, Literal
from urllib import request
-from packaging.version import Version
-
import vl_convert as vlc
sys.path.insert(0, str(Path.cwd()))
@@ -48,72 +45,10 @@
)
if TYPE_CHECKING:
- from http.client import HTTPResponse
-
from tools.schemapi.codegen import ArgInfo, AttrGetter
from vl_convert import VegaThemes
-def vl_convert_to_vega_version(
- *,
- version: str | None = None,
- url_fmt: str = "https://raw.githubusercontent.com/vega/vl-convert/refs/tags/vl-convert-vendor%40{0}/vl-convert-vendor/src/main.rs",
- prefix: bytes = b"const VEGA_PATH",
- pattern: str = r".+\/pin\/vega@(?Pv\d\.\d{1,3}\.\d{1,3})",
-) -> str:
- """
- Return the minimum supported ``vega`` release for a ``vl_convert`` version.
-
- Parameters
- ----------
- version
- A target `vl_convert` release.
- Defaults to currently installed version.
- url_fmt
- Format string for source file.
- prefix
- Byte string prefix of target line.
- pattern
- Matches and extracts vega version, e.g. `"v5.30.0"`.
-
- Examples
- --------
- >>> vl_convert_to_vega_version(version="0.2.0")
- 'v5.22.1'
- >>> vl_convert_to_vega_version(version="1.0.0")
- 'v5.25.0'
- >>> vl_convert_to_vega_version(version="1.3.0")
- 'v5.28.0'
- >>> vl_convert_to_vega_version(version="1.6.1")
- 'v5.30.0'
- """
- MIN_VL_CONVERT = "0.2.0"
- vlc_version: str = version or vlc.__version__ # pyright: ignore[reportAttributeAccessIssue]
- if Version(vlc_version) < Version(MIN_VL_CONVERT):
- msg = (
- f"Operation requires `vl_convert>={MIN_VL_CONVERT!r}`\n"
- f"but got: {version!r}"
- )
- raise NotImplementedError(msg)
- with request.urlopen(url_fmt.format(vlc_version)) as response:
- response = cast("HTTPResponse", response)
- line = response.readline()
- while not line.startswith(prefix):
- line = response.readline()
- if line.startswith(b"fn main"):
- msg = f"Failed to find {prefix!r} in {url_fmt.format(vlc_version)}."
- raise ValueError(msg)
- src_line = line.decode()
- if match := re.match(pattern, src_line):
- return match.group("vega_version")
- else:
- msg = (
- f"Failed to match a vega version specifier.\n"
- f"{src_line=}\n{pattern=}\n{match=}\n"
- )
- raise NotImplementedError(msg)
-
-
SCHEMA_VERSION: Final = "v5.20.1"
From 0969b30adc4fa67dd667e851f146efa61eade12d Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 7 Oct 2024 14:00:19 +0100
Subject: [PATCH 75/77] refactor: Factor out `download_expressions_md`
Also moves away from using the legacy `python2` interace
https://docs.python.org/3/library/urllib.request.html#legacy-interface
---
tools/markup.py | 16 +++++++++++++---
tools/schemapi/vega_expr.py | 26 ++++++--------------------
2 files changed, 19 insertions(+), 23 deletions(-)
diff --git a/tools/markup.py b/tools/markup.py
index d9e8230ec..b17e7ad24 100644
--- a/tools/markup.py
+++ b/tools/markup.py
@@ -4,7 +4,9 @@
import re
from html import unescape
+from pathlib import Path
from typing import TYPE_CHECKING, Any, Iterable, Literal
+from urllib import request
import mistune.util
from mistune import InlineParser as _InlineParser
@@ -13,7 +15,6 @@
if TYPE_CHECKING:
import sys
- from pathlib import Path
if sys.version_info >= (3, 11):
from typing import TypeAlias
@@ -23,6 +24,8 @@
from mistune import BaseRenderer, BlockParser, BlockState, InlineState
+ Url: TypeAlias = str
+
Token: TypeAlias = "dict[str, Any]"
_RE_LINK: Pattern[str] = re.compile(r"(?<=\[)([^\]]+)(?=\]\([^\)]+\))", re.MULTILINE)
@@ -127,13 +130,20 @@ def process_text(self, text: str, state: InlineState) -> None:
state.append_token({"type": "text", "raw": _RE_LIQUID_INCLUDE.sub(r"", text)})
-def read_ast_tokens(source: Path, /) -> list[Token]:
+def read_ast_tokens(source: Url | Path, /) -> list[Token]:
"""
Read from ``source``, drop ``BlockState``.
Factored out to provide accurate typing.
"""
- return _Markdown(renderer=None, inline=InlineParser()).read(source)[0]
+ markdown = _Markdown(renderer=None, inline=InlineParser())
+ if isinstance(source, Path):
+ tokens = markdown.read(source)
+ else:
+ with request.urlopen(source) as response:
+ s = response.read().decode("utf-8")
+ tokens = markdown.parse(s, markdown.block.state_cls())
+ return tokens[0]
def rst_syntax_for_class(class_name: str) -> str:
diff --git a/tools/schemapi/vega_expr.py b/tools/schemapi/vega_expr.py
index ce12660f1..90ed3ad0c 100644
--- a/tools/schemapi/vega_expr.py
+++ b/tools/schemapi/vega_expr.py
@@ -7,7 +7,6 @@
from collections import deque
from inspect import getmembers
from itertools import chain
-from pathlib import Path
from textwrap import TextWrapper as _TextWrapper
from textwrap import indent
from typing import (
@@ -22,7 +21,6 @@
Sequence,
overload,
)
-from urllib import request
from tools.markup import RSTParse, Token, read_ast_tokens
from tools.markup import RSTRenderer as _RSTRenderer
@@ -33,6 +31,7 @@
if TYPE_CHECKING:
import sys
+ from pathlib import Path
from re import Match, Pattern
from mistune import BlockState
@@ -43,6 +42,8 @@
from typing_extensions import LiteralString, Self
from _typeshed import SupportsKeysAndGetItem
+ from tools.markup import Url
+
__all__ = ["parse_expressions", "write_expr_module"]
@@ -845,20 +846,6 @@ def from_texts(cls, raw_texts: Iterable[str], /) -> Iterator[Self]:
continue
-def download_expressions_md(url: str, /) -> Path:
- """Download to a temporary file, return that as a ``pathlib.Path``."""
- tmp, _ = request.urlretrieve(url)
- fp = Path(tmp)
- if not fp.exists():
- msg = (
- f"Expressions download failed: {fp!s}.\n\n"
- f"Try manually accessing resource: {url!r}"
- )
- raise FileNotFoundError(msg)
- else:
- return fp
-
-
def expand_urls(url: str, /) -> str:
if url.startswith("#"):
url = f"{EXPRESSIONS_DOCS_URL}{url}"
@@ -935,15 +922,14 @@ def italics_to_backticks(s: str, names: Iterable[str], /) -> str:
return re.sub(pattern, r"\g``\g``\g", s)
-def parse_expressions(url: str, /) -> Iterator[VegaExprDef]:
+def parse_expressions(source: Url | Path, /) -> Iterator[VegaExprDef]:
"""
- Download, read markdown and eagerly parse signatures of relevant definitions.
+ Download remote or read local `.md` resource and eagerly parse signatures of relevant definitions.
Yields with docs to ensure each can use all remapped names, regardless of the order they appear.
"""
- tokens = read_ast_tokens(download_expressions_md(url))
+ tokens = read_ast_tokens(source)
expr_defs = tuple(VegaExprDef.from_tokens(tokens))
- request.urlcleanup()
VegaExprDef.remap_title.refresh()
for expr_def in expr_defs:
yield expr_def.with_doc()
From ad1ac8f9bdcec0a0c09c26d3a5c4588b21fa9bd9 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 12 Oct 2024 13:51:04 +0100
Subject: [PATCH 76/77] refactor: `tools.schemapi.vega_expr.py` ->
`tools.vega_expr.py`
https://github.com/vega/altair/pull/3600#issuecomment-2408521893
---
tools/generate_schema_wrapper.py | 11 ++---------
tools/schemapi/__init__.py | 2 +-
tools/{schemapi => }/vega_expr.py | 0
3 files changed, 3 insertions(+), 10 deletions(-)
rename tools/{schemapi => }/vega_expr.py (100%)
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index 22dbede8d..58a395fb3 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -21,15 +21,7 @@
from tools.markup import rst_syntax_for_class
-from tools.schemapi import ( # noqa: F401
- CodeSnippet,
- SchemaInfo,
- arg_invalid_kwds,
- arg_kwds,
- arg_required_kwds,
- codegen,
- write_expr_module,
-)
+from tools.schemapi import CodeSnippet, SchemaInfo, arg_kwds, arg_required_kwds, codegen
from tools.schemapi.utils import (
SchemaProperties,
TypeAliasTracer,
@@ -43,6 +35,7 @@
ruff_write_lint_format_str,
spell_literal,
)
+from tools.vega_expr import write_expr_module
if TYPE_CHECKING:
from tools.schemapi.codegen import ArgInfo, AttrGetter
diff --git a/tools/schemapi/__init__.py b/tools/schemapi/__init__.py
index a08b8f44e..b3ea70704 100644
--- a/tools/schemapi/__init__.py
+++ b/tools/schemapi/__init__.py
@@ -9,7 +9,7 @@
)
from tools.schemapi.schemapi import SchemaBase, Undefined
from tools.schemapi.utils import OneOrSeq, SchemaInfo
-from tools.schemapi.vega_expr import write_expr_module
+from tools.vega_expr import write_expr_module
__all__ = [
"CodeSnippet",
diff --git a/tools/schemapi/vega_expr.py b/tools/vega_expr.py
similarity index 100%
rename from tools/schemapi/vega_expr.py
rename to tools/vega_expr.py
From 91180d111a958b8eaae13028196f120398de829b Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 12 Oct 2024 13:59:00 +0100
Subject: [PATCH 77/77] docs: Add module docstring for `vega_expr.py`
---
tools/vega_expr.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/tools/vega_expr.py b/tools/vega_expr.py
index 90ed3ad0c..ce87cb2fb 100644
--- a/tools/vega_expr.py
+++ b/tools/vega_expr.py
@@ -1,3 +1,10 @@
+"""
+Parsing `Vega Expressions`_ docs to write the ``alt.expr`` module.
+
+.. _Vega Expressions:
+ https://vega.github.io/vega/docs/expressions/
+"""
+
from __future__ import annotations
import dataclasses