vgplot 0.1.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.
@@ -0,0 +1,38 @@
1
+ .DS_Store
2
+ .mosaic
3
+ .duckdb
4
+ .ipynb_checkpoints
5
+ .idea
6
+ dev/specs
7
+ docs/.vitepress/cache
8
+ docs/.vitepress/dist
9
+ node_modules
10
+ **/dist
11
+ **/test-output
12
+ **/__pycache__
13
+ **/.ruff_cache
14
+ **/.pytest_cache
15
+ **/.mypy_cache
16
+ **/.coverage
17
+ **/.venv
18
+ **/tsconfig.tsbuildinfo
19
+ packages/server/duckdb-server-go/duckdb-server-go
20
+ transitionFolder
21
+
22
+ # Virtual environment
23
+ venv/
24
+ .env/
25
+
26
+ # Conda environment
27
+ conda-env/
28
+
29
+ # Python cache
30
+ __pycache__/
31
+ *.pyc
32
+
33
+ # Test cache
34
+ .pytest_cache/
35
+ .coverage
36
+
37
+ # Python API Generated classes
38
+ packages/vgplot-python/schema_wrapper/generated_classes.py
vgplot-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.4
2
+ Name: vgplot
3
+ Version: 0.1.0
4
+ Summary: Schema wrapper classes for Mosaic
5
+ Project-URL: homepage, https://github.com/uwdata/mosaic
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: narwhals
8
+ Requires-Dist: pyyaml
9
+ Description-Content-Type: text/markdown
10
+
11
+ # Mosaic Python API
12
+
13
+ ## Generate the API from the JSON schama
14
+
15
+ `uv run generate-schema-wrapper`.
16
+
17
+ ## Run Tests
18
+
19
+ `uv run pytest -v`
20
+
21
+ ## Run the ruff formatter
22
+
23
+ `uv run ruff format` and `uv run ruff check`.
24
+
25
+ ## Test notebook
26
+
27
+ `uv run jupyter lab` and then open `example.ipynb`.
vgplot-0.1.0/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # Mosaic Python API
2
+
3
+ ## Generate the API from the JSON schama
4
+
5
+ `uv run generate-schema-wrapper`.
6
+
7
+ ## Run Tests
8
+
9
+ `uv run pytest -v`
10
+
11
+ ## Run the ruff formatter
12
+
13
+ `uv run ruff format` and `uv run ruff check`.
14
+
15
+ ## Test notebook
16
+
17
+ `uv run jupyter lab` and then open `example.ipynb`.
@@ -0,0 +1 @@
1
+ # Mosaic Python package namespace for vgplot authoring API.
@@ -0,0 +1,119 @@
1
+ from .spec import meta, spec, Spec
2
+ from .data import parquet, table, data
3
+ from .plot import (
4
+ plot,
5
+ from_,
6
+ rule_y,
7
+ rule_x,
8
+ line_y,
9
+ line_x,
10
+ bar_y,
11
+ bar_x,
12
+ area_y,
13
+ area_x,
14
+ text,
15
+ dot,
16
+ density,
17
+ y_grid,
18
+ y_label,
19
+ y_tick_format,
20
+ x_tick_format,
21
+ x_axis,
22
+ y_axis,
23
+ x_label,
24
+ x_label_anchor,
25
+ y_label_anchor,
26
+ r_range,
27
+ color_domain,
28
+ color_scale,
29
+ x_tick_size,
30
+ y_tick_size,
31
+ width,
32
+ height,
33
+ margins,
34
+ vconcat,
35
+ hconcat,
36
+ vspace,
37
+ hspace,
38
+ mark,
39
+ directive,
40
+ slider,
41
+ select,
42
+ checkbox,
43
+ input,
44
+ )
45
+
46
+ __all__ = [
47
+ "meta",
48
+ "spec",
49
+ "Spec",
50
+ "parquet",
51
+ "table",
52
+ "data",
53
+ "plot",
54
+ "from_",
55
+ "mark",
56
+ "rule_y",
57
+ "rule_x",
58
+ "line_y",
59
+ "line_x",
60
+ "bar_y",
61
+ "bar_x",
62
+ "area_y",
63
+ "area_x",
64
+ "text",
65
+ "dot",
66
+ "density",
67
+ "y_grid",
68
+ "y_label",
69
+ "y_tick_format",
70
+ "x_tick_format",
71
+ "x_axis",
72
+ "y_axis",
73
+ "x_label",
74
+ "x_label_anchor",
75
+ "y_label_anchor",
76
+ "r_range",
77
+ "color_domain",
78
+ "color_scale",
79
+ "x_tick_size",
80
+ "y_tick_size",
81
+ "width",
82
+ "height",
83
+ "margins",
84
+ "vconcat",
85
+ "hconcat",
86
+ "vspace",
87
+ "hspace",
88
+ "directive",
89
+ "slider",
90
+ "select",
91
+ "checkbox",
92
+ "input",
93
+ ]
94
+
95
+
96
+ def __getattr__(name: str):
97
+ """Dynamic mark/directive factory for names not explicitly exported.
98
+
99
+ Allows any snake_case mark or directive to be used without an explicit
100
+ function definition, e.g.::
101
+ """
102
+ if name.startswith("_"):
103
+ raise AttributeError(name)
104
+
105
+ from .plot import Directive, Mark
106
+ from .util import camelize
107
+
108
+ camel = camelize(name)
109
+
110
+ def _factory(*args, data=None, **kwargs):
111
+ if len(args) == 1 and data is None and not kwargs:
112
+ # Single positional arg to directive
113
+ return Directive(camel, args[0])
114
+ # otherwise creates a mark
115
+ return Mark(camel, data=data, enc=kwargs if kwargs else None)
116
+
117
+ _factory.__name__ = name
118
+ _factory.__qualname__ = f"vgplot.{name}"
119
+ return _factory
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Dict
5
+
6
+
7
+ @dataclass
8
+ class DataDef:
9
+ payload: Dict[str, Any]
10
+
11
+ def to_dict(self) -> Dict[str, Any]:
12
+ return self.payload
13
+
14
+
15
+ def parquet(file: str) -> DataDef:
16
+ return DataDef({"type": "parquet", "file": file})
17
+
18
+
19
+ def table(query: str) -> DataDef:
20
+ return DataDef({"type": "table", "query": query})
21
+
22
+
23
+ def data(**named_defs: DataDef) -> Dict[str, Any]:
24
+ out: Dict[str, Any] = {}
25
+ for k, v in named_defs.items():
26
+ out[k] = v.to_dict() if hasattr(v, "to_dict") else v
27
+ return out
@@ -0,0 +1,266 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Dict, List, Optional, Union
5
+
6
+ from .util import camelize, omit_none
7
+
8
+
9
+ class FromRef:
10
+ def __init__(self, name: str):
11
+ self.name = name
12
+
13
+ def to_dict(self) -> Dict[str, str]:
14
+ return {"from": self.name}
15
+
16
+
17
+ def from_(name: str) -> FromRef:
18
+ return FromRef(name)
19
+
20
+
21
+ @dataclass
22
+ class Directive:
23
+ key: str
24
+ value: Any
25
+
26
+ def to_kv(self):
27
+ return camelize(self.key), self.value
28
+
29
+
30
+ @dataclass
31
+ class Mark:
32
+ mark: str
33
+ data: Optional[Any] = None
34
+ enc: Optional[Dict[str, Any]] = None
35
+
36
+ def to_dict(self) -> Dict[str, Any]:
37
+ payload: Dict[str, Any] = {"mark": self.mark}
38
+ if self.data is not None:
39
+ payload["data"] = encode_value(self.data)
40
+ if self.enc:
41
+ for k, v in self.enc.items():
42
+ payload[camelize(k)] = encode_value(v)
43
+ return payload
44
+
45
+
46
+ def encode_value(v: Any) -> Any:
47
+ if isinstance(v, FromRef):
48
+ return v.to_dict()
49
+ if isinstance(v, Mark):
50
+ return v.to_dict()
51
+ if isinstance(v, list):
52
+ return [encode_value(x) for x in v]
53
+ if isinstance(v, dict):
54
+ return {k: encode_value(val) for k, val in v.items()}
55
+ return v
56
+
57
+
58
+ def plot(*items: Union[Mark, Directive]) -> Dict[str, Any]:
59
+ marks: List[Dict[str, Any]] = []
60
+ directives: Dict[str, Any] = {}
61
+ for item in items:
62
+ if isinstance(item, Mark):
63
+ marks.append(item.to_dict())
64
+ elif isinstance(item, Directive):
65
+ k, v = item.to_kv()
66
+ directives[k] = v
67
+ elif isinstance(item, dict):
68
+ # Treat dicts as already-structured mark entries.
69
+ marks.append(item)
70
+ else:
71
+ raise TypeError(f"Unsupported plot item: {item}")
72
+ root: Dict[str, Any] = {"plot": marks}
73
+ root.update(directives)
74
+ return root
75
+
76
+
77
+ def directive(key: str, value: Any) -> Directive:
78
+ return Directive(key, value)
79
+
80
+
81
+ def mark(name: str, data: Any = None, **enc: Any) -> Mark:
82
+ return Mark(name, data=data, enc=enc)
83
+
84
+
85
+ def rule_y(data: Any = None, **enc: Any) -> Mark:
86
+ return Mark("ruleY", data=data, enc=enc)
87
+
88
+
89
+ def rule_x(data: Any = None, **enc: Any) -> Mark:
90
+ return Mark("ruleX", data=data, enc=enc)
91
+
92
+
93
+ def line_y(data: Any = None, **enc: Any) -> Mark:
94
+ return Mark("lineY", data=data, enc=enc)
95
+
96
+
97
+ def line_x(data: Any = None, **enc: Any) -> Mark:
98
+ return Mark("lineX", data=data, enc=enc)
99
+
100
+
101
+ def bar_y(data: Any = None, **enc: Any) -> Mark:
102
+ return Mark("barY", data=data, enc=enc)
103
+
104
+
105
+ def bar_x(data: Any = None, **enc: Any) -> Mark:
106
+ return Mark("barX", data=data, enc=enc)
107
+
108
+
109
+ def area_y(data: Any = None, **enc: Any) -> Mark:
110
+ return Mark("areaY", data=data, enc=enc)
111
+
112
+
113
+ def area_x(data: Any = None, **enc: Any) -> Mark:
114
+ return Mark("areaX", data=data, enc=enc)
115
+
116
+
117
+ def text(data: Any = None, **enc: Any) -> Mark:
118
+ return Mark("text", data=data, enc=enc)
119
+
120
+
121
+ def dot(data: Any = None, **enc: Any) -> Mark:
122
+ return Mark("dot", data=data, enc=enc)
123
+
124
+
125
+ def density(data: Any = None, **enc: Any) -> Mark:
126
+ return Mark("density", data=data, enc=enc)
127
+
128
+
129
+ def y_grid(value: bool) -> Directive:
130
+ return Directive("y_grid", value)
131
+
132
+
133
+ def y_label(value: str) -> Directive:
134
+ return Directive("y_label", value)
135
+
136
+
137
+ def y_tick_format(value: str) -> Directive:
138
+ return Directive("y_tick_format", value)
139
+
140
+
141
+ def x_tick_format(value: str) -> Directive:
142
+ return Directive("x_tick_format", value)
143
+
144
+
145
+ def x_axis(value: Any) -> Directive:
146
+ return Directive("x_axis", value)
147
+
148
+
149
+ def y_axis(value: Any) -> Directive:
150
+ return Directive("y_axis", value)
151
+
152
+
153
+ def x_label(value: str) -> Directive:
154
+ return Directive("x_label", value)
155
+
156
+
157
+ def x_label_anchor(value: str) -> Directive:
158
+ return Directive("x_label_anchor", value)
159
+
160
+
161
+ def y_label_anchor(value: str) -> Directive:
162
+ return Directive("y_label_anchor", value)
163
+
164
+
165
+ def r_range(value: Any) -> Directive:
166
+ return Directive("r_range", value)
167
+
168
+
169
+ def color_domain(value: Any) -> Directive:
170
+ return Directive("color_domain", value)
171
+
172
+
173
+ def color_scale(value: Any) -> Directive:
174
+ return Directive("color_scale", value)
175
+
176
+
177
+ def width(value: int) -> Directive:
178
+ return Directive("width", value)
179
+
180
+
181
+ def height(value: int) -> Directive:
182
+ return Directive("height", value)
183
+
184
+
185
+ def margins(
186
+ top: int = None,
187
+ right: int = None,
188
+ bottom: int = None,
189
+ left: int = None,
190
+ **kwargs: Any,
191
+ ) -> Directive:
192
+ return Directive(
193
+ "margins",
194
+ omit_none(
195
+ {"top": top, "right": right, "bottom": bottom, "left": left, **kwargs}
196
+ ),
197
+ )
198
+
199
+
200
+ def x_tick_size(value: Any) -> Directive:
201
+ return Directive("x_tick_size", value)
202
+
203
+
204
+ def y_tick_size(value: Any) -> Directive:
205
+ return Directive("y_tick_size", value)
206
+
207
+
208
+ # Layout helpers
209
+ def vconcat(*items: Any) -> Dict[str, Any]:
210
+ return {"vconcat": [encode_value(i) for i in items]}
211
+
212
+
213
+ def hconcat(*items: Any) -> Dict[str, Any]:
214
+ return {"hconcat": [encode_value(i) for i in items]}
215
+
216
+
217
+ def hspace(px: int) -> Dict[str, Any]:
218
+ return {"hspace": px}
219
+
220
+
221
+ def vspace(px: int) -> Dict[str, Any]:
222
+ return {"vspace": px}
223
+
224
+
225
+ # Input helpers
226
+ def input(kind: str, **opts: Any) -> Dict[str, Any]:
227
+ payload = {"input": kind}
228
+ payload.update({camelize(k): v for k, v in opts.items() if v is not None})
229
+ return payload
230
+
231
+
232
+ def slider(
233
+ label: str,
234
+ as_: str,
235
+ min: Any = None,
236
+ max: Any = None,
237
+ step: Any = None,
238
+ value: Any = None,
239
+ **opts: Any,
240
+ ) -> Dict[str, Any]:
241
+ return input(
242
+ "slider", label=label, as_=as_, min=min, max=max, step=step, value=value, **opts
243
+ )
244
+
245
+
246
+ def select(
247
+ label: str,
248
+ as_: str,
249
+ options: Any,
250
+ multiple: bool = False,
251
+ value: Any = None,
252
+ **opts: Any,
253
+ ) -> Dict[str, Any]:
254
+ return input(
255
+ "select",
256
+ label=label,
257
+ as_=as_,
258
+ options=options,
259
+ multiple=multiple,
260
+ value=value,
261
+ **opts,
262
+ )
263
+
264
+
265
+ def checkbox(label: str, as_: str, value: bool = False, **opts: Any) -> Dict[str, Any]:
266
+ return input("checkbox", label=label, as_=as_, value=value, **opts)
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from typing import Any, Dict, Optional
6
+
7
+ from .util import omit_none
8
+
9
+
10
+ @dataclass
11
+ class Meta:
12
+ title: Optional[str] = None
13
+ description: Optional[str] = None
14
+ credit: Optional[str] = None
15
+
16
+ def to_dict(self) -> Dict[str, Any]:
17
+ return omit_none(
18
+ {
19
+ "title": self.title,
20
+ "description": self.description,
21
+ "credit": self.credit,
22
+ }
23
+ )
24
+
25
+
26
+ def meta(
27
+ title: str | None = None, description: str | None = None, credit: str | None = None
28
+ ) -> Meta:
29
+ return Meta(title=title, description=description, credit=credit)
30
+
31
+
32
+ class Spec:
33
+ def __init__(
34
+ self,
35
+ *,
36
+ meta: Meta | Dict[str, Any] | None = None,
37
+ data: Dict[str, Any] | None = None,
38
+ params: Dict[str, Any] | None = None,
39
+ plotDefaults: Dict[str, Any] | None = None,
40
+ config: Dict[str, Any] | None = None,
41
+ view: Dict[str, Any] | None = None,
42
+ **extra: Any,
43
+ ) -> None:
44
+ self.meta = meta.to_dict() if hasattr(meta, "to_dict") else meta
45
+ self.data = data
46
+ self.params = params
47
+ self.plotDefaults = plotDefaults
48
+ self.config = config
49
+ self.view = view or {}
50
+ self.extra = extra
51
+
52
+ def to_dict(self) -> Dict[str, Any]:
53
+ base: Dict[str, Any] = {}
54
+ if self.meta:
55
+ base["meta"] = self.meta
56
+ if self.config:
57
+ base["config"] = self.config
58
+ if self.data:
59
+ base["data"] = self.data
60
+ if self.params:
61
+ base["params"] = self.params
62
+ if self.plotDefaults:
63
+ base["plotDefaults"] = self.plotDefaults
64
+ base.update(self.view)
65
+ base.update(omit_none(self.extra))
66
+ return base
67
+
68
+ def to_json(self, **kwargs: Any) -> str:
69
+ return json.dumps(self.to_dict(), **kwargs)
70
+
71
+
72
+ def spec(
73
+ *,
74
+ meta: Meta | Dict[str, Any] | None = None,
75
+ data: Dict[str, Any] | None = None,
76
+ params: Dict[str, Any] | None = None,
77
+ plotDefaults: Dict[str, Any] | None = None,
78
+ config: Dict[str, Any] | None = None,
79
+ view: Dict[str, Any] | None = None,
80
+ **extra: Any,
81
+ ) -> Spec:
82
+ return Spec(
83
+ meta=meta,
84
+ data=data,
85
+ params=params,
86
+ plotDefaults=plotDefaults,
87
+ config=config,
88
+ view=view,
89
+ **extra,
90
+ )
@@ -0,0 +1,14 @@
1
+ from typing import Any, Dict
2
+
3
+
4
+ def camelize(key: str) -> str:
5
+ """Convert snake_case to lowerCamelCase."""
6
+ if not key:
7
+ return key
8
+ parts = key.split("_")
9
+ return parts[0] + "".join(p[:1].upper() + p[1:] for p in parts[1:])
10
+
11
+
12
+ def omit_none(d: Dict[str, Any]) -> Dict[str, Any]:
13
+ """Return a copy without None values."""
14
+ return {k: v for k, v in d.items() if v is not None}
@@ -0,0 +1,43 @@
1
+ [project]
2
+ name = "vgplot"
3
+ version = "0.1.0"
4
+ description = "Schema wrapper classes for Mosaic"
5
+ readme = "README.md"
6
+ requires-python = ">=3.9"
7
+ dependencies = [
8
+ "pyyaml",
9
+ "narwhals"
10
+ ]
11
+
12
+ [project.urls]
13
+ homepage = "https://github.com/uwdata/mosaic"
14
+
15
+ [project.scripts]
16
+ generate-schema-wrapper = "schema_wrapper.generate_schema_wrapper:main"
17
+
18
+ [build-system]
19
+ requires = ["hatchling"]
20
+ build-backend = "hatchling.build"
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "jupyterlab>=4.2.5",
25
+ "mypy>=1.11.1",
26
+ "pytest>=7.4.4",
27
+ "pytest-cov",
28
+ "ruff>=0.6.1",
29
+ "pandas",
30
+ "mosaic-widget"
31
+ ]
32
+
33
+ [tool.uv.sources]
34
+ "mosaic-widget" = { workspace = true }
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["api", "schema_wrapper"]
38
+
39
+ # https://mypy.readthedocs.io/en/stable/config_file.html
40
+ [tool.mypy]
41
+ files = "src/**/*.py"
42
+ strict = false
43
+ ignore_missing_imports = true