vgplot 0.29.1__tar.gz → 0.31.0__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.
Files changed (26) hide show
  1. {vgplot-0.29.1 → vgplot-0.31.0}/.gitignore +1 -0
  2. {vgplot-0.29.1 → vgplot-0.31.0}/PKG-INFO +21 -11
  3. {vgplot-0.29.1 → vgplot-0.31.0}/README.md +19 -9
  4. {vgplot-0.29.1 → vgplot-0.31.0}/package.json +1 -1
  5. {vgplot-0.29.1 → vgplot-0.31.0}/pyproject.toml +0 -4
  6. {vgplot-0.29.1/test → vgplot-0.31.0/tests}/test_full_round_trip.py +15 -19
  7. {vgplot-0.29.1/test → vgplot-0.31.0/tests}/test_python_api.py +34 -24
  8. vgplot-0.31.0/tests/test_schema_coverage.py +65 -0
  9. vgplot-0.31.0/tests/test_unset_sentinel.py +70 -0
  10. {vgplot-0.29.1 → vgplot-0.31.0}/vgplot/__init__.py +31 -29
  11. vgplot-0.31.0/vgplot/_compat.py +101 -0
  12. {vgplot-0.29.1 → vgplot-0.31.0}/vgplot/_generated/__init__.py +5 -3
  13. {vgplot-0.29.1 → vgplot-0.31.0}/vgplot/_generated/attributes.py +12 -6
  14. {vgplot-0.29.1 → vgplot-0.31.0}/vgplot/_generated/encodings.py +9 -7
  15. vgplot-0.31.0/vgplot/_generated/marks.py +4126 -0
  16. {vgplot-0.29.1 → vgplot-0.31.0}/vgplot/_types.py +8 -9
  17. {vgplot-0.29.1 → vgplot-0.31.0}/vgplot/params.py +6 -6
  18. {vgplot-0.29.1 → vgplot-0.31.0}/vgplot/plot.py +22 -18
  19. {vgplot-0.29.1 → vgplot-0.31.0}/vgplot/spec.py +24 -13
  20. {vgplot-0.29.1 → vgplot-0.31.0}/vgplot/util.py +2 -0
  21. vgplot-0.29.1/test/test_schema_coverage.py +0 -61
  22. vgplot-0.29.1/vgplot/_generated/marks.py +0 -4124
  23. {vgplot-0.29.1 → vgplot-0.31.0}/CONTRIBUTING.md +0 -0
  24. {vgplot-0.29.1/test → vgplot-0.31.0/tests}/__init__.py +0 -0
  25. {vgplot-0.29.1 → vgplot-0.31.0}/vgplot/data.py +0 -0
  26. {vgplot-0.29.1 → vgplot-0.31.0}/vgplot/encodings.py +0 -0
@@ -22,6 +22,7 @@ transitionFolder
22
22
  # Virtual environment
23
23
  venv/
24
24
  .env/
25
+ **/.python-version
25
26
 
26
27
  # Conda environment
27
28
  conda-env/
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: vgplot
3
- Version: 0.29.1
3
+ Version: 0.31.0
4
4
  Summary: A Python API for authoring Mosaic vgplot visualizations
5
5
  Project-URL: homepage, https://github.com/uwdata/mosaic
6
6
  Requires-Python: >=3.10
@@ -59,18 +59,30 @@ brush = vg.selection.crossfilter()
59
59
 
60
60
  view = vg.vconcat(
61
61
  vg.plot(
62
- vg.rect_y(flights, x=vg.bin("delay"), y=vg.count(),
63
- filter_by=brush, fill="steelblue",
64
- inset_left=0.5, inset_right=0.5),
62
+ vg.rect_y(
63
+ flights,
64
+ x=vg.bin("delay"),
65
+ y=vg.count(),
66
+ filter_by=brush,
67
+ fill="steelblue",
68
+ inset_left=0.5,
69
+ inset_right=0.5,
70
+ ),
65
71
  vg.interval_x(bind=brush),
66
72
  vg.x_domain("Fixed"),
67
73
  vg.x_label("Arrival Delay (min)"),
68
74
  vg.height(200),
69
75
  ),
70
76
  vg.plot(
71
- vg.rect_y(flights, x=vg.bin("time"), y=vg.count(),
72
- filter_by=brush, fill="steelblue",
73
- inset_left=0.5, inset_right=0.5),
77
+ vg.rect_y(
78
+ flights,
79
+ x=vg.bin("time"),
80
+ y=vg.count(),
81
+ filter_by=brush,
82
+ fill="steelblue",
83
+ inset_left=0.5,
84
+ inset_right=0.5,
85
+ ),
74
86
  vg.interval_x(bind=brush),
75
87
  vg.x_domain("Fixed"),
76
88
  vg.x_label("Departure Time (hour)"),
@@ -94,9 +106,7 @@ bias = vg.param(0)
94
106
  view = vg.vconcat(
95
107
  vg.slider(label="Bias", bind=bias, min=0, max=1000, step=1),
96
108
  vg.plot(
97
- vg.area_y(walk, x="t", y=vg.sql("v + $bias")),
98
- vg.width(680),
99
- vg.height(200),
109
+ vg.area_y(walk, x="t", y=vg.sql("v + $bias")), vg.width(680), vg.height(200)
100
110
  ),
101
111
  )
102
112
 
@@ -50,18 +50,30 @@ brush = vg.selection.crossfilter()
50
50
 
51
51
  view = vg.vconcat(
52
52
  vg.plot(
53
- vg.rect_y(flights, x=vg.bin("delay"), y=vg.count(),
54
- filter_by=brush, fill="steelblue",
55
- inset_left=0.5, inset_right=0.5),
53
+ vg.rect_y(
54
+ flights,
55
+ x=vg.bin("delay"),
56
+ y=vg.count(),
57
+ filter_by=brush,
58
+ fill="steelblue",
59
+ inset_left=0.5,
60
+ inset_right=0.5,
61
+ ),
56
62
  vg.interval_x(bind=brush),
57
63
  vg.x_domain("Fixed"),
58
64
  vg.x_label("Arrival Delay (min)"),
59
65
  vg.height(200),
60
66
  ),
61
67
  vg.plot(
62
- vg.rect_y(flights, x=vg.bin("time"), y=vg.count(),
63
- filter_by=brush, fill="steelblue",
64
- inset_left=0.5, inset_right=0.5),
68
+ vg.rect_y(
69
+ flights,
70
+ x=vg.bin("time"),
71
+ y=vg.count(),
72
+ filter_by=brush,
73
+ fill="steelblue",
74
+ inset_left=0.5,
75
+ inset_right=0.5,
76
+ ),
65
77
  vg.interval_x(bind=brush),
66
78
  vg.x_domain("Fixed"),
67
79
  vg.x_label("Departure Time (hour)"),
@@ -85,9 +97,7 @@ bias = vg.param(0)
85
97
  view = vg.vconcat(
86
98
  vg.slider(label="Bias", bind=bias, min=0, max=1000, step=1),
87
99
  vg.plot(
88
- vg.area_y(walk, x="t", y=vg.sql("v + $bias")),
89
- vg.width(680),
90
- vg.height(200),
100
+ vg.area_y(walk, x="t", y=vg.sql("v + $bias")), vg.width(680), vg.height(200)
91
101
  ),
92
102
  )
93
103
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgplot-python",
3
- "version": "0.29.1",
3
+ "version": "0.31.0",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "prerelease": "uv run ruff check && uv run ruff format --check",
@@ -31,7 +31,3 @@ pattern = "\"version\": \"(?P<version>.+?)\""
31
31
 
32
32
  [tool.hatch.build.targets.wheel]
33
33
  packages = ["vgplot"]
34
-
35
- [tool.pyright]
36
- ignore = ["../**/.venv/"] # Do not display diagnostics in external source code
37
- typeCheckingMode = "standard"
@@ -3,48 +3,44 @@
3
3
  # in specs/json/. Also checks that Python, JSON, and ESM example sets stay
4
4
  # in sync (same filenames).
5
5
  #
6
- # Run: pytest packages/vgplot/vgplot-python/test/test_full_round_trip.py
6
+ # Run: pytest packages/vgplot/vgplot-python/tests/test_full_round_trip.py
7
+ from __future__ import annotations
8
+
7
9
  import json
8
- import sys
9
10
  from pathlib import Path
10
11
  from typing import Any
11
12
 
12
13
  import pytest
13
14
 
14
- ROOT = Path(__file__).resolve().parents[4] # mosaic project root
15
- SPEC_DIR = ROOT / "specs"
15
+ SPEC_DIR = Path(__file__).resolve().parents[4] / "specs"
16
16
  JSON_DIR = SPEC_DIR / "json"
17
17
  ESM_DIR = SPEC_DIR / "esm"
18
18
  PYTHON_DIR = SPEC_DIR / "python"
19
19
 
20
- VGPLOT_PKG = ROOT / "packages" / "vgplot" / "vgplot-python"
21
-
22
- for p in (VGPLOT_PKG, ROOT):
23
- if str(p) not in sys.path:
24
- sys.path.insert(0, str(p))
25
-
26
20
 
27
- def load_json_fixture(name: str) -> dict:
21
+ def load_json_fixture(name: str) -> dict[str, Any]:
28
22
  return json.loads((JSON_DIR / f"{name}.json").read_text(encoding="utf-8"))
29
23
 
30
24
 
31
- def run_spec_file_with_exec(path: Path) -> dict:
32
- namespace: dict[str, Any] = {
25
+ def run_spec_file_with_exec(path: Path) -> dict[str, Any]:
26
+ namespace = {
33
27
  "__name__": "__spec_exec__",
34
28
  "__file__": str(path),
35
29
  "__builtins__": __builtins__,
36
30
  }
37
31
  code = compile(path.read_text(encoding="utf-8"), str(path), "exec")
38
- exec(code, namespace)
32
+ exec(code, namespace) # ruff: ignore[exec-builtin]
39
33
 
40
34
  view = namespace.get("view")
41
35
  if view is None:
42
- raise AssertionError(f"`{path.name}` must define a top-level `view` variable")
36
+ msg = f"`{path.name}` must define a top-level `view` variable"
37
+ raise AssertionError(msg)
43
38
  if not hasattr(view, "to_dict"):
44
- raise AssertionError(f"`view` in `{path.name}` does not expose to_dict()")
39
+ msg = f"`view` in `{path.name}` does not expose to_dict()"
40
+ raise AssertionError(msg)
45
41
  # Execute to_dict() within the spec namespace so frame inspection finds the right variables
46
42
  namespace["__view__"] = view
47
- exec("__result__ = __view__.to_dict()", namespace)
43
+ exec("__result__ = __view__.to_dict()", namespace) # ruff: ignore[exec-builtin]
48
44
  return namespace["__result__"]
49
45
 
50
46
 
@@ -53,13 +49,13 @@ ESM_EXAMPLES = sorted(path.stem for path in ESM_DIR.glob("*.js"))
53
49
  PYTHON_EXAMPLES = sorted(path.stem for path in PYTHON_DIR.glob("*.py"))
54
50
 
55
51
 
56
- def test_generated_examples_stay_in_sync():
52
+ def test_generated_examples_stay_in_sync() -> None:
57
53
  assert PYTHON_EXAMPLES == JSON_EXAMPLES
58
54
  assert ESM_EXAMPLES == JSON_EXAMPLES
59
55
 
60
56
 
61
57
  @pytest.mark.parametrize("example_name", PYTHON_EXAMPLES)
62
- def test_python_round_trip(example_name: str):
58
+ def test_python_round_trip(example_name: str) -> None:
63
59
  generated = run_spec_file_with_exec(PYTHON_DIR / f"{example_name}.py")
64
60
  original = load_json_fixture(example_name)
65
61
 
@@ -1,25 +1,19 @@
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
- import sys
4
- from pathlib import Path
3
+ from __future__ import annotations
5
4
 
6
- import pytest
7
-
8
- ROOT = Path(__file__).resolve().parents[4]
9
- VGPLOT_PKG = ROOT / "packages" / "vgplot" / "vgplot-python"
10
- for p in (VGPLOT_PKG, ROOT):
11
- if str(p) not in sys.path:
12
- sys.path.insert(0, str(p))
5
+ from typing import TYPE_CHECKING
13
6
 
14
- import vgplot as vg # noqa: E402
15
- from vgplot.plot import Mark # noqa: E402
7
+ import pytest
8
+ import vgplot as vg
9
+ from vgplot.plot import Mark
16
10
 
17
11
 
18
12
  class TestGeneratedMarks:
19
13
  """Marks are generated from the schema; there is no dynamic fallback, so
20
14
  unknown names fail loudly rather than being silently fabricated."""
21
15
 
22
- def test_positional_data_with_encodings_is_kept(self):
16
+ def test_positional_data_with_encodings_is_kept(self) -> None:
23
17
  # A leading positional arg is the data source even when the call also
24
18
  # has keyword encodings.
25
19
  mark = vg.waffle_y("athletes", x="a", y={"count": ""})
@@ -29,45 +23,45 @@ class TestGeneratedMarks:
29
23
  assert d["data"] == {"from": "athletes"}
30
24
  assert d["x"] == "a"
31
25
 
32
- def test_explicit_none_channel_is_preserved(self):
26
+ def test_explicit_none_channel_is_preserved(self) -> None:
33
27
  # Passing a channel explicitly as None keeps it in the output (distinct
34
28
  # from not passing it at all).
35
29
  assert vg.line_y("t", x="a", z=None).to_dict()["z"] is None
36
30
  assert "z" not in vg.line_y("t", x="a").to_dict()
37
31
 
38
- def test_unknown_name_raises(self):
32
+ def test_unknown_name_raises(self) -> None:
39
33
  # No __getattr__ fallback: a missing or mis-typed API name is an error.
40
34
  with pytest.raises(AttributeError):
41
- vg.some_custom_mark # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
35
+ _ = vg.some_custom_mark # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[unresolved-attribute]
42
36
  with pytest.raises(AttributeError):
43
- 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]
44
38
 
45
39
 
46
40
  class TestDataHelpers:
47
- def test_json_inline_data(self):
41
+ def test_json_inline_data(self) -> None:
48
42
  d = vg.json([{"a": 1}, {"a": 2}])
49
43
  assert d.to_dict() == {"type": "json", "data": [{"a": 1}, {"a": 2}]}
50
44
 
51
- def test_json_file(self):
45
+ def test_json_file(self) -> None:
52
46
  assert vg.json(file="x.json").to_dict() == {"type": "json", "file": "x.json"}
53
47
 
54
48
 
55
49
  class TestParams:
56
- def test_date_param(self):
50
+ def test_date_param(self) -> None:
57
51
  from datetime import date
58
52
 
59
53
  assert vg.param(date(2013, 5, 13)).param_def() == {"date": "2013-05-13"}
60
54
 
61
- def test_datetime_param(self):
55
+ def test_datetime_param(self) -> None:
62
56
  from datetime import datetime
63
57
 
64
- 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]
65
59
  "date": "2013-05-13T10:30:00"
66
60
  }
67
61
 
68
62
 
69
63
  class TestAutoNaming:
70
- def test_auto_param_name_skips_explicit_name(self):
64
+ def test_auto_param_name_skips_explicit_name(self) -> None:
71
65
  from vgplot.spec import Spec
72
66
 
73
67
  named = vg.param(1)
@@ -86,7 +80,8 @@ class TestDataFrames:
86
80
  referenced as a table, and carried in the spec's data section for the host
87
81
  (e.g. the widget) to register."""
88
82
 
89
- def test_frame_is_referenced_and_named_after_variable(self):
83
+ def test_frame_is_referenced_and_named_after_variable(self) -> None:
84
+ pytest.importorskip("pyarrow")
90
85
  import pyarrow as pa
91
86
 
92
87
  weather = pa.table({"a": [1, 2, 3]})
@@ -96,7 +91,8 @@ class TestDataFrames:
96
91
  assert d["plot"][0]["data"] == {"from": "weather"}
97
92
  assert d["data"]["weather"] is weather
98
93
 
99
- def test_frame_and_datadef_share_the_data_section(self):
94
+ def test_frame_and_datadef_share_the_data_section(self) -> None:
95
+ pytest.importorskip("pyarrow")
100
96
  import pyarrow as pa
101
97
 
102
98
  weather = pa.table({"a": [1]})
@@ -108,3 +104,17 @@ class TestDataFrames:
108
104
  assert d["plot"][1]["data"] == {"from": "athletes"}
109
105
  assert d["data"]["weather"] is weather
110
106
  assert d["data"]["athletes"] == {"type": "csv", "file": "athletes.csv"}
107
+
108
+
109
+ if TYPE_CHECKING:
110
+
111
+ def typing_dunder_all() -> None:
112
+ # Ok
113
+ _ = vg.arrow
114
+ _ = vg.errorbar_x
115
+ _ = vg.mark
116
+
117
+ # TODO @dangotbanned: Get pyright to error on these
118
+ # Error (`_generated` modules)
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}"
@@ -0,0 +1,70 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ from typing import TYPE_CHECKING, Any, Union, get_type_hints
5
+
6
+ if TYPE_CHECKING:
7
+ # NOTE: Don't move this into a runtime import (yet)
8
+ from vgplot._types import UNSET
9
+
10
+
11
+ def _import_unset() -> UNSET:
12
+ # NOTE: A regular import would add:
13
+ # - `vgplot._types` to `sys.modules`
14
+ # - `UNSET` to `globals`
15
+ # And we can't use `importlib.reload`, since that would create a new object
16
+ from vgplot._types import UNSET
17
+
18
+ return UNSET
19
+
20
+
21
+ def test_unset_identity() -> None:
22
+ unset_1 = _import_unset()
23
+ unset_2 = _import_unset()
24
+ assert unset_1 is unset_2
25
+
26
+
27
+ def test_unset_repr() -> None:
28
+ assert repr(_import_unset()) == "UNSET"
29
+
30
+
31
+ def test_unset_pickle() -> None:
32
+ import pickle
33
+
34
+ unset = _import_unset()
35
+ assert pickle.loads(pickle.dumps(unset)) is unset
36
+
37
+
38
+ def test_unset_type_expression_union() -> None:
39
+ # Adapted from https://github.com/python/typing_extensions/blob/83400e979b8e3b0b647f9a6a57f0275230e5f19f/src/test_typing_extensions.py#L9694-L9701
40
+ from vgplot._types import UNSET
41
+
42
+ def func1(a: int | UNSET = UNSET) -> None: ...
43
+ def func2(a: UNSET | int = UNSET) -> None: ...
44
+
45
+ assert get_type_hints(func1, localns=locals())["a"] == Union[int, UNSET] # noqa: UP007
46
+ assert get_type_hints(func2, localns=locals())["a"] == Union[UNSET, int] # noqa: UP007
47
+
48
+
49
+ def test_unset_copy_identity() -> None:
50
+ # Adapted from https://github.com/python/typing_extensions/blob/83400e979b8e3b0b647f9a6a57f0275230e5f19f/src/test_typing_extensions.py#L9711-L9713
51
+ unset = _import_unset()
52
+ assert unset is copy.copy(unset)
53
+ assert unset is copy.deepcopy(unset)
54
+
55
+
56
+ def test_unset_union_identity() -> None:
57
+ unset = _import_unset()
58
+ assert (unset | unset) is unset
59
+
60
+
61
+ if TYPE_CHECKING:
62
+ from typing_extensions import assert_type
63
+
64
+ def typing_unset(
65
+ a: UNSET, b: str | UNSET, c: Any | UNSET, d: int | None | UNSET = UNSET
66
+ ) -> None:
67
+ assert_type(a, UNSET)
68
+ assert_type(b, str | UNSET)
69
+ assert_type(c, Any | UNSET)
70
+ assert_type(d, int | None | UNSET)
@@ -1,50 +1,52 @@
1
- from .spec import spec, Spec, View
2
- from .data import parquet, csv, spatial, table, json, data
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
- plot,
7
- source,
8
- mark,
9
+ brush,
10
+ checkbox,
11
+ color_legend,
9
12
  directive,
10
- margins,
11
- vconcat,
12
13
  hconcat,
13
- vspace,
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
- toggle,
29
- toggle_x,
30
- toggle_y,
31
- toggle_color,
19
+ interval_y,
20
+ margins,
21
+ mark,
22
+ menu,
32
23
  nearest_x,
33
24
  nearest_y,
34
- region,
35
- highlight,
25
+ opacity_legend,
26
+ option,
36
27
  pan,
37
28
  pan_x,
38
29
  pan_y,
39
30
  pan_zoom,
40
31
  pan_zoom_x,
41
32
  pan_zoom_y,
42
- color_legend,
43
- opacity_legend,
33
+ plot,
34
+ region,
35
+ search,
36
+ select,
37
+ slider,
38
+ sort,
39
+ source,
44
40
  symbol_legend,
41
+ table_input,
42
+ toggle,
43
+ toggle_color,
44
+ toggle_x,
45
+ toggle_y,
46
+ vconcat,
47
+ vspace,
45
48
  )
46
- from ._generated import * # noqa: F401, F403
47
- from ._generated import __all__ as _generated_all
49
+ from .spec import Spec, View, spec
48
50
 
49
51
  __all__ = [
50
52
  "Spec",
@@ -0,0 +1,101 @@
1
+ """Backwards compatibility for features not available at our [requires-python][1].
2
+
3
+ Import from here to avoid introducing runtime dependency on [typing_extensions][2].
4
+
5
+ [1]: https://packaging.python.org/en/latest/specifications/pyproject-toml/#requires-python
6
+ [2]: https://github.com/python/typing_extensions
7
+
8
+ ## sentinel
9
+ [`sentinel`][3] was introduced in `3.15` (see [PEP 661][4]).
10
+
11
+ We can remove the backport after [3.15 end-of-life][5].
12
+
13
+ [3]: https://docs.python.org/3.15/library/functions.html#sentinel
14
+ [4]: https://peps.python.org/pep-0661/
15
+ [5]: https://peps.python.org/pep-0790/#lifespan
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import sys
21
+ import typing
22
+ from contextlib import suppress
23
+ from importlib.util import find_spec
24
+ from typing import TYPE_CHECKING, Any, ClassVar
25
+
26
+
27
+ def _sentinel_backport_pre_typing_extensions_4_16() -> Any:
28
+ class _sentinel_backport:
29
+ """Create a unique sentinel object.
30
+
31
+ *name* should be the name of the variable to which the return value shall be assigned.
32
+ """
33
+
34
+ def __init__(self, name: str, /, *, repr: str | None = None) -> None:
35
+ self.__name__: str = name
36
+ self._repr: str = repr if repr is not None else name
37
+ # TODO @dangotbanned: Figure out why they didn't use the `"__main__"` default here?
38
+ module: str | None = None
39
+ if hasattr(sys, "_getframemodulename"):
40
+ module = sys._getframemodulename(1) or "__main__"
41
+ elif hasattr(sys, "_getframe"):
42
+ with suppress(ValueError):
43
+ module = sys._getframe(1).f_globals.get("__name__", "__main__")
44
+
45
+ # For pickling as a singleton
46
+ self.__module__ = module # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[invalid-assignment]
47
+
48
+ __init_subclass__: ClassVar[None] = None
49
+
50
+ def __repr__(self) -> str:
51
+ return self._repr
52
+
53
+ if sys.version_info < (3, 11):
54
+ # The presence of this method convinces typing._type_check that Sentinels are types.
55
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
56
+ msg = f"{type(self).__name__!r} object is not callable"
57
+ raise TypeError(msg)
58
+
59
+ def __or__(self, other: Any) -> Any:
60
+ return typing.Union[self, other] # noqa: UP007
61
+
62
+ def __ror__(self, other: Any) -> Any:
63
+ return typing.Union[other, self] # noqa: UP007
64
+
65
+ def __reduce__(self) -> str:
66
+ return self.__name__
67
+
68
+ return _sentinel_backport
69
+
70
+
71
+ def _sentinel_backport_pre_py_3_15() -> Any:
72
+ """Return a [PEP 661]-compatible [`sentinel`](https://docs.python.org/3.15/library/functions.html#sentinel) factory.
73
+
74
+ [PEP 661]: https://peps.python.org/pep-0661/
75
+
76
+ ## Notes
77
+ - Does not depend on `typing_extensions`, but will use it if a suitable version is available
78
+ - Fallback is adapted from [`typing_extensions==4.16.0`](https://github.com/python/typing_extensions/blob/f29cd28d8ed7642cafb1d18daf5aa41be6a5c0aa/src/typing_extensions.py#L176-L271)
79
+ """
80
+ if find_spec("typing_extensions"):
81
+ import typing_extensions
82
+
83
+ # NOTE: In the same release the name changed, this guy landed https://github.com/python/typing_extensions/pull/617
84
+ # `4.14-4.15` is fine for typing, but the runtime changes are too big to rely on
85
+ if hasattr(typing_extensions, "sentinel"):
86
+ return getattr(typing_extensions, "sentinel", typing_extensions.Sentinel)
87
+
88
+ return _sentinel_backport_pre_typing_extensions_4_16()
89
+
90
+
91
+ if TYPE_CHECKING:
92
+ # Was renamed in https://github.com/python/typing_extensions/releases/tag/4.16.0
93
+ from typing_extensions import Sentinel as sentinel
94
+ else: # ruff: ignore[collapsible-else-if]
95
+ if sys.version_info >= (3, 15):
96
+ from builtins import sentinel
97
+ else:
98
+ sentinel = _sentinel_backport_pre_py_3_15()
99
+
100
+
101
+ __all__ = ("sentinel",)
@@ -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]
12
+ __all__ = [*_mark_all, *_attr_all, *_enc_all] # pyright: ignore[reportUnsupportedDunderAll] # ruff: ignore[invalid-all-object]