From 6cb633fd537eb555e16a5f71e4e2912c8ff9fe29 Mon Sep 17 00:00:00 2001 From: Dan Redding <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 12 Oct 2024 14:09:20 +0100 Subject: [PATCH 1/2] feat: Generate `expr` method signatures, docs (#3600) --- altair/expr/__init__.py | 1789 +++++++++++++++++++----------- tests/expr/test_expr.py | 60 +- tests/vegalite/v5/test_api.py | 14 +- tools/__init__.py | 9 +- tools/generate_schema_wrapper.py | 24 +- tools/markup.py | 150 +++ tools/schemapi/__init__.py | 2 + tools/schemapi/utils.py | 58 +- tools/vega_expr.py | 980 ++++++++++++++++ 9 files changed, 2349 insertions(+), 737 deletions(-) create mode 100644 tools/markup.py create mode 100644 tools/vega_expr.py diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py index 1f93ac2b7..38d87f4c5 100644 --- a/altair/expr/__init__.py +++ b/altair/expr/__init__.py @@ -1,8 +1,12 @@ +# 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 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 +16,65 @@ else: from typing_extensions import override +if TYPE_CHECKING: + from altair.expr.core import Expression, IntoExpression + + +class _ExprMeta(type): + """ + Metaclass for :class:`expr`. -class _ConstExpressionType(type): - """Metaclass providing read-only class properties for :class:`expr`.""" + Currently providing read-only class properties, representing JavaScript constants. + """ @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""" +class expr(_ExprRef, metaclass=_ExprMeta): + """ Utility providing *constants* and *classmethods* to construct expressions. `Expressions`_ can be used to write basic formulas that enable custom interactions. @@ -110,1321 +121,1803 @@ 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) + def isArray(cls, value: IntoExpression, /) -> Expression: + """Returns true if ``value`` is an array, false otherwise.""" + return FunctionExpression("isArray", (value,)) @classmethod - def isArray(cls, *args) -> FunctionExpression: - """Returns true if *value* is an array, false otherwise.""" - return FunctionExpression("isArray", 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 isBoolean(cls, *args) -> FunctionExpression: - """Returns true if *value* is a boolean (`true` or `false`), false otherwise.""" - return FunctionExpression("isBoolean", args) - - @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 `Number.isNaN`_. - Same as JavaScript's `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`. + 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", args) + return FunctionExpression("min", (value1, value2, *args)) @classmethod - def pow(cls, *args) -> FunctionExpression: + def pow(cls, value: IntoExpression, exponent: IntoExpression, /) -> Expression: """ - Returns *value* raised to the given *exponent*. + Returns ``value`` raised to the given ``exponent``. + + Same as JavaScript's `Math.pow`_. - 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", args) + return FunctionExpression("pow", (value, exponent)) @classmethod - def random(cls, *args) -> FunctionExpression: + def random(cls) -> Expression: """ - Returns a pseudo-random number in the range `[0, 1]`. + Returns a pseudo-random number in the range [0,1). + + Same as JavaScript's `Math.random`_. - 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", args) + return FunctionExpression("random", ()) @classmethod - def round(cls, *args) -> FunctionExpression: + def round(cls, value: IntoExpression, /) -> Expression: """ - Rounds *value* to the nearest integer. + Rounds ``value`` to the nearest integer. - Same as JavaScript's `Math.round`. + 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", args) + return FunctionExpression("round", (value,)) @classmethod - def sin(cls, *args) -> FunctionExpression: + def sin(cls, value: IntoExpression, /) -> Expression: """ Trigonometric sine. - Same as JavaScript's `Math.sin`. + 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", args) + return FunctionExpression("sin", (value,)) @classmethod - def sqrt(cls, *args) -> FunctionExpression: + def sqrt(cls, value: IntoExpression, /) -> Expression: """ Square root function. - Same as JavaScript's `Math.sqrt`. + 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", args) + return FunctionExpression("sqrt", (value,)) @classmethod - def tan(cls, *args) -> FunctionExpression: + def tan(cls, value: IntoExpression, /) -> Expression: """ Trigonometric tangent. - Same as JavaScript's `Math.tan`. + 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", args) + return FunctionExpression("tan", (value,)) @classmethod - def sampleNormal(cls, *args) -> FunctionExpression: + 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*. + 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``. - 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", args) + return FunctionExpression("sampleNormal", (mean, stdev)) @classmethod - def cumulativeNormal(cls, *args) -> FunctionExpression: + 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*. + 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``. - 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", args) + return FunctionExpression("cumulativeNormal", (value, mean, stdev)) @classmethod - def densityNormal(cls, *args) -> FunctionExpression: + 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*. + 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`. + 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", args) + return FunctionExpression("densityNormal", (value, mean, stdev)) @classmethod - def quantileNormal(cls, *args) -> FunctionExpression: + 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*. + 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``. - 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", args) + return FunctionExpression("quantileNormal", (probability, mean, stdev)) @classmethod - def sampleLogNormal(cls, *args) -> FunctionExpression: + 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*. + 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``. - 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", args) + return FunctionExpression("sampleLogNormal", (mean, stdev)) @classmethod - def cumulativeLogNormal(cls, *args) -> FunctionExpression: + 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*. + 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`. + 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", args) + return FunctionExpression("cumulativeLogNormal", (value, mean, stdev)) @classmethod - def densityLogNormal(cls, *args) -> FunctionExpression: + 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*. + 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``. - 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", args) + return FunctionExpression("densityLogNormal", (value, mean, stdev)) @classmethod - def quantileLogNormal(cls, *args) -> FunctionExpression: + 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*. + 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``. - 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", args) + return FunctionExpression("quantileLogNormal", (probability, mean, stdev)) @classmethod - def sampleUniform(cls, *args) -> FunctionExpression: + 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]`. + 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. + 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", args) + return FunctionExpression("sampleUniform", (min, max)) @classmethod - def cumulativeUniform(cls, *args) -> FunctionExpression: + 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]`. + 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. - 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", args) + return FunctionExpression("cumulativeUniform", (value, min, max)) @classmethod - def densityUniform(cls, *args) -> FunctionExpression: + 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]`. + 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. + 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", args) + return FunctionExpression("densityUniform", (value, min, max)) @classmethod - def quantileUniform(cls, *args) -> FunctionExpression: + 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]`. + 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. + .. _cumulative distribution function: + https://en.wikipedia.org/wiki/Cumulative_distribution_function """ - return FunctionExpression("quantileUniform", args) + return FunctionExpression("quantileUniform", (probability, min, max)) @classmethod - def now(cls, *args) -> FunctionExpression: + def now(cls) -> Expression: """Returns the timestamp for the current time.""" - return FunctionExpression("now", args) + return FunctionExpression("now", ()) @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`_ or `TimeMultiFormat object`_. + For example: ``alt.expr.timeFormat(timestamp, '%A')``. Null values are formatted as + ``"null"``. - The *specifier* must be a valid `d3-time-format specifier `__. - For example: `timeFormat(timestamp, '%A')`. + .. _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: `timeParse('June 30, 2015', '%B %d, %Y')`. + 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", 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`_ or `TimeMultiFormat object`_. + For example: ``alt.expr.utcFormat(timestamp, '%A')``. Null values are formatted as + ``"null"``. - The *specifier* must be a valid `d3-time-format specifier `__. - For example: `utcFormat(timestamp, '%A')`. + .. _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: + def lab(cls, *args: Any) -> Expression: """ - Constructs a new `CIE LAB `__ color. + 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 `__. + 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) -> FunctionExpression: + def hcl(cls, *args: Any) -> Expression: """ - Constructs a new `HCL `__ (hue, chroma, luminance) color. + 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`_. - 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 luminance(cls, *args) -> FunctionExpression: + def luminance(cls, specifier: IntoExpression, /) -> Expression: """ - Returns the luminance for the given color *specifier* (compatible with `d3-color's rgb function `__. + 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`_. - 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", args) + return FunctionExpression("luminance", (specifier,)) @classmethod - def contrast(cls, *args) -> FunctionExpression: + 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 `__. + 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", args) + return FunctionExpression("contrast", (specifier1, specifier2)) @classmethod - def item(cls, *args) -> FunctionExpression: + def item(cls) -> Expression: """Returns the current scenegraph item that is the target of the event.""" - return FunctionExpression("item", args) + return FunctionExpression("item", ()) @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) + 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 containerSize(cls, *args) -> FunctionExpression: + 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 containerSize(cls) -> Expression: """ - Returns the current CSS box size (`[el.clientWidth, el.clientHeight]`) of the parent DOM element that contains the Vega view. + 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]`. + If there is no container element, returns ``[undefined, undefined]``. """ - return FunctionExpression("containerSize", args) + return FunctionExpression("containerSize", ()) @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) + 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, *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 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, *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/tests/expr/test_expr.py b/tests/expr/test_expr.py index 8842e0ced..2170c1e36 100644 --- a/tests/expr/test_expr.py +++ b/tests/expr/test_expr.py @@ -2,14 +2,20 @@ 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, Callable, Iterable, Iterator, TypeVar, cast import pytest 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: + from inspect import _IntrospectableCallable + +T = TypeVar("T") # This maps vega expression function names to the Python name VEGA_REMAP = {"if_": "if"} @@ -19,20 +25,29 @@ 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 _get_property_names(tp: type[Any], /) -> Iterator[str]: + for nm, _ in getmembers(tp, _is_property): + yield nm -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 signature_n_params( + obj: _IntrospectableCallable, + /, + *, + exclude: Iterable[str] = frozenset(("cls", "self")), +) -> int: + sig = signature(obj) + return len(set(sig.parameters).difference(exclude)) -def _get_property_names(tp: type[Any], /) -> Iterator[str]: - for nm, _ in getmembers(tp, _is_property): - yield nm +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: + 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(): @@ -86,15 +101,18 @@ def test_abs(): assert repr(z) == "abs(datum.xxx)" -@pytest.mark.parametrize(("veganame", "methodname"), _remap_classmethod_names(expr)) -def test_expr_funcs(veganame: str, methodname: str): - """Test all functions defined in expr.funcs.""" - func = getattr(expr, methodname) - z = func(datum.xxx) - assert repr(z) == f"{veganame}(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("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) @@ -102,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/tests/vegalite/v5/test_api.py b/tests/vegalite/v5/test_api.py index 95c91a12b..741003421 100644 --- a/tests/vegalite/v5/test_api.py +++ b/tests/vegalite/v5/test_api.py @@ -553,9 +553,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)) lhs_param = param_color_py_expr.param rhs_param = param_color_py_when.param assert isinstance(lhs_param, alt.VariableParameter) @@ -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( 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 85860e8f3..58a395fb3 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -20,14 +20,8 @@ sys.path.insert(0, str(Path.cwd())) -from tools.schemapi import ( # noqa: F401 - CodeSnippet, - SchemaInfo, - arg_invalid_kwds, - arg_kwds, - arg_required_kwds, - codegen, -) +from tools.markup import rst_syntax_for_class +from tools.schemapi import CodeSnippet, SchemaInfo, arg_kwds, arg_required_kwds, codegen from tools.schemapi.utils import ( SchemaProperties, TypeAliasTracer, @@ -37,16 +31,17 @@ import_typing_extensions, indent_docstring, resolve_references, - rst_syntax_for_class, ruff_format_py, 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 from vl_convert import VegaThemes + SCHEMA_VERSION: Final = "v5.20.1" @@ -60,8 +55,14 @@ """ 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 = ( + Path(__file__).parent / ".." / "altair" / "expr" / "__init__.py" +).resolve() CHANNEL_MYPY_IGNORE_STATEMENTS: Final = """\ # These errors need to be ignored as they come from the overload methods @@ -1207,6 +1208,11 @@ def main() -> None: args = parser.parse_args() copy_schemapi_util() vegalite_main(args.skip_download) + write_expr_module( + vlc.get_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/markup.py b/tools/markup.py new file mode 100644 index 000000000..b17e7ad24 --- /dev/null +++ b/tools/markup.py @@ -0,0 +1,150 @@ +"""Tools for working with formats like ``.md``, ``.rst``.""" + +from __future__ import annotations + +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 +from mistune import Markdown as _Markdown +from mistune.renderers.rst import RSTRenderer as _RSTRenderer + +if TYPE_CHECKING: + import sys + + 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, InlineState + + Url: TypeAlias = str + +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): + 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(_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() + s = self.renderer(self._iter_render(tokens, state), state) + return mistune.util.unescape(s) + + +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() + + +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: Url | Path, /) -> list[Token]: + """ + Read from ``source``, drop ``BlockState``. + + Factored out to provide accurate typing. + """ + 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: + return f":class:`{class_name}`" diff --git a/tools/schemapi/__init__.py b/tools/schemapi/__init__.py index 55c5f4148..b3ea70704 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.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/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/vega_expr.py b/tools/vega_expr.py new file mode 100644 index 000000000..ce87cb2fb --- /dev/null +++ b/tools/vega_expr.py @@ -0,0 +1,980 @@ +""" +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 +import enum +import keyword +import re +from collections import deque +from inspect import getmembers +from itertools import chain +from textwrap import TextWrapper as _TextWrapper +from textwrap import indent +from typing import ( + TYPE_CHECKING, + Any, + Callable, + ClassVar, + Iterable, + Iterator, + Literal, + Mapping, + Sequence, + overload, +) + +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 ( + ruff_write_lint_format_str as _ruff_write_lint_format_str, +) + +if TYPE_CHECKING: + import sys + from pathlib import Path + from re import Match, Pattern + + from mistune import BlockState + + if sys.version_info >= (3, 11): + from typing import LiteralString, Self + else: + from typing_extensions import LiteralString, Self + from _typeshed import SupportsKeysAndGetItem + + from tools.markup import Url + +__all__ = ["parse_expressions", "write_expr_module"] + + +# NOTE: Urls/fragments +VEGA_DOCS_URL: LiteralString = "https://vega.github.io/vega/docs/" +EXPRESSIONS_DOCS_URL: LiteralString = f"{VEGA_DOCS_URL}expressions/" +EXPRESSIONS_URL_TEMPLATE = "https://raw.githubusercontent.com/vega/vega/refs/tags/{version}/docs/docs/expressions.md" + + +# NOTE: Regex patterns +FUNCTION_DEF_LINE: Pattern[str] = re.compile( + r".+)\" href=\"#(.+)\">" +) +SENTENCE_BREAK: Pattern[str] = re.compile(r"(?= (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{sep}{param_list}{marker}) -> {return_ann}:{type_ignore}""" +) + +METHOD_TEMPLATE = '''\ + {decorator} + {signature} + """ + {doc} + """ + return {return_wrapper}({name}, {body_params}) +''' + + +def _override_predicate(obj: Any, /) -> bool: + return callable(obj) and not (name := obj.__name__).startswith("_") # noqa: F841 + + +_SCHEMA_BASE_MEMBERS: frozenset[str] = frozenset( + nm for nm, _ in getmembers(_SchemaBase, _override_predicate) +) + + +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 = expand_urls(attrs["url"]) + text = self.render_children(token, state) + text = text.replace("`", "") + inline = f"`{text}`_" + state.env["ref_links"][text] = {"url": url} + return inline + + 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 __call__(self, tokens: Iterable[Token], state: BlockState) -> str: + result = super().__call__(tokens, state) + if links := state.env.get("ref_links", {}): + return self._with_links(result, links) + else: + return result + + +parser: RSTParse = RSTParse(RSTRenderer()) +text_wrap = _TextWrapper( + width=100, + break_long_words=False, + break_on_hyphens=False, + initial_indent=METHOD_INDENT, + subsequent_indent=METHOD_INDENT, +) + + +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, + /, + fmt_match: str = "(?P{0})", + fmt_replace: str = "{0}", + ) -> None: + 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, + 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 refresh(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() + self._is_prepared = True + + 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 + """ + if not self._is_prepared or refresh: + self.refresh() + return self.pattern.sub(self.repl, s, count) + + def _compile(self) -> Pattern[str]: + if not self._mapping: + name = self._mapping.__qualname__ # type: ignore[attr-defined] + 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 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`_. + + .. _expressions.md: + 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 = "" + self._special: set[Special] = set() + + def with_doc(self) -> Self: + """ + Parses docstring content in full. + + Accessible via ``self.doc`` + """ + 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 = format_doc(s) + 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.from_texts(split)) + if not self.parameters: + self._special.add(Special.NO_PARAMETERS) + return self + + def with_signature(self) -> Self: + """ + Parses ``self.parameters`` into a full signature definition line. + + Accessible via ``self.signature`` + """ + param_list = ( + VegaExprParam.star_args() + if self.is_overloaded() + else ", ".join(p.render() for p in self.parameters) + ) + self.signature = METHOD_SIGNATURE.format( + title=self.title, + sep="" if self.is_no_parameters() else ",", + param_list=param_list, + 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 "" + ), + ) + return self + + def parameter_names(self, *, variadic: bool = True) -> Iterator[str]: + """Pass ``variadic=False`` to omit names like``*args``.""" + if self.parameters: + 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 + 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()\n\n" + f"{self!r}" + ) + raise TypeError(msg) + + def render(self) -> str: + """Return fully parsed method definition.""" + 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 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: + """ + Use for the method definition, but not when calling internally. + + 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}"}) + return title + + 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) + while current[TYPE] != SOFTBREAK: + current = next(it) + next(it) + for target in it: + if target[TYPE] == TEXT: + yield target + elif target[TYPE] == SOFTBREAK: + break + else: + continue + + def _split_signature_tokens(self, *, exclude_name: bool = False) -> 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', ']', ')'] + + When called with ``exclude_name=True``: + + ['(', '[', 'start', ']', 'stop', '[', 'step', ']', ')'] + """ + 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 + 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 VegaExprDef._split_markers(s) + yield from end + + def _doc_tokens(self) -> Sequence[Token]: + """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 :] + else: + continue + 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 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 + + 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, "") + name: str = self.name.casefold() + while current.casefold() != name: + if (el := next(it, None)) is not None: + current = el.get(RAW, "") + else: + return False + if current != self.name: + self.name = current + next(it) + return next(it).get(RAW, "").startswith(OPEN_PAREN) + + def is_bound_variable_name(self) -> bool: + """ + ``Vega`` `bound variables`_. + + These do not provide signatures: + + {"datum", "event", "signal"} + + .. _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) + + 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, "").endswith(INLINE_OVERLOAD): + 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_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) + + def is_incompatible_override(self) -> bool: + """ + ``self.title`` shadows an unrelated ``SchemaBase`` method. + + Requires an ignore comment for a type checker. + """ + return self.title 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 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 + + @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) + + 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" + ")" + ) + + @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["name"], children)).is_callable() + ): + yield node.with_parameters().with_signature() + + +@dataclasses.dataclass +class VegaExprParam: + name: str + required: bool + variadic: bool = False + + @staticmethod + def star_args() -> LiteralString: + return f"{STAR_ARGS}: Any" + + def render(self) -> str: + """Return as an annotated parameter, with a default if needed.""" + if self.required: + return f"{self.name}: {INPUT_ANNOTATION}" + elif not self.variadic: + return f"{self.name}: {INPUT_ANNOTATION} = {NONE}" + else: + return self.star_args() + + @classmethod + def from_texts(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 {OPEN_PAREN, CLOSE_PAREN}: + if s == OPEN_BRACKET: + is_required = False + continue + elif s == CLOSE_BRACKET: + is_required = True + continue + elif s.isalnum(): + yield cls(s, required=is_required) + elif s == ELLIPSIS: + yield cls(STAR_ARGS, required=False, variadic=True) + else: + continue + + +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 + + +def format_doc(doc: str, /) -> str: + """ + Format rendered docstring content. + + Primarily used to prevent wrapping on `summary line`_ and references. + + 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) + 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() + + +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) + + +def parse_expressions(source: Url | Path, /) -> Iterator[VegaExprDef]: + """ + 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(source) + expr_defs = tuple(VegaExprDef.from_tokens(tokens)) + VegaExprDef.remap_title.refresh() + for expr_def in expr_defs: + yield expr_def.with_doc() + + +def write_expr_module(version: str, output: Path, *, header: str) -> None: + """ + Parse an ``expressions.md`` into a ``.py`` module. + + Parameters + ---------- + version + Vega release version, e.g. ``"v5.30.0"``. + 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( + header=header, + metaclass=CLS_META, + const=CONST_WRAPPER, + return_ann=RETURN_ANNOTATION, + input_ann=INPUT_ANNOTATION, + func=RETURN_WRAPPER, + ), + CLS_TEMPLATE.format( + base="_ExprRef", + metaclass=CLS_META, + doc=CLS_DOC, + type_ignore=IGNORE_MISC, + ), + ) + contents = chain( + content, + (expr_def.render() for expr_def in parse_expressions(url)), + [MODULE_POST], + ) + print(f"Generating\n {url!s}\n ->{output!s}") + _ruff_write_lint_format_str(output, contents) From 8135911fc361899e7723b99c35ab237f4f096e27 Mon Sep 17 00:00:00 2001 From: Dan Redding <125183946+dangotbanned@users.noreply.github.com> Date: Sat, 12 Oct 2024 14:35:06 +0100 Subject: [PATCH 2/2] fix: Replace unsafe `locals()` manipulation in `Chart.encode` (#3637) Fixes https://github.com/vega/altair/issues/3634, https://github.com/vega/altair/issues/3554 --- altair/vegalite/v5/schema/channels.py | 46 ++++++++++++++++++++++++--- tools/generate_schema_wrapper.py | 6 ++-- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/altair/vegalite/v5/schema/channels.py b/altair/vegalite/v5/schema/channels.py index c978330a5..ee20f09cb 100644 --- a/altair/vegalite/v5/schema/channels.py +++ b/altair/vegalite/v5/schema/channels.py @@ -25256,10 +25256,48 @@ def encode( yOffset : str, :class:`YOffset`, Dict, :class:`YOffsetDatum`, :class:`YOffsetValue` Offset of y-position of the marks """ - # Compat prep for `infer_encoding_types` signature - kwargs = locals() - kwargs.pop("self") - args = kwargs.pop("args") + kwargs = { + "angle": angle, + "color": color, + "column": column, + "description": description, + "detail": detail, + "facet": facet, + "fill": fill, + "fillOpacity": fillOpacity, + "href": href, + "key": key, + "latitude": latitude, + "latitude2": latitude2, + "longitude": longitude, + "longitude2": longitude2, + "opacity": opacity, + "order": order, + "radius": radius, + "radius2": radius2, + "row": row, + "shape": shape, + "size": size, + "stroke": stroke, + "strokeDash": strokeDash, + "strokeOpacity": strokeOpacity, + "strokeWidth": strokeWidth, + "text": text, + "theta": theta, + "theta2": theta2, + "tooltip": tooltip, + "url": url, + "x": x, + "x2": x2, + "xError": xError, + "xError2": xError2, + "xOffset": xOffset, + "y": y, + "y2": y2, + "yError": yError, + "yError2": yError2, + "yOffset": yOffset, + } if args: kwargs = {k: v for k, v in kwargs.items() if v is not Undefined} diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index 58a395fb3..3b120bc77 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -278,10 +278,7 @@ class _EncodingMixin: def encode(self, *args: Any, {method_args}) -> Self: """Map properties of the data to visual properties of the chart (see :class:`FacetedEncoding`) {docstring}""" - # Compat prep for `infer_encoding_types` signature - kwargs = locals() - kwargs.pop("self") - args = kwargs.pop("args") + kwargs = {dict_literal} if args: kwargs = {{k: v for k, v in kwargs.items() if v is not Undefined}} @@ -1181,6 +1178,7 @@ def generate_encoding_artifacts( method: str = fmt_method.format( method_args=", ".join(signature_args), docstring=indent_docstring(signature_doc_params, indent_level=8, lstrip=False), + dict_literal="{" + ", ".join(f"{kwd!r}:{kwd}" for kwd in channel_infos) + "}", ) typed_dict = generate_typed_dict( facet_encoding,