vgplot 0.29.1__tar.gz → 0.30.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.
@@ -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
1
  Metadata-Version: 2.4
2
2
  Name: vgplot
3
- Version: 0.29.1
3
+ Version: 0.30.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.30.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,26 +3,18 @@
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
7
  import json
8
- import sys
9
8
  from pathlib import Path
10
9
  from typing import Any
11
10
 
12
11
  import pytest
13
12
 
14
- ROOT = Path(__file__).resolve().parents[4] # mosaic project root
15
- SPEC_DIR = ROOT / "specs"
13
+ SPEC_DIR = Path(__file__).resolve().parents[4] / "specs"
16
14
  JSON_DIR = SPEC_DIR / "json"
17
15
  ESM_DIR = SPEC_DIR / "esm"
18
16
  PYTHON_DIR = SPEC_DIR / "python"
19
17
 
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
18
 
27
19
  def load_json_fixture(name: str) -> dict:
28
20
  return json.loads((JSON_DIR / f"{name}.json").read_text(encoding="utf-8"))
@@ -1,18 +1,11 @@
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 typing import TYPE_CHECKING
5
4
 
6
5
  import pytest
7
6
 
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))
13
-
14
- import vgplot as vg # noqa: E402
15
- from vgplot.plot import Mark # noqa: E402
7
+ import vgplot as vg
8
+ from vgplot.plot import Mark
16
9
 
17
10
 
18
11
  class TestGeneratedMarks:
@@ -87,6 +80,7 @@ class TestDataFrames:
87
80
  (e.g. the widget) to register."""
88
81
 
89
82
  def test_frame_is_referenced_and_named_after_variable(self):
83
+ pytest.importorskip("pyarrow")
90
84
  import pyarrow as pa
91
85
 
92
86
  weather = pa.table({"a": [1, 2, 3]})
@@ -97,6 +91,7 @@ class TestDataFrames:
97
91
  assert d["data"]["weather"] is weather
98
92
 
99
93
  def test_frame_and_datadef_share_the_data_section(self):
94
+ pytest.importorskip("pyarrow")
100
95
  import pyarrow as pa
101
96
 
102
97
  weather = pa.table({"a": [1]})
@@ -108,3 +103,17 @@ class TestDataFrames:
108
103
  assert d["plot"][1]["data"] == {"from": "athletes"}
109
104
  assert d["data"]["weather"] is weather
110
105
  assert d["data"]["athletes"] == {"type": "csv", "file": "athletes.csv"}
106
+
107
+
108
+ if TYPE_CHECKING:
109
+
110
+ def typing_dunder_all() -> None:
111
+ # Ok
112
+ vg.arrow
113
+ vg.errorbar_x
114
+ vg.mark
115
+
116
+ # TODO @dangotbanned: Get pyright to error on these
117
+ # Error (`_generated` modules)
118
+ vg.attributes # ty: ignore[unresolved-attribute]
119
+ vg.marks # ty: ignore[unresolved-attribute]
@@ -4,16 +4,11 @@
4
4
  # schema addition that was never generated or hand-added.
5
5
  import json
6
6
  import re
7
- import sys
8
7
  from pathlib import Path
9
8
 
10
- ROOT = Path(__file__).resolve().parents[4]
11
- for p in (ROOT / "packages" / "vgplot" / "vgplot-python", ROOT):
12
- if str(p) not in sys.path:
13
- sys.path.insert(0, str(p))
14
-
15
- import vgplot as vg # noqa: E402
9
+ import vgplot as vg
16
10
 
11
+ ROOT = Path(__file__).resolve().parents[4]
17
12
  SCHEMA = ROOT / "docs" / "public" / "schema" / "latest.json"
18
13
 
19
14
  # Schema names whose Python export is renamed or intentionally absent.
@@ -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 # noqa: S301
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)
@@ -0,0 +1,103 @@
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
+
23
+ # ruff: noqa: A002
24
+ from contextlib import suppress
25
+ from importlib.util import find_spec
26
+ from typing import TYPE_CHECKING, Any, ClassVar
27
+
28
+
29
+ def _sentinel_backport_pre_typing_extensions_4_16() -> Any:
30
+ class _sentinel_backport:
31
+ """Create a unique sentinel object.
32
+
33
+ *name* should be the name of the variable to which the return value shall be assigned.
34
+ """
35
+
36
+ def __init__(self, name: str, /, *, repr: str | None = None) -> None:
37
+ self.__name__: str = name
38
+ self._repr: str = repr if repr is not None else name
39
+ # TODO @dangotbanned: Figure out why they didn't use the `"__main__"` default here?
40
+ module: str | None = None
41
+ if hasattr(sys, "_getframemodulename"):
42
+ module = sys._getframemodulename(1) or "__main__"
43
+ elif hasattr(sys, "_getframe"):
44
+ with suppress(ValueError):
45
+ module = sys._getframe(1).f_globals.get("__name__", "__main__")
46
+
47
+ # For pickling as a singleton
48
+ self.__module__ = module # pyright: ignore[reportAttributeAccessIssue] # ty: ignore[invalid-assignment]
49
+
50
+ __init_subclass__: ClassVar[None] = None
51
+
52
+ def __repr__(self) -> str:
53
+ return self._repr
54
+
55
+ if sys.version_info < (3, 11):
56
+ # The presence of this method convinces typing._type_check that Sentinels are types.
57
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
58
+ msg = f"{type(self).__name__!r} object is not callable"
59
+ raise TypeError(msg)
60
+
61
+ def __or__(self, other: Any) -> Any:
62
+ return typing.Union[self, other] # noqa: UP007
63
+
64
+ def __ror__(self, other: Any) -> Any:
65
+ return typing.Union[other, self] # noqa: UP007
66
+
67
+ def __reduce__(self) -> str:
68
+ return self.__name__
69
+
70
+ return _sentinel_backport
71
+
72
+
73
+ def _sentinel_backport_pre_py_3_15() -> Any:
74
+ """Return a [PEP 661]-compatible [`sentinel`](https://docs.python.org/3.15/library/functions.html#sentinel) factory.
75
+
76
+ [PEP 661]: https://peps.python.org/pep-0661/
77
+
78
+ ## Notes
79
+ - Does not depend on `typing_extensions`, but will use it if a suitable version is available
80
+ - Fallback is adapted from [`typing_extensions==4.16.0`](https://github.com/python/typing_extensions/blob/f29cd28d8ed7642cafb1d18daf5aa41be6a5c0aa/src/typing_extensions.py#L176-L271)
81
+ """
82
+ if find_spec("typing_extensions"):
83
+ import typing_extensions
84
+
85
+ # NOTE: In the same release the name changed, this guy landed https://github.com/python/typing_extensions/pull/617
86
+ # `4.14-4.15` is fine for typing, but the runtime changes are too big to rely on
87
+ if hasattr(typing_extensions, "sentinel"):
88
+ return getattr(typing_extensions, "sentinel", typing_extensions.Sentinel)
89
+
90
+ return _sentinel_backport_pre_typing_extensions_4_16()
91
+
92
+
93
+ if TYPE_CHECKING:
94
+ # Was renamed in https://github.com/python/typing_extensions/releases/tag/4.16.0
95
+ from typing_extensions import Sentinel as sentinel # noqa: N813
96
+ else: # noqa: PLR5501
97
+ if sys.version_info >= (3, 15):
98
+ from builtins import sentinel
99
+ else:
100
+ sentinel = _sentinel_backport_pre_py_3_15()
101
+
102
+
103
+ __all__ = ("sentinel",)
@@ -7,4 +7,4 @@ from .attributes import __all__ as _attr_all
7
7
  from .encodings import __all__ as _enc_all
8
8
  from .marks import __all__ as _mark_all
9
9
 
10
- __all__ = [*_mark_all, *_attr_all, *_enc_all]
10
+ __all__ = [*_mark_all, *_attr_all, *_enc_all] # pyright: ignore[reportUnsupportedDunderAll]
@@ -54,7 +54,7 @@ def column(col: TransformArg, **options: Any) -> dict[str, Any]:
54
54
  return _transform("column", (col,), options)
55
55
 
56
56
 
57
- def count(col: TransformArg = UNSET, **options: Any) -> dict[str, Any]:
57
+ def count(col: TransformArg | UNSET = UNSET, **options: Any) -> dict[str, Any]:
58
58
  """Compute the count of records in an aggregation group."""
59
59
  return _transform("count", (col,), options)
60
60
 
@@ -101,8 +101,8 @@ def geojson(col: TransformArg, **options: Any) -> dict[str, Any]:
101
101
 
102
102
  def lag(
103
103
  col: TransformArg,
104
- offset: TransformArg = UNSET,
105
- default: TransformArg = UNSET,
104
+ offset: TransformArg | UNSET = UNSET,
105
+ default: TransformArg | UNSET = UNSET,
106
106
  **options: Any,
107
107
  ) -> dict[str, Any]:
108
108
  """Compute lagging values in a column."""
@@ -121,8 +121,8 @@ def last_value(col: TransformArg, **options: Any) -> dict[str, Any]:
121
121
 
122
122
  def lead(
123
123
  col: TransformArg,
124
- offset: TransformArg = UNSET,
125
- default: TransformArg = UNSET,
124
+ offset: TransformArg | UNSET = UNSET,
125
+ default: TransformArg | UNSET = UNSET,
126
126
  **options: Any,
127
127
  ) -> dict[str, Any]:
128
128
  """Compute leading values in a column."""
@@ -150,7 +150,7 @@ def mode(col: TransformArg, **options: Any) -> dict[str, Any]:
150
150
 
151
151
 
152
152
  def nth_value(
153
- col: TransformArg, offset: TransformArg = UNSET, **options: Any
153
+ col: TransformArg, offset: TransformArg | UNSET = UNSET, **options: Any
154
154
  ) -> dict[str, Any]:
155
155
  """Get the nth value of the given column in the current window frame, counting from one."""
156
156
  return _transform("nth_value", (col, offset), options)