Skip to content

Commit b588a4e

Browse files
committed
Switch rendering to writer.render()
1 parent 7eaab8e commit b588a4e

16 files changed

Lines changed: 155 additions & 127 deletions

File tree

CLAUDE.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -165,16 +165,16 @@ let spec = reader.execute(
165165

166166
// Render to Vega-Lite JSON
167167
let writer = VegaLiteWriter::new();
168-
let json = spec.render(&writer)?;
168+
let json = writer.render(&spec)?;
169169
```
170170

171171
### Core Functions
172172

173-
| Function | Purpose |
174-
| ------------------------ | ------------------------------------------------------ |
175-
| `reader.execute(query)` | Main entry point: parse, execute SQL, resolve mappings |
176-
| `spec.render(writer)` | Generate output (Vega-Lite JSON) from Spec |
177-
| `validate(query)` | Validate syntax + semantics, inspect query structure |
173+
| Function | Purpose |
174+
| ----------------------- | ------------------------------------------------------ |
175+
| `reader.execute(query)` | Main entry point: parse, execute SQL, resolve mappings |
176+
| `writer.render(spec)` | Generate output from a Spec |
177+
| `validate(query)` | Validate syntax + semantics, inspect query structure |
178178

179179
### Key Types
180180

@@ -909,7 +909,7 @@ print(f"SQL: {spec.sql()}")
909909

910910
# Render to Vega-Lite JSON
911911
writer = ggsql.VegaLiteWriter()
912-
json_output = spec.render(writer)
912+
json_output = writer.render(spec)
913913
```
914914

915915
**Convenience Function** (`render_altair`):
@@ -942,21 +942,21 @@ print(f"Errors: {validated.errors()}")
942942

943943
**Classes**:
944944

945-
| Class | Description |
946-
| -------------------------- | ------------------------------------------------ |
947-
| `DuckDBReader(connection)` | Database reader with DataFrame registration |
948-
| `VegaLiteWriter()` | Vega-Lite JSON output writer |
949-
| `Validated` | Result of `validate()` with query inspection |
945+
| Class | Description |
946+
| -------------------------- | ------------------------------------------------- |
947+
| `DuckDBReader(connection)` | Database reader with DataFrame registration |
948+
| `VegaLiteWriter()` | Vega-Lite JSON output writer |
949+
| `Validated` | Result of `validate()` with query inspection |
950950
| `Spec` | Result of `reader.execute()`, ready for rendering |
951951

952952
**Functions**:
953953

954-
| Function | Description |
955-
| -------------------------- | ------------------------------------------------- |
956-
| `validate(query)` | Syntax/semantic validation with query inspection |
957-
| `reader.execute(query)` | Execute ggsql query, return Spec |
958-
| `execute(query, reader)` | Execute with custom reader (bridge path) |
959-
| `render_altair(df, viz)` | Convenience: render DataFrame to Altair chart |
954+
| Function | Description |
955+
| ------------------------ | ------------------------------------------------ |
956+
| `validate(query)` | Syntax/semantic validation with query inspection |
957+
| `reader.execute(query)` | Execute ggsql query, return Spec |
958+
| `execute(query, reader)` | Execute with custom reader (bridge path) |
959+
| `render_altair(df, viz)` | Convenience: render DataFrame to Altair chart |
960960

961961
**Spec Methods**:
962962

README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,10 @@ chart.display()
327327
reader = ggsql.DuckDBReader("duckdb://memory")
328328
reader.register("data", df)
329329

330-
prepared = ggsql.prepare(
331-
"SELECT * FROM data VISUALISE x, y DRAW point",
332-
reader
333-
)
330+
spec = reader.execute("SELECT * FROM data VISUALISE x, y DRAW point")
334331

335332
writer = ggsql.VegaLiteWriter()
336-
json_output = prepared.render(writer)
333+
json_output = writer.render(spec)
337334
```
338335

339336
See the [ggsql-python README](ggsql-python/README.md) for complete API documentation.

ggsql-jupyter/src/executor.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use anyhow::Result;
77
use ggsql::{
88
reader::{DuckDBReader, Reader},
99
validate,
10-
writer::VegaLiteWriter,
10+
writer::{VegaLiteWriter, Writer},
1111
};
1212
use polars::frame::DataFrame;
1313

@@ -77,8 +77,8 @@ impl QueryExecutor {
7777
spec.metadata().layer_count
7878
);
7979

80-
// 4. Render to Vega-Lite
81-
let vega_json = spec.render(&self.writer)?;
80+
// 4. Render to output format
81+
let vega_json = self.writer.render(&spec)?;
8282

8383
tracing::debug!("Generated Vega-Lite spec: {} chars", vega_json.len());
8484

ggsql-python/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ print(spec.data()) # Returns polars DataFrame
106106

107107
# 6. Render to Vega-Lite JSON
108108
writer = ggsql.VegaLiteWriter()
109-
vegalite_json = spec.render(writer)
109+
vegalite_json = writer.render(spec)
110110
print(vegalite_json)
111111
```
112112

@@ -135,7 +135,7 @@ Writer that generates Vega-Lite v6 JSON specifications.
135135

136136
```python
137137
writer = ggsql.VegaLiteWriter()
138-
json_output = spec.render(writer)
138+
json_output = writer.render(spec)
139139
```
140140

141141
#### `Validated`
@@ -259,7 +259,7 @@ spec = ggsql.execute(
259259
reader
260260
)
261261
writer = ggsql.VegaLiteWriter()
262-
json_output = spec.render(writer)
262+
json_output = writer.render(spec)
263263
```
264264

265265
**Optional methods** for custom readers:

ggsql-python/python/ggsql/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def render_altair(
8484
# Execute and render
8585
spec = reader.execute(query)
8686
writer = VegaLiteWriter()
87-
vegalite_json = spec.render(writer)
87+
vegalite_json = writer.render(spec)
8888

8989
# Parse to determine the correct Altair class
9090
spec = json.loads(vegalite_json)

ggsql-python/src/lib.rs

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ use pyo3::prelude::*;
66
use pyo3::types::{PyBytes, PyDict, PyList};
77
use std::io::Cursor;
88

9-
use ggsql::validate::{validate as rust_validate, ValidationWarning};
109
use ggsql::reader::Spec;
1110
use ggsql::reader::{DuckDBReader as RustDuckDBReader, Reader};
12-
use ggsql::writer::VegaLiteWriter as RustVegaLiteWriter;
11+
use ggsql::validate::{validate as rust_validate, ValidationWarning};
12+
use ggsql::writer::{VegaLiteWriter as RustVegaLiteWriter, Writer as RustWriter};
1313
use ggsql::GgsqlError;
1414

1515
use polars::prelude::{DataFrame, IpcReader, IpcWriter, SerReader, SerWriter};
@@ -308,7 +308,8 @@ impl PyDuckDBReader {
308308
/// --------
309309
/// >>> reader = DuckDBReader("duckdb://memory")
310310
/// >>> spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")
311-
/// >>> json_output = spec.render(VegaLiteWriter())
311+
/// >>> writer = VegaLiteWriter()
312+
/// >>> json_output = writer.render(spec)
312313
fn execute(&self, query: &str) -> PyResult<PySpec> {
313314
self.inner
314315
.execute(query)
@@ -329,7 +330,7 @@ impl PyDuckDBReader {
329330
/// --------
330331
/// >>> writer = VegaLiteWriter()
331332
/// >>> spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")
332-
/// >>> json_output = spec.render(writer)
333+
/// >>> json_output = writer.render(spec)
333334
#[pyclass(name = "VegaLiteWriter")]
334335
struct PyVegaLiteWriter {
335336
inner: RustVegaLiteWriter,
@@ -349,6 +350,35 @@ impl PyVegaLiteWriter {
349350
inner: RustVegaLiteWriter::new(),
350351
}
351352
}
353+
354+
/// Render a Spec to Vega-Lite JSON output
355+
///
356+
/// Parameters
357+
/// ----------
358+
/// spec : Spec
359+
/// The visualization specification from reader.execute().
360+
///
361+
/// Returns
362+
/// -------
363+
/// str
364+
/// The output (i.e., Vega-Lite JSON string).
365+
///
366+
/// Raises
367+
/// ------
368+
/// ValueError
369+
/// If rendering fails.
370+
///
371+
/// Examples
372+
/// --------
373+
/// >>> reader = DuckDBReader("duckdb://memory")
374+
/// >>> spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")
375+
/// >>> writer = VegaLiteWriter()
376+
/// >>> json_output = writer.render(spec)
377+
fn render(&self, spec: &PySpec) -> PyResult<String> {
378+
self.inner
379+
.render(&spec.inner)
380+
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))
381+
}
352382
}
353383

354384
// ============================================================================
@@ -439,42 +469,21 @@ impl PyValidated {
439469
/// Result of reader.execute(), ready for rendering.
440470
///
441471
/// Contains the resolved plot specification, data, and metadata.
442-
/// Use render() to generate Vega-Lite JSON output.
472+
/// Use writer.render(spec) to generate output.
443473
///
444474
/// Examples
445475
/// --------
446476
/// >>> spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")
447477
/// >>> print(f"Rows: {spec.metadata()['rows']}")
448-
/// >>> json_output = spec.render(VegaLiteWriter())
478+
/// >>> writer = VegaLiteWriter()
479+
/// >>> json_output = writer.render(spec)
449480
#[pyclass(name = "Spec")]
450481
struct PySpec {
451482
inner: Spec,
452483
}
453484

454485
#[pymethods]
455486
impl PySpec {
456-
/// Render to output format (Vega-Lite JSON).
457-
///
458-
/// Parameters
459-
/// ----------
460-
/// writer : VegaLiteWriter
461-
/// The writer to use for rendering.
462-
///
463-
/// Returns
464-
/// -------
465-
/// str
466-
/// The Vega-Lite JSON specification as a string.
467-
///
468-
/// Raises
469-
/// ------
470-
/// ValueError
471-
/// If rendering fails.
472-
fn render(&self, writer: &PyVegaLiteWriter) -> PyResult<String> {
473-
self.inner
474-
.render(&writer.inner)
475-
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))
476-
}
477-
478487
/// Get visualization metadata.
479488
///
480489
/// Returns
@@ -688,8 +697,9 @@ fn validate(query: &str) -> PyResult<PyValidated> {
688697
/// --------
689698
/// >>> # Using native reader (prefer reader.execute() instead)
690699
/// >>> reader = DuckDBReader("duckdb://memory")
691-
/// >>> spec = execute("SELECT 1 AS x, 2 AS Y VISUALISE x, y DRAW point", reader)
692-
/// >>> json_output = spec.render(VegaLiteWriter())
700+
/// >>> spec = execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point", reader)
701+
/// >>> writer = VegaLiteWriter()
702+
/// >>> json_output = writer.render(spec)
693703
///
694704
/// >>> # Using custom Python reader
695705
/// >>> class MyReader:

ggsql-python/tests/test_ggsql.py

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,7 @@ class TestExecute:
9898

9999
def test_execute_simple_query(self):
100100
reader = ggsql.DuckDBReader("duckdb://memory")
101-
spec = reader.execute(
102-
"SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point"
103-
)
101+
spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")
104102
assert spec is not None
105103
assert spec.layer_count() == 1
106104

@@ -127,23 +125,17 @@ def test_execute_metadata(self):
127125

128126
def test_execute_sql_accessor(self):
129127
reader = ggsql.DuckDBReader("duckdb://memory")
130-
spec = reader.execute(
131-
"SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point"
132-
)
128+
spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")
133129
assert "SELECT" in spec.sql()
134130

135131
def test_execute_visual_accessor(self):
136132
reader = ggsql.DuckDBReader("duckdb://memory")
137-
spec = reader.execute(
138-
"SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point"
139-
)
133+
spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")
140134
assert "VISUALISE" in spec.visual()
141135

142136
def test_execute_data_accessor(self):
143137
reader = ggsql.DuckDBReader("duckdb://memory")
144-
spec = reader.execute(
145-
"SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point"
146-
)
138+
spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")
147139
data = spec.data()
148140
assert isinstance(data, pl.DataFrame)
149141
assert data.shape == (1, 2)
@@ -154,17 +146,15 @@ def test_execute_without_visualise_fails(self):
154146
reader.execute("SELECT 1 AS x, 2 AS y")
155147

156148

157-
class TestSpecRender:
158-
"""Tests for Spec.render() method."""
149+
class TestWriterRender:
150+
"""Tests for VegaLiteWriter.render() method."""
159151

160152
def test_render_to_vegalite(self):
161153
reader = ggsql.DuckDBReader("duckdb://memory")
162-
spec = reader.execute(
163-
"SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point"
164-
)
154+
spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")
165155
writer = ggsql.VegaLiteWriter()
166156

167-
result = spec.render(writer)
157+
result = writer.render(spec)
168158
assert isinstance(result, str)
169159

170160
spec_dict = json.loads(result)
@@ -179,7 +169,7 @@ def test_render_contains_data(self):
179169
spec = reader.execute("SELECT * FROM data VISUALISE x, y DRAW point")
180170
writer = ggsql.VegaLiteWriter()
181171

182-
result = spec.render(writer)
172+
result = writer.render(spec)
183173
spec_dict = json.loads(result)
184174
# Data should be in the spec (either inline or in datasets)
185175
assert "data" in spec_dict or "datasets" in spec_dict
@@ -194,7 +184,7 @@ def test_render_multi_layer(self):
194184
)
195185
writer = ggsql.VegaLiteWriter()
196186

197-
result = spec.render(writer)
187+
result = writer.render(spec)
198188
spec_dict = json.loads(result)
199189
assert "layer" in spec_dict
200190

@@ -367,7 +357,7 @@ def test_end_to_end_workflow(self):
367357

368358
# Render to Vega-Lite
369359
writer = ggsql.VegaLiteWriter()
370-
result = spec.render(writer)
360+
result = writer.render(spec)
371361

372362
# Verify output
373363
spec_dict = json.loads(result)
@@ -377,9 +367,7 @@ def test_end_to_end_workflow(self):
377367
def test_can_introspect_spec(self):
378368
"""Test all introspection methods on Spec."""
379369
reader = ggsql.DuckDBReader("duckdb://memory")
380-
spec = reader.execute(
381-
"SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point"
382-
)
370+
spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")
383371

384372
# All these should work without error
385373
assert spec.sql() is not None
@@ -430,9 +418,7 @@ def register(self, name: str, df: pl.DataFrame) -> None:
430418
self.tables[name] = df
431419

432420
reader = RegisterReader()
433-
spec = ggsql.execute(
434-
"SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point", reader
435-
)
421+
spec = ggsql.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point", reader)
436422
assert spec is not None
437423

438424
def test_custom_reader_error_handling(self):
@@ -460,9 +446,7 @@ def execute_sql(self, sql: str):
460446
def test_native_reader_fast_path(self):
461447
"""Native DuckDBReader still works (fast path)."""
462448
reader = ggsql.DuckDBReader("duckdb://memory")
463-
spec = reader.execute(
464-
"SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point"
465-
)
449+
spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")
466450
assert spec.metadata()["rows"] == 1
467451

468452
def test_custom_reader_can_render(self):
@@ -485,7 +469,7 @@ def execute_sql(self, sql: str) -> pl.DataFrame:
485469
)
486470

487471
writer = ggsql.VegaLiteWriter()
488-
result = spec.render(writer)
472+
result = writer.render(spec)
489473

490474
spec_dict = json.loads(result)
491475
assert "$schema" in spec_dict

0 commit comments

Comments
 (0)