Skip to content

Commit a2ddf17

Browse files
committed
fix: address Copilot review findings
- Bump declared Rust MSRV to 1.88: the native-writer dependency graph (parley 0.9) requires it, so 1.86 was not actually supported. - Validate dpi in SVGWriter/PDFWriter constructors (rejects non-positive and non-finite values) instead of letting them reach the renderer. - Add Python integration tests for builtin datasets (ggsql:penguins, ggsql:airquality) and dpi validation.
1 parent 9011a16 commit a2ddf17

4 files changed

Lines changed: 47 additions & 1 deletion

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
- Upgraded to ggsql Rust crate v0.5.2 (from v0.3.3). See the [crate changelog](https://github.com/posit-dev/ggsql/blob/main/CHANGELOG.md) for the many language and rendering improvements in 0.4.0–0.5.2.
1414
- Upgraded the Arrow dependency from v56 to v58.
15+
- Declared Rust MSRV is now 1.88 (required by the native-writer dependency graph, e.g. parley 0.9).
1516
- Temporarily vendored the ggsql crate (via `[patch.crates-io]`) with its duckdb pin relaxed to `~1.10506`, fixing bundled duckdb builds that fail on Windows/MSVC ([duckdb/duckdb-rs#783](https://github.com/duckdb/duckdb-rs/issues/783)). This will be removed once a ggsql release depends on duckdb `~1.10506`.
1617
- `VegaLiteWriter.render_chart()` and `render_altair()` now accept `validate`, `height`, and `width` as explicit keyword arguments. `height` and `width` default to `None`, which preserves whatever dimensions ggsql produces. `validate` defaults to `False` (same behavior as before, now a named parameter).
1718

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ license = "MIT"
77
repository = "https://github.com/posit-dev/ggsql-python"
88
homepage = "https://ggsql.org"
99
description = "Python bindings for ggsql"
10-
rust-version = "1.86"
10+
rust-version = "1.88"
1111

1212
[lib]
1313
name = "_ggsql"

‎python/ggsql/__init__.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import json
4+
import math
45
import warnings
56
from pathlib import Path
67
from typing import Any, Union
@@ -101,6 +102,12 @@ def _json_to_altair_chart(vegalite_json: str, **kwargs: Any) -> AltairChart:
101102
return altair.Chart.from_json(vegalite_json, **kwargs)
102103

103104

105+
def _validate_dpi(dpi: float) -> None:
106+
"""Reject non-positive or non-finite DPI before reaching the renderer."""
107+
if not math.isfinite(dpi) or dpi <= 0:
108+
raise ValueError(f"dpi must be a positive, finite number, got {dpi!r}")
109+
110+
104111
class SVGWriter:
105112
"""SVG output writer using ggsql's native renderer.
106113
@@ -120,6 +127,7 @@ class SVGWriter:
120127
"""
121128

122129
def __init__(self, width: int = 600, height: int = 400, dpi: float = 96.0) -> None:
130+
_validate_dpi(dpi)
123131
self._inner = _RustSVGWriter(width, height, dpi)
124132

125133
def render(self, spec: Spec) -> str:
@@ -152,6 +160,7 @@ class PDFWriter:
152160
"""
153161

154162
def __init__(self, width: int = 600, height: int = 400, dpi: float = 96.0) -> None:
163+
_validate_dpi(dpi)
155164
self._inner = _RustPDFWriter(width, height, dpi)
156165

157166
def render(self, spec: Spec) -> bytes:

‎tests/test_ggsql.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,3 +794,39 @@ def test_save_with_dimensions(self, spec, tmp_path):
794794
def test_save_unknown_extension_raises(self, spec, tmp_path):
795795
with pytest.raises(ValueError, match="Unsupported file extension"):
796796
ggsql.save(spec, tmp_path / "plot.png")
797+
798+
def test_save_invalid_dpi_raises(self, spec, tmp_path):
799+
with pytest.raises(ValueError, match="dpi must be a positive"):
800+
ggsql.save(spec, tmp_path / "plot.svg", dpi=0)
801+
802+
803+
class TestWriterDpiValidation:
804+
@pytest.mark.parametrize("bad_dpi", [0, -1, -96.0, float("nan"), float("inf")])
805+
def test_svg_writer_rejects_invalid_dpi(self, bad_dpi):
806+
with pytest.raises(ValueError, match="dpi must be a positive"):
807+
ggsql.SVGWriter(dpi=bad_dpi)
808+
809+
@pytest.mark.parametrize("bad_dpi", [0, -1, -96.0, float("nan"), float("inf")])
810+
def test_pdf_writer_rejects_invalid_dpi(self, bad_dpi):
811+
with pytest.raises(ValueError, match="dpi must be a positive"):
812+
ggsql.PDFWriter(dpi=bad_dpi)
813+
814+
815+
class TestBuiltinData:
816+
"""Integration coverage for builtin datasets (ggsql:... relations)."""
817+
818+
def test_execute_builtin_dataset(self):
819+
reader = ggsql.DuckDBReader("duckdb://memory")
820+
spec = reader.execute(
821+
"SELECT * FROM ggsql:penguins "
822+
"VISUALISE DRAW point MAPPING bill_len AS x, bill_dep AS y"
823+
)
824+
assert spec is not None
825+
826+
def test_builtin_dataset_renders_svg(self):
827+
reader = ggsql.DuckDBReader("duckdb://memory")
828+
spec = reader.execute(
829+
"VISUALISE FROM ggsql:airquality DRAW point MAPPING Temp AS x, Ozone AS y"
830+
)
831+
svg = ggsql.SVGWriter().render(spec)
832+
assert "<svg" in svg

0 commit comments

Comments
 (0)