vgplot 0.30.0__tar.gz → 0.32.0b0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vgplot-0.30.0 → vgplot-0.32.0b0}/PKG-INFO +2 -2
- {vgplot-0.30.0 → vgplot-0.32.0b0}/package.json +1 -1
- {vgplot-0.30.0 → vgplot-0.32.0b0}/tests/test_full_round_trip.py +13 -9
- {vgplot-0.30.0 → vgplot-0.32.0b0}/tests/test_python_api.py +20 -19
- vgplot-0.32.0b0/tests/test_schema_coverage.py +65 -0
- {vgplot-0.30.0 → vgplot-0.32.0b0}/tests/test_unset_sentinel.py +2 -2
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/__init__.py +35 -29
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/_compat.py +2 -4
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/_generated/__init__.py +5 -3
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/_generated/attributes.py +12 -6
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/_generated/encodings.py +4 -2
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/_generated/marks.py +2 -0
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/_types.py +6 -2
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/params.py +6 -6
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/plot.py +32 -18
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/spec.py +24 -13
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/util.py +2 -0
- vgplot-0.30.0/tests/test_schema_coverage.py +0 -56
- {vgplot-0.30.0 → vgplot-0.32.0b0}/.gitignore +0 -0
- {vgplot-0.30.0 → vgplot-0.32.0b0}/CONTRIBUTING.md +0 -0
- {vgplot-0.30.0 → vgplot-0.32.0b0}/README.md +0 -0
- {vgplot-0.30.0 → vgplot-0.32.0b0}/pyproject.toml +0 -0
- {vgplot-0.30.0 → vgplot-0.32.0b0}/tests/__init__.py +0 -0
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/data.py +0 -0
- {vgplot-0.30.0 → vgplot-0.32.0b0}/vgplot/encodings.py +0 -0
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# in sync (same filenames).
|
|
5
5
|
#
|
|
6
6
|
# Run: pytest packages/vgplot/vgplot-python/tests/test_full_round_trip.py
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
7
9
|
import json
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from typing import Any
|
|
@@ -16,27 +18,29 @@ ESM_DIR = SPEC_DIR / "esm"
|
|
|
16
18
|
PYTHON_DIR = SPEC_DIR / "python"
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
def load_json_fixture(name: str) -> dict:
|
|
21
|
+
def load_json_fixture(name: str) -> dict[str, Any]:
|
|
20
22
|
return json.loads((JSON_DIR / f"{name}.json").read_text(encoding="utf-8"))
|
|
21
23
|
|
|
22
24
|
|
|
23
|
-
def run_spec_file_with_exec(path: Path) -> dict:
|
|
24
|
-
namespace
|
|
25
|
+
def run_spec_file_with_exec(path: Path) -> dict[str, Any]:
|
|
26
|
+
namespace = {
|
|
25
27
|
"__name__": "__spec_exec__",
|
|
26
28
|
"__file__": str(path),
|
|
27
29
|
"__builtins__": __builtins__,
|
|
28
30
|
}
|
|
29
31
|
code = compile(path.read_text(encoding="utf-8"), str(path), "exec")
|
|
30
|
-
exec(code, namespace)
|
|
32
|
+
exec(code, namespace) # ruff: ignore[exec-builtin]
|
|
31
33
|
|
|
32
34
|
view = namespace.get("view")
|
|
33
35
|
if view is None:
|
|
34
|
-
|
|
36
|
+
msg = f"`{path.name}` must define a top-level `view` variable"
|
|
37
|
+
raise AssertionError(msg)
|
|
35
38
|
if not hasattr(view, "to_dict"):
|
|
36
|
-
|
|
39
|
+
msg = f"`view` in `{path.name}` does not expose to_dict()"
|
|
40
|
+
raise AssertionError(msg)
|
|
37
41
|
# Execute to_dict() within the spec namespace so frame inspection finds the right variables
|
|
38
42
|
namespace["__view__"] = view
|
|
39
|
-
exec("__result__ = __view__.to_dict()", namespace)
|
|
43
|
+
exec("__result__ = __view__.to_dict()", namespace) # ruff: ignore[exec-builtin]
|
|
40
44
|
return namespace["__result__"]
|
|
41
45
|
|
|
42
46
|
|
|
@@ -45,13 +49,13 @@ ESM_EXAMPLES = sorted(path.stem for path in ESM_DIR.glob("*.js"))
|
|
|
45
49
|
PYTHON_EXAMPLES = sorted(path.stem for path in PYTHON_DIR.glob("*.py"))
|
|
46
50
|
|
|
47
51
|
|
|
48
|
-
def test_generated_examples_stay_in_sync():
|
|
52
|
+
def test_generated_examples_stay_in_sync() -> None:
|
|
49
53
|
assert PYTHON_EXAMPLES == JSON_EXAMPLES
|
|
50
54
|
assert ESM_EXAMPLES == JSON_EXAMPLES
|
|
51
55
|
|
|
52
56
|
|
|
53
57
|
@pytest.mark.parametrize("example_name", PYTHON_EXAMPLES)
|
|
54
|
-
def test_python_round_trip(example_name: str):
|
|
58
|
+
def test_python_round_trip(example_name: str) -> None:
|
|
55
59
|
generated = run_spec_file_with_exec(PYTHON_DIR / f"{example_name}.py")
|
|
56
60
|
original = load_json_fixture(example_name)
|
|
57
61
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Unit tests for the vgplot Python API, covering behaviors that the
|
|
2
2
|
# generated-spec round-trip suite does not exercise directly.
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
from typing import TYPE_CHECKING
|
|
4
6
|
|
|
5
7
|
import pytest
|
|
6
|
-
|
|
7
8
|
import vgplot as vg
|
|
8
9
|
from vgplot.plot import Mark
|
|
9
10
|
|
|
@@ -12,7 +13,7 @@ class TestGeneratedMarks:
|
|
|
12
13
|
"""Marks are generated from the schema; there is no dynamic fallback, so
|
|
13
14
|
unknown names fail loudly rather than being silently fabricated."""
|
|
14
15
|
|
|
15
|
-
def test_positional_data_with_encodings_is_kept(self):
|
|
16
|
+
def test_positional_data_with_encodings_is_kept(self) -> None:
|
|
16
17
|
# A leading positional arg is the data source even when the call also
|
|
17
18
|
# has keyword encodings.
|
|
18
19
|
mark = vg.waffle_y("athletes", x="a", y={"count": ""})
|
|
@@ -22,45 +23,45 @@ class TestGeneratedMarks:
|
|
|
22
23
|
assert d["data"] == {"from": "athletes"}
|
|
23
24
|
assert d["x"] == "a"
|
|
24
25
|
|
|
25
|
-
def test_explicit_none_channel_is_preserved(self):
|
|
26
|
+
def test_explicit_none_channel_is_preserved(self) -> None:
|
|
26
27
|
# Passing a channel explicitly as None keeps it in the output (distinct
|
|
27
28
|
# from not passing it at all).
|
|
28
29
|
assert vg.line_y("t", x="a", z=None).to_dict()["z"] is None
|
|
29
30
|
assert "z" not in vg.line_y("t", x="a").to_dict()
|
|
30
31
|
|
|
31
|
-
def test_unknown_name_raises(self):
|
|
32
|
+
def test_unknown_name_raises(self) -> None:
|
|
32
33
|
# No __getattr__ fallback: a missing or mis-typed API name is an error.
|
|
33
34
|
with pytest.raises(AttributeError):
|
|
34
|
-
vg.some_custom_mark # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
|
|
35
|
+
_ = vg.some_custom_mark # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
|
|
35
36
|
with pytest.raises(AttributeError):
|
|
36
|
-
vg.definitely_not_a_real_directive # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
|
|
37
|
+
_ = vg.definitely_not_a_real_directive # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
class TestDataHelpers:
|
|
40
|
-
def test_json_inline_data(self):
|
|
41
|
+
def test_json_inline_data(self) -> None:
|
|
41
42
|
d = vg.json([{"a": 1}, {"a": 2}])
|
|
42
43
|
assert d.to_dict() == {"type": "json", "data": [{"a": 1}, {"a": 2}]}
|
|
43
44
|
|
|
44
|
-
def test_json_file(self):
|
|
45
|
+
def test_json_file(self) -> None:
|
|
45
46
|
assert vg.json(file="x.json").to_dict() == {"type": "json", "file": "x.json"}
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
class TestParams:
|
|
49
|
-
def test_date_param(self):
|
|
50
|
+
def test_date_param(self) -> None:
|
|
50
51
|
from datetime import date
|
|
51
52
|
|
|
52
53
|
assert vg.param(date(2013, 5, 13)).param_def() == {"date": "2013-05-13"}
|
|
53
54
|
|
|
54
|
-
def test_datetime_param(self):
|
|
55
|
+
def test_datetime_param(self) -> None:
|
|
55
56
|
from datetime import datetime
|
|
56
57
|
|
|
57
|
-
assert vg.param(datetime(2013, 5, 13, 10, 30)).param_def() == {
|
|
58
|
+
assert vg.param(datetime(2013, 5, 13, 10, 30)).param_def() == { # ruff: ignore[call-datetime-without-tzinfo]
|
|
58
59
|
"date": "2013-05-13T10:30:00"
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
class TestAutoNaming:
|
|
63
|
-
def test_auto_param_name_skips_explicit_name(self):
|
|
64
|
+
def test_auto_param_name_skips_explicit_name(self) -> None:
|
|
64
65
|
from vgplot.spec import Spec
|
|
65
66
|
|
|
66
67
|
named = vg.param(1)
|
|
@@ -79,7 +80,7 @@ class TestDataFrames:
|
|
|
79
80
|
referenced as a table, and carried in the spec's data section for the host
|
|
80
81
|
(e.g. the widget) to register."""
|
|
81
82
|
|
|
82
|
-
def test_frame_is_referenced_and_named_after_variable(self):
|
|
83
|
+
def test_frame_is_referenced_and_named_after_variable(self) -> None:
|
|
83
84
|
pytest.importorskip("pyarrow")
|
|
84
85
|
import pyarrow as pa
|
|
85
86
|
|
|
@@ -90,7 +91,7 @@ class TestDataFrames:
|
|
|
90
91
|
assert d["plot"][0]["data"] == {"from": "weather"}
|
|
91
92
|
assert d["data"]["weather"] is weather
|
|
92
93
|
|
|
93
|
-
def test_frame_and_datadef_share_the_data_section(self):
|
|
94
|
+
def test_frame_and_datadef_share_the_data_section(self) -> None:
|
|
94
95
|
pytest.importorskip("pyarrow")
|
|
95
96
|
import pyarrow as pa
|
|
96
97
|
|
|
@@ -109,11 +110,11 @@ if TYPE_CHECKING:
|
|
|
109
110
|
|
|
110
111
|
def typing_dunder_all() -> None:
|
|
111
112
|
# Ok
|
|
112
|
-
vg.arrow
|
|
113
|
-
vg.errorbar_x
|
|
114
|
-
vg.mark
|
|
113
|
+
_ = vg.arrow
|
|
114
|
+
_ = vg.errorbar_x
|
|
115
|
+
_ = vg.mark
|
|
115
116
|
|
|
116
117
|
# TODO @dangotbanned: Get pyright to error on these
|
|
117
118
|
# Error (`_generated` modules)
|
|
118
|
-
vg.attributes # ty: ignore[unresolved-attribute]
|
|
119
|
-
vg.marks # ty: ignore[unresolved-attribute]
|
|
119
|
+
_ = vg.attributes # ty: ignore[unresolved-attribute]
|
|
120
|
+
_ = vg.marks # ty: ignore[unresolved-attribute]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Ensures the exported vgplot API covers every mark, attribute, interactor, and
|
|
2
|
+
# input in the Mosaic JSON schema (the source of truth). Marks and attributes
|
|
3
|
+
# are generated; interactors and inputs are hand-written. This guards against a
|
|
4
|
+
# schema addition that was never generated or hand-added.
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
import vgplot as vg
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
|
|
17
|
+
from typing_extensions import TypedDict
|
|
18
|
+
|
|
19
|
+
class Schema(TypedDict, closed=True, total=False):
|
|
20
|
+
properties: dict[str, dict[str, Any]]
|
|
21
|
+
anyOf: Sequence[Schema]
|
|
22
|
+
|
|
23
|
+
class Definitions(TypedDict, extra_items=Schema):
|
|
24
|
+
PlotAttributes: Schema
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
ROOT = Path(__file__).resolve().parents[4]
|
|
28
|
+
SCHEMA = ROOT / "docs" / "public" / "schema" / "latest.json"
|
|
29
|
+
|
|
30
|
+
# Schema names whose Python export is renamed or intentionally absent.
|
|
31
|
+
RENAME = {"table": "table_input"} # `table` input -> vg.table_input (vg.table is data)
|
|
32
|
+
IGNORE = {"value"} # internal selection const, not a user-facing interactor
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _snake(name: str) -> str:
|
|
36
|
+
return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name).lower()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_discriminator_values(defs: Definitions, key: str) -> set[str]:
|
|
40
|
+
out: set[str] = set()
|
|
41
|
+
for d in defs.values():
|
|
42
|
+
if (p := d.get("properties")) and (c := p.get(key, {}).get("const")):
|
|
43
|
+
out.add(c)
|
|
44
|
+
elif u := d.get("anyOf"):
|
|
45
|
+
out.update(
|
|
46
|
+
c
|
|
47
|
+
for member in u
|
|
48
|
+
if (p := member.get("properties"))
|
|
49
|
+
and (c := p.get(key, {}).get("const"))
|
|
50
|
+
)
|
|
51
|
+
return out
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_schema_surface_is_exported() -> None:
|
|
55
|
+
defs: Definitions = json.loads(SCHEMA.read_text("utf-8"))["definitions"]
|
|
56
|
+
names = (
|
|
57
|
+
get_discriminator_values(defs, "mark")
|
|
58
|
+
| get_discriminator_values(defs, "select")
|
|
59
|
+
| get_discriminator_values(defs, "input")
|
|
60
|
+
| set(defs["PlotAttributes"].get("properties", ()))
|
|
61
|
+
) - IGNORE
|
|
62
|
+
|
|
63
|
+
exported = set(vg.__all__)
|
|
64
|
+
missing = sorted(n for n in names if RENAME.get(n, _snake(n)) not in exported)
|
|
65
|
+
assert not missing, f"schema names with no exported vgplot function: {missing}"
|
|
@@ -32,7 +32,7 @@ def test_unset_pickle() -> None:
|
|
|
32
32
|
import pickle
|
|
33
33
|
|
|
34
34
|
unset = _import_unset()
|
|
35
|
-
assert pickle.loads(pickle.dumps(unset)) is unset
|
|
35
|
+
assert pickle.loads(pickle.dumps(unset)) is unset
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def test_unset_type_expression_union() -> None:
|
|
@@ -62,7 +62,7 @@ if TYPE_CHECKING:
|
|
|
62
62
|
from typing_extensions import assert_type
|
|
63
63
|
|
|
64
64
|
def typing_unset(
|
|
65
|
-
a: UNSET, b: str | UNSET, c: Any | UNSET, d: int |
|
|
65
|
+
a: UNSET, b: str | UNSET, c: Any | UNSET, d: int | UNSET | None = UNSET
|
|
66
66
|
) -> None:
|
|
67
67
|
assert_type(a, UNSET)
|
|
68
68
|
assert_type(b, str | UNSET)
|
|
@@ -1,50 +1,54 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ._generated import * # noqa: F403
|
|
4
|
+
from ._generated import __all__ as _generated_all
|
|
5
|
+
from .data import csv, data, json, parquet, spatial, table
|
|
6
|
+
from .encodings import channels, sql
|
|
3
7
|
from .params import param, selection
|
|
4
|
-
from .encodings import sql, channels
|
|
5
8
|
from .plot import (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
brush,
|
|
10
|
+
checkbox,
|
|
11
|
+
color_legend,
|
|
9
12
|
directive,
|
|
10
|
-
margins,
|
|
11
|
-
vconcat,
|
|
12
13
|
hconcat,
|
|
13
|
-
|
|
14
|
+
highlight,
|
|
14
15
|
hspace,
|
|
15
|
-
slider,
|
|
16
|
-
select,
|
|
17
|
-
checkbox,
|
|
18
|
-
menu,
|
|
19
|
-
search,
|
|
20
|
-
table_input,
|
|
21
16
|
input,
|
|
22
|
-
option,
|
|
23
|
-
brush,
|
|
24
|
-
sort,
|
|
25
17
|
interval_x,
|
|
26
|
-
interval_y,
|
|
27
18
|
interval_xy,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
interval_y,
|
|
20
|
+
margins,
|
|
21
|
+
mark,
|
|
22
|
+
menu,
|
|
23
|
+
nearest,
|
|
32
24
|
nearest_x,
|
|
33
25
|
nearest_y,
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
opacity_legend,
|
|
27
|
+
option,
|
|
36
28
|
pan,
|
|
37
29
|
pan_x,
|
|
38
30
|
pan_y,
|
|
39
31
|
pan_zoom,
|
|
40
32
|
pan_zoom_x,
|
|
41
33
|
pan_zoom_y,
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
plot,
|
|
35
|
+
region,
|
|
36
|
+
search,
|
|
37
|
+
select,
|
|
38
|
+
slider,
|
|
39
|
+
sort,
|
|
40
|
+
source,
|
|
44
41
|
symbol_legend,
|
|
42
|
+
table_input,
|
|
43
|
+
toggle,
|
|
44
|
+
toggle_color,
|
|
45
|
+
toggle_x,
|
|
46
|
+
toggle_y,
|
|
47
|
+
toggle_z,
|
|
48
|
+
vconcat,
|
|
49
|
+
vspace,
|
|
45
50
|
)
|
|
46
|
-
from .
|
|
47
|
-
from ._generated import __all__ as _generated_all
|
|
51
|
+
from .spec import Spec, View, spec
|
|
48
52
|
|
|
49
53
|
__all__ = [
|
|
50
54
|
"Spec",
|
|
@@ -67,6 +71,7 @@ __all__ = [
|
|
|
67
71
|
"margins",
|
|
68
72
|
"mark",
|
|
69
73
|
"menu",
|
|
74
|
+
"nearest",
|
|
70
75
|
"nearest_x",
|
|
71
76
|
"nearest_y",
|
|
72
77
|
"opacity_legend",
|
|
@@ -97,6 +102,7 @@ __all__ = [
|
|
|
97
102
|
"toggle_color",
|
|
98
103
|
"toggle_x",
|
|
99
104
|
"toggle_y",
|
|
105
|
+
"toggle_z",
|
|
100
106
|
"vconcat",
|
|
101
107
|
"vspace",
|
|
102
108
|
] + list(_generated_all) # pyright: ignore[reportUnsupportedDunderAll]
|
|
@@ -19,8 +19,6 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import sys
|
|
21
21
|
import typing
|
|
22
|
-
|
|
23
|
-
# ruff: noqa: A002
|
|
24
22
|
from contextlib import suppress
|
|
25
23
|
from importlib.util import find_spec
|
|
26
24
|
from typing import TYPE_CHECKING, Any, ClassVar
|
|
@@ -92,8 +90,8 @@ def _sentinel_backport_pre_py_3_15() -> Any:
|
|
|
92
90
|
|
|
93
91
|
if TYPE_CHECKING:
|
|
94
92
|
# Was renamed in https://github.com/python/typing_extensions/releases/tag/4.16.0
|
|
95
|
-
from typing_extensions import Sentinel as sentinel
|
|
96
|
-
else: #
|
|
93
|
+
from typing_extensions import Sentinel as sentinel
|
|
94
|
+
else: # ruff: ignore[collapsible-else-if]
|
|
97
95
|
if sys.version_info >= (3, 15):
|
|
98
96
|
from builtins import sentinel
|
|
99
97
|
else:
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# DO NOT EDIT. Generated from the Mosaic JSON schema by bin/generate-python-api.js.
|
|
2
2
|
# Regenerate with: pnpm run generate:python-api
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
from .attributes import * # noqa: F403
|
|
4
|
-
from .encodings import * # noqa: F403
|
|
5
|
-
from .marks import * # noqa: F403
|
|
6
6
|
from .attributes import __all__ as _attr_all
|
|
7
|
+
from .encodings import * # noqa: F403
|
|
7
8
|
from .encodings import __all__ as _enc_all
|
|
9
|
+
from .marks import * # noqa: F403
|
|
8
10
|
from .marks import __all__ as _mark_all
|
|
9
11
|
|
|
10
|
-
__all__ = [*_mark_all, *_attr_all, *_enc_all] # pyright: ignore[reportUnsupportedDunderAll]
|
|
12
|
+
__all__ = [*_mark_all, *_attr_all, *_enc_all] # pyright: ignore[reportUnsupportedDunderAll] # ruff: ignore[invalid-all-object]
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# DO NOT EDIT. Generated from the Mosaic JSON schema by bin/generate-python-api.js.
|
|
2
2
|
# Regenerate with: pnpm run generate:python-api
|
|
3
3
|
|
|
4
|
-
from
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
5
8
|
from ..plot import Directive
|
|
6
9
|
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .._types import AttrValue
|
|
12
|
+
|
|
7
13
|
|
|
8
14
|
def align(value: AttrValue) -> Directive:
|
|
9
15
|
"""How to distribute unused space in the **range** for *point* and *band* scales."""
|
|
@@ -1130,9 +1136,9 @@ __all__ = [
|
|
|
1130
1136
|
"fx_tick_format",
|
|
1131
1137
|
"fx_tick_padding",
|
|
1132
1138
|
"fx_tick_rotate",
|
|
1133
|
-
"fx_ticks",
|
|
1134
1139
|
"fx_tick_size",
|
|
1135
1140
|
"fx_tick_spacing",
|
|
1141
|
+
"fx_ticks",
|
|
1136
1142
|
"fy_align",
|
|
1137
1143
|
"fy_aria_description",
|
|
1138
1144
|
"fy_aria_label",
|
|
@@ -1156,9 +1162,9 @@ __all__ = [
|
|
|
1156
1162
|
"fy_tick_format",
|
|
1157
1163
|
"fy_tick_padding",
|
|
1158
1164
|
"fy_tick_rotate",
|
|
1159
|
-
"fy_ticks",
|
|
1160
1165
|
"fy_tick_size",
|
|
1161
1166
|
"fy_tick_spacing",
|
|
1167
|
+
"fy_ticks",
|
|
1162
1168
|
"grid",
|
|
1163
1169
|
"height",
|
|
1164
1170
|
"inset",
|
|
@@ -1250,11 +1256,11 @@ __all__ = [
|
|
|
1250
1256
|
"x_tick_format",
|
|
1251
1257
|
"x_tick_padding",
|
|
1252
1258
|
"x_tick_rotate",
|
|
1253
|
-
"x_ticks",
|
|
1254
1259
|
"x_tick_size",
|
|
1255
1260
|
"x_tick_spacing",
|
|
1256
|
-
"
|
|
1261
|
+
"x_ticks",
|
|
1257
1262
|
"x_zero",
|
|
1263
|
+
"xy_domain",
|
|
1258
1264
|
"y_align",
|
|
1259
1265
|
"y_aria_description",
|
|
1260
1266
|
"y_aria_label",
|
|
@@ -1286,8 +1292,8 @@ __all__ = [
|
|
|
1286
1292
|
"y_tick_format",
|
|
1287
1293
|
"y_tick_padding",
|
|
1288
1294
|
"y_tick_rotate",
|
|
1289
|
-
"y_ticks",
|
|
1290
1295
|
"y_tick_size",
|
|
1291
1296
|
"y_tick_spacing",
|
|
1297
|
+
"y_ticks",
|
|
1292
1298
|
"y_zero",
|
|
1293
1299
|
]
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# DO NOT EDIT. Generated from the Mosaic JSON schema by bin/generate-python-api.js.
|
|
2
2
|
# Regenerate with: pnpm run generate:python-api
|
|
3
3
|
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
4
6
|
from typing import Any
|
|
5
7
|
|
|
6
8
|
from .._types import UNSET, TransformArg
|
|
@@ -150,7 +152,7 @@ def mode(col: TransformArg, **options: Any) -> dict[str, Any]:
|
|
|
150
152
|
|
|
151
153
|
|
|
152
154
|
def nth_value(
|
|
153
|
-
col: TransformArg, offset: TransformArg
|
|
155
|
+
col: TransformArg, offset: TransformArg, **options: Any
|
|
154
156
|
) -> dict[str, Any]:
|
|
155
157
|
"""Get the nth value of the given column in the current window frame, counting from one."""
|
|
156
158
|
return _transform("nth_value", (col, offset), options)
|
|
@@ -247,6 +249,6 @@ __all__ = [
|
|
|
247
249
|
"stddev",
|
|
248
250
|
"stddev_pop",
|
|
249
251
|
"sum",
|
|
250
|
-
"variance",
|
|
251
252
|
"var_pop",
|
|
253
|
+
"variance",
|
|
252
254
|
]
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from collections.abc import Sequence
|
|
2
4
|
from datetime import date
|
|
3
|
-
from typing import Any
|
|
5
|
+
from typing import Any, TypeAlias
|
|
4
6
|
|
|
5
7
|
from vgplot._compat import sentinel
|
|
6
8
|
|
|
@@ -18,7 +20,9 @@ AttrValue = (
|
|
|
18
20
|
str | float | bool | date | dict[str, Any] | Sequence[Any] | _ParamBase | None
|
|
19
21
|
)
|
|
20
22
|
MarkData = str | FromRef | DataDef | dict[str, Any] | Sequence[Any] | _ParamBase | None
|
|
21
|
-
TransformArg = str | float | bool | _ParamBase
|
|
23
|
+
TransformArg = str | float | bool | dict[str, Any] | _ParamBase
|
|
22
24
|
|
|
23
25
|
UNSET = sentinel("UNSET")
|
|
24
26
|
"""Sentinel for mark channels that were not passed (distinct from None)."""
|
|
27
|
+
|
|
28
|
+
MimeBundle: TypeAlias = dict[str, str] | tuple[dict[str, Any], dict[str, Any]] | None
|
|
@@ -39,10 +39,10 @@ class ParamValue(_ParamBase):
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class ParamArray(_ParamBase):
|
|
42
|
-
def __init__(self, values: list) -> None:
|
|
43
|
-
self._values = list(values)
|
|
42
|
+
def __init__(self, values: list[Any]) -> None:
|
|
43
|
+
self._values: list[Any] = list(values)
|
|
44
44
|
|
|
45
|
-
def param_def(self, *, param_names: dict[int, str] | None = None) -> Any:
|
|
45
|
+
def param_def(self, *, param_names: dict[int, str] | None = None) -> list[Any]:
|
|
46
46
|
return [_resolve(v, param_names) for v in self._values]
|
|
47
47
|
|
|
48
48
|
def __repr__(self) -> str:
|
|
@@ -54,8 +54,8 @@ class SelectionDef(_ParamBase):
|
|
|
54
54
|
self._select = select
|
|
55
55
|
self._opts = {k: v for k, v in opts.items() if v is not None}
|
|
56
56
|
|
|
57
|
-
def param_def(self, *, param_names: dict[int, str] | None = None) -> Any:
|
|
58
|
-
d: dict = {"select": self._select}
|
|
57
|
+
def param_def(self, *, param_names: dict[int, str] | None = None) -> dict[str, Any]:
|
|
58
|
+
d: dict[str, Any] = {"select": self._select}
|
|
59
59
|
d.update(_resolve(self._opts, param_names))
|
|
60
60
|
return d
|
|
61
61
|
|
|
@@ -70,7 +70,7 @@ def param(value: Any = None) -> _ParamBase:
|
|
|
70
70
|
return ParamValue(value)
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
class selection:
|
|
73
|
+
class selection: # ruff: ignore[invalid-class-name]
|
|
74
74
|
"""Namespace for creating selection params."""
|
|
75
75
|
|
|
76
76
|
@staticmethod
|
|
@@ -2,15 +2,18 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Mapping
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
|
-
from .util import camelize, omit_none
|
|
8
|
-
from .params import _ParamBase
|
|
9
7
|
from .data import DataDef, is_frame
|
|
8
|
+
from .params import _ParamBase
|
|
9
|
+
from .util import camelize, omit_none
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from vgplot.spec import View
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
class FromRef:
|
|
13
|
-
def __init__(self, name: str, **opts: Any):
|
|
16
|
+
def __init__(self, name: str, **opts: Any) -> None:
|
|
14
17
|
self.name = name
|
|
15
18
|
self.opts = {camelize(k): v for k, v in opts.items() if v is not None}
|
|
16
19
|
|
|
@@ -27,7 +30,7 @@ class Directive:
|
|
|
27
30
|
key: str
|
|
28
31
|
value: Any
|
|
29
32
|
|
|
30
|
-
def to_kv(self):
|
|
33
|
+
def to_kv(self) -> tuple[str, Any]:
|
|
31
34
|
return camelize(self.key), self.value
|
|
32
35
|
|
|
33
36
|
|
|
@@ -95,7 +98,7 @@ def plot(
|
|
|
95
98
|
*items: Mark | Directive | Mapping[str, Any],
|
|
96
99
|
param_names: dict[int, str] | None = None,
|
|
97
100
|
**kwargs: Any,
|
|
98
|
-
) ->
|
|
101
|
+
) -> View:
|
|
99
102
|
from .spec import View
|
|
100
103
|
|
|
101
104
|
marks: list[dict[str, Any]] = []
|
|
@@ -109,7 +112,8 @@ def plot(
|
|
|
109
112
|
elif isinstance(item, (dict, Mapping)):
|
|
110
113
|
marks.append({k: encode_value(v, param_names) for k, v in item.items()})
|
|
111
114
|
else:
|
|
112
|
-
|
|
115
|
+
msg = f"Unsupported plot item: {item}"
|
|
116
|
+
raise TypeError(msg)
|
|
113
117
|
root: dict[str, Any] = {"plot": marks}
|
|
114
118
|
root.update(directives)
|
|
115
119
|
return View(root, **kwargs)
|
|
@@ -172,7 +176,7 @@ def _encode_component(
|
|
|
172
176
|
# Layout helpers
|
|
173
177
|
def vconcat(
|
|
174
178
|
*items: Any, param_names: dict[int, str] | None = None, **kwargs: Any
|
|
175
|
-
) ->
|
|
179
|
+
) -> View:
|
|
176
180
|
from .spec import View
|
|
177
181
|
|
|
178
182
|
return View(
|
|
@@ -182,7 +186,7 @@ def vconcat(
|
|
|
182
186
|
|
|
183
187
|
def hconcat(
|
|
184
188
|
*items: Any, param_names: dict[int, str] | None = None, **kwargs: Any
|
|
185
|
-
) ->
|
|
189
|
+
) -> View:
|
|
186
190
|
from .spec import View
|
|
187
191
|
|
|
188
192
|
return View(
|
|
@@ -190,13 +194,13 @@ def hconcat(
|
|
|
190
194
|
)
|
|
191
195
|
|
|
192
196
|
|
|
193
|
-
def hspace(value: int | str) ->
|
|
197
|
+
def hspace(value: int | str) -> View:
|
|
194
198
|
from .spec import View
|
|
195
199
|
|
|
196
200
|
return View({"hspace": value})
|
|
197
201
|
|
|
198
202
|
|
|
199
|
-
def vspace(value: int | str) ->
|
|
203
|
+
def vspace(value: int | str) -> View:
|
|
200
204
|
from .spec import View
|
|
201
205
|
|
|
202
206
|
return View({"vspace": value})
|
|
@@ -297,6 +301,16 @@ def toggle_color(bind: Any = None, peers: Any = None) -> dict[str, Any]:
|
|
|
297
301
|
return _interactor("toggleColor", **{"as": bind, "peers": peers})
|
|
298
302
|
|
|
299
303
|
|
|
304
|
+
def toggle_z(bind: Any = None, peers: Any = None) -> dict[str, Any]:
|
|
305
|
+
return _interactor("toggleZ", **{"as": bind, "peers": peers})
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def nearest(
|
|
309
|
+
bind: Any = None, field: Any = None, channels: Any = None
|
|
310
|
+
) -> dict[str, Any]:
|
|
311
|
+
return _interactor("nearest", **{"as": bind, "field": field, "channels": channels})
|
|
312
|
+
|
|
313
|
+
|
|
300
314
|
def nearest_x(
|
|
301
315
|
bind: Any = None, field: Any = None, channels: Any = None
|
|
302
316
|
) -> dict[str, Any]:
|
|
@@ -417,7 +431,7 @@ def symbol_legend(
|
|
|
417
431
|
_INPUT_ALIASES = {"bind": "as", "source": "from"}
|
|
418
432
|
|
|
419
433
|
|
|
420
|
-
def input(kind: str, **opts: Any) ->
|
|
434
|
+
def input(kind: str, **opts: Any) -> View:
|
|
421
435
|
from .spec import View
|
|
422
436
|
|
|
423
437
|
payload: dict[str, Any] = {"input": kind}
|
|
@@ -443,7 +457,7 @@ def slider(
|
|
|
443
457
|
step: Any = None,
|
|
444
458
|
value: Any = None,
|
|
445
459
|
**opts: Any,
|
|
446
|
-
) ->
|
|
460
|
+
) -> View:
|
|
447
461
|
return input(
|
|
448
462
|
"slider",
|
|
449
463
|
label=label,
|
|
@@ -463,7 +477,7 @@ def select(
|
|
|
463
477
|
multiple: bool = False,
|
|
464
478
|
value: Any = None,
|
|
465
479
|
**opts: Any,
|
|
466
|
-
) ->
|
|
480
|
+
) -> View:
|
|
467
481
|
return input(
|
|
468
482
|
"select",
|
|
469
483
|
label=label,
|
|
@@ -475,7 +489,7 @@ def select(
|
|
|
475
489
|
)
|
|
476
490
|
|
|
477
491
|
|
|
478
|
-
def checkbox(label: str, bind: Any, value: bool = False, **opts: Any) ->
|
|
492
|
+
def checkbox(label: str, bind: Any, value: bool = False, **opts: Any) -> View:
|
|
479
493
|
return input("checkbox", label=label, bind=bind, value=value, **opts)
|
|
480
494
|
|
|
481
495
|
|
|
@@ -488,7 +502,7 @@ def menu(
|
|
|
488
502
|
column: Any = None,
|
|
489
503
|
filter_by: Any = None,
|
|
490
504
|
**opts: Any,
|
|
491
|
-
) ->
|
|
505
|
+
) -> View:
|
|
492
506
|
return input(
|
|
493
507
|
"menu",
|
|
494
508
|
label=label,
|
|
@@ -510,7 +524,7 @@ def search(
|
|
|
510
524
|
filter_by: Any = None,
|
|
511
525
|
type: Any = None,
|
|
512
526
|
**opts: Any,
|
|
513
|
-
) ->
|
|
527
|
+
) -> View:
|
|
514
528
|
return input(
|
|
515
529
|
"search",
|
|
516
530
|
label=label,
|
|
@@ -534,7 +548,7 @@ def table_input(
|
|
|
534
548
|
row_batch: Any = None,
|
|
535
549
|
align: Any = None,
|
|
536
550
|
**opts: Any,
|
|
537
|
-
) ->
|
|
551
|
+
) -> View:
|
|
538
552
|
return input(
|
|
539
553
|
"table",
|
|
540
554
|
source=source,
|
|
@@ -2,12 +2,18 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
|
-
from .util import omit_none
|
|
8
|
-
from .params import _ParamBase
|
|
9
7
|
from .data import DataDef, is_frame
|
|
8
|
+
from .params import _ParamBase
|
|
10
9
|
from .plot import _encode_component
|
|
10
|
+
from .util import omit_none
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from duckdb import DuckDBPyConnection
|
|
14
|
+
from narwhals.typing import IntoFrame
|
|
15
|
+
|
|
16
|
+
from vgplot._types import MimeBundle
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
def _caller_locals() -> dict[str, Any]:
|
|
@@ -80,10 +86,10 @@ class Spec:
|
|
|
80
86
|
data: dict[str, Any] | None = None,
|
|
81
87
|
data_names: dict[int, str] | None = None,
|
|
82
88
|
params: dict[str, Any] | None = None,
|
|
83
|
-
plotDefaults: dict[str, Any] | None = None,
|
|
89
|
+
plotDefaults: dict[str, Any] | None = None, # ruff: ignore[invalid-argument-name]
|
|
84
90
|
plot_defaults: dict[str, Any] | None = None,
|
|
85
91
|
config: dict[str, Any] | None = None,
|
|
86
|
-
view: dict[str, Any] | None = None,
|
|
92
|
+
view: dict[str, Any] | View | None = None,
|
|
87
93
|
**extra: Any,
|
|
88
94
|
) -> None:
|
|
89
95
|
# Serialize any DataDef values in data and build id->name mapping
|
|
@@ -170,7 +176,7 @@ class Spec:
|
|
|
170
176
|
def to_json(self, **kwargs: Any) -> str:
|
|
171
177
|
return json.dumps(self.to_dict(), **kwargs)
|
|
172
178
|
|
|
173
|
-
def _repr_mimebundle_(self, **kwargs):
|
|
179
|
+
def _repr_mimebundle_(self, **kwargs: Any) -> MimeBundle:
|
|
174
180
|
try:
|
|
175
181
|
from mosaic_widget import MosaicWidget
|
|
176
182
|
except ImportError:
|
|
@@ -178,12 +184,17 @@ class Spec:
|
|
|
178
184
|
widget = MosaicWidget(self.to_dict())
|
|
179
185
|
return widget._repr_mimebundle_(**kwargs)
|
|
180
186
|
|
|
181
|
-
def show(
|
|
187
|
+
def show(
|
|
188
|
+
self,
|
|
189
|
+
con: DuckDBPyConnection | None = None,
|
|
190
|
+
data: dict[str, IntoFrame] | None = None,
|
|
191
|
+
) -> None:
|
|
182
192
|
try:
|
|
183
|
-
from mosaic_widget import MosaicWidget
|
|
184
193
|
from IPython.display import display
|
|
194
|
+
from mosaic_widget import MosaicWidget
|
|
185
195
|
except ImportError as e:
|
|
186
|
-
|
|
196
|
+
msg = "pip install mosaic-widget"
|
|
197
|
+
raise ImportError(msg) from e
|
|
187
198
|
widget = MosaicWidget(self.to_dict(), con=con, data=data)
|
|
188
199
|
display(widget)
|
|
189
200
|
|
|
@@ -200,7 +211,7 @@ class View:
|
|
|
200
211
|
view: dict[str, Any],
|
|
201
212
|
*,
|
|
202
213
|
data: dict[str, Any] | None = None,
|
|
203
|
-
plotDefaults: dict[str, Any] | None = None,
|
|
214
|
+
plotDefaults: dict[str, Any] | None = None, # ruff: ignore[invalid-argument-name]
|
|
204
215
|
plot_defaults: dict[str, Any] | None = None,
|
|
205
216
|
config: dict[str, Any] | None = None,
|
|
206
217
|
**extra: Any,
|
|
@@ -249,7 +260,7 @@ class View:
|
|
|
249
260
|
def show(self, con: Any = None, data: Any = None) -> None:
|
|
250
261
|
self._build_spec(_caller_locals()).show(con=con, data=data)
|
|
251
262
|
|
|
252
|
-
def _repr_mimebundle_(self, **kwargs: Any):
|
|
263
|
+
def _repr_mimebundle_(self, **kwargs: Any) -> MimeBundle:
|
|
253
264
|
# Walk up frames to find the one where this View object lives
|
|
254
265
|
frame = inspect.currentframe()
|
|
255
266
|
frame = frame.f_back if frame is not None else None
|
|
@@ -268,10 +279,10 @@ def spec(
|
|
|
268
279
|
*args: Any,
|
|
269
280
|
data: dict[str, Any] | None = None,
|
|
270
281
|
params: dict[str, Any] | None = None,
|
|
271
|
-
plotDefaults: dict[str, Any] | None = None,
|
|
282
|
+
plotDefaults: dict[str, Any] | None = None, # ruff: ignore[invalid-argument-name]
|
|
272
283
|
plot_defaults: dict[str, Any] | None = None,
|
|
273
284
|
config: dict[str, Any] | None = None,
|
|
274
|
-
view: dict[str, Any] | None = None,
|
|
285
|
+
view: dict[str, Any] | View | None = None,
|
|
275
286
|
**extra: Any,
|
|
276
287
|
) -> Spec:
|
|
277
288
|
"""Assemble a Spec from a view plus data sources and params.
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# Ensures the exported vgplot API covers every mark, attribute, interactor, and
|
|
2
|
-
# input in the Mosaic JSON schema (the source of truth). Marks and attributes
|
|
3
|
-
# are generated; interactors and inputs are hand-written. This guards against a
|
|
4
|
-
# schema addition that was never generated or hand-added.
|
|
5
|
-
import json
|
|
6
|
-
import re
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
import vgplot as vg
|
|
10
|
-
|
|
11
|
-
ROOT = Path(__file__).resolve().parents[4]
|
|
12
|
-
SCHEMA = ROOT / "docs" / "public" / "schema" / "latest.json"
|
|
13
|
-
|
|
14
|
-
# Schema names whose Python export is renamed or intentionally absent.
|
|
15
|
-
RENAME = {"table": "table_input"} # `table` input -> vg.table_input (vg.table is data)
|
|
16
|
-
IGNORE = {"value"} # internal selection const, not a user-facing interactor
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _snake(name: str) -> str:
|
|
20
|
-
return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name).lower()
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _const(node: dict, key: str):
|
|
24
|
-
prop = node.get("properties", {}).get(key)
|
|
25
|
-
return prop["const"] if isinstance(prop, dict) and "const" in prop else None
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _consts(defs: dict, key: str) -> set[str]:
|
|
29
|
-
"""Const values for `key`, including intersection defs (anyOf/allOf) whose
|
|
30
|
-
branches all agree on a single const (e.g. the densityX mark)."""
|
|
31
|
-
out = set()
|
|
32
|
-
for d in defs.values():
|
|
33
|
-
c = _const(d, key)
|
|
34
|
-
if c is not None:
|
|
35
|
-
out.add(c)
|
|
36
|
-
continue
|
|
37
|
-
branch_consts = {
|
|
38
|
-
_const(b, key) for b in (d.get("anyOf") or d.get("allOf") or [])
|
|
39
|
-
} - {None}
|
|
40
|
-
if len(branch_consts) == 1:
|
|
41
|
-
out |= branch_consts
|
|
42
|
-
return out
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def test_schema_surface_is_exported():
|
|
46
|
-
defs = json.loads(SCHEMA.read_text("utf-8"))["definitions"]
|
|
47
|
-
names = (
|
|
48
|
-
_consts(defs, "mark")
|
|
49
|
-
| _consts(defs, "select")
|
|
50
|
-
| _consts(defs, "input")
|
|
51
|
-
| set(defs["PlotAttributes"]["properties"])
|
|
52
|
-
) - IGNORE
|
|
53
|
-
|
|
54
|
-
exported = set(vg.__all__)
|
|
55
|
-
missing = sorted(n for n in names if RENAME.get(n, _snake(n)) not in exported)
|
|
56
|
-
assert not missing, f"schema names with no exported vgplot function: {missing}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|