Skip to content

Commit cfe47e7

Browse files
authored
feat: upgrade to ggsql 0.5.2 with native SVG/PDF/HEP writers (#9)
* chore: bump ggsql crate to 0.5.2 and Python package to 0.5.2 Test expectation update: point's position aesthetics became optional in ggsql 0.4.0, so the missing-required-aesthetic test now uses a text layer missing its required label aesthetic. * feat: add SVGWriter for native SVG rendering * feat: add PDFWriter and HepWriter for native rendering * feat: add ggsql.save() for exporting specs to svg/pdf/hep/json * docs: document native writers and ggsql.save() * docs: document py/v* release tag workflow * 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. * ci: pin Windows jobs to windows-2022 windows-latest now resolves to a Windows Server 2025 image with the VS2026/v145 toolchain, whose STL removed stdext::checked_array_iterator. ggsql 0.5.2 pins duckdb ~1.10502, and duckdb 1.5.2's vendored fmt still uses that symbol under #ifdef _SECURE_SCL, so the bundled duckdb build hard-fails on windows-latest (duckdb/duckdb-rs#783, fixed upstream in libduckdb-sys 1.10506). windows-2022's VS2022/v143 STL still has the (deprecated) symbol, and upstream ggsql pins windows-2022 for its own release builds for the same reason. This is a stopgap: revert to windows-latest once ggsql releases with duckdb ~1.10506. * docs: add missing 0.3.3 changelog section and release checklist The 0.3.3 release went out with its changelog entries still under UNRELEASED (and the altair.Chart fallback entry misfiled under 0.3.2). Record them under a proper 0.3.3 section, and add a release checklist to CLAUDE.md so the changelog/version steps aren't skipped again. * fix: derive __version__ from package metadata; validate width/height ggsql.__version__ was hardcoded and drifted from pyproject (it read 0.3.0 in the published 0.3.1-0.3.3 wheels). Read it via importlib.metadata instead, with a test pinning it to pyproject.toml. Also reject non-positive width/height in SVGWriter/PDFWriter/save() with a clear ValueError, matching the existing dpi validation, instead of surfacing a raw PyO3 OverflowError (negative) or silently clamping (zero). * perf: release the GIL while native writers render SVG/PDF/HEP rendering is pure Rust work on immutable data, but it ran while holding the GIL, blocking other Python threads for the duration. Wrap the render calls in Python::detach so concurrent Python code (e.g. a web server or async event loop) keeps running during a slow render. Safety is compiler-checked: detach requires a Send closure, so this only builds because the writer and spec types are Sync. Single-threaded behavior is unchanged. * feat: expose the crate's writer options as **options on native writers The crate's native writers take more than width/height/dpi: units, background, and per-format settings (svg: text/embed-fonts/id-prefix; pdf: compress/links; hep: lossy/embed-fonts). Route constructor kwargs through the crate's WriterOptions/from_options so ggsql itself validates keys per writer — an unknown key or unusable value raises ValueError naming the option and listing the supported ones. Python booleans become "true"/"false"; underscore spellings match the crate's hyphenated keys via its key normalisation. This goes beyond the R bindings' surface (width/height/dpi only) by design: routing through from_options means options added upstream become available here without binding changes. HepWriter also gains optional width/height/dpi, recorded as consumer hints only when given (an optionless writer still records no hint). save() passes **options through to the native writer. * Revert "feat: expose the crate's writer options as **options on native writers" This reverts commit 97c5a28. * fix: address Copilot review — document fontconfig runtime dep, add wheel smoke test and writer warning tests - README: document that native SVG/PDF/HEP writers dlopen libfontconfig.so.1 on Linux and need installed fonts; renders silently lose text otherwise - CI: smoke-test the built wheel in a python:3.12-slim container provisioned per the README, asserting rendered SVG retains its text - tests: exercise the render-issue warning path with a stubbed inner writer returning non-empty issues (pytest.warns + output preserved), plus a no-warning case for an empty issue list * feat: point raster save() attempts at SVG conversion .png/.jpg/.jpeg/.tif/.tiff/.webp now raise a targeted ValueError explaining that ggsql's raster writers need a GPU adapter (unavailable in some environments) and suggesting .svg/.pdf plus a converter of the user's choice, instead of the generic unsupported-extension message they previously shared with typos. The GPU-backed raster writers stay disabled; this only improves the error UX. * feat: add __repr__ to writer classes SVGWriter/PDFWriter show their constructor dimensions; HepWriter and VegaLiteWriter show the bare call. Pure Python-layer change on the public wrappers. * ci: checkout before downloading wheel artifact (checkout cleans workspace)
1 parent 2fffdd6 commit cfe47e7

13 files changed

Lines changed: 1510 additions & 67 deletions

File tree

‎.github/workflows/python.yml‎

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,23 @@ jobs:
1111
strategy:
1212
fail-fast: false
1313
matrix:
14-
os: [ubuntu-latest, macos-latest, windows-latest]
14+
# NOTE: windows is pinned to windows-2022 (not windows-latest).
15+
# ggsql 0.5.2 pins duckdb ~1.10502, whose bundled build uses
16+
# stdext::checked_array_iterator (via vendored fmt). That symbol is
17+
# deprecated but present in windows-2022's VS2022/v143 STL, and fully
18+
# removed in the VS2026/v145 STL that windows-latest now provides, so
19+
# the duckdb build hard-fails there (duckdb/duckdb-rs#783, fixed in
20+
# libduckdb-sys 1.10506). Upstream ggsql pins windows-2022 for the
21+
# same reason. Revert to windows-latest once we depend on a ggsql
22+
# release with duckdb ~1.10506.
23+
os: [ubuntu-latest, macos-latest, windows-2022]
1524
python: ["3.10", "3.11", "3.12", "3.13"]
1625
include:
1726
- os: ubuntu-latest
1827
delay: 140
1928
- os: macos-latest
2029
delay: 210
21-
- os: windows-latest
30+
- os: windows-2022
2231
delay: 70
2332
runs-on: ${{ matrix.os }}
2433
steps:
@@ -68,3 +77,42 @@ jobs:
6877
- name: Run tests
6978
shell: bash
7079
run: pytest tests/ -v
80+
81+
# The abi3 wheel is identical across Python legs; keep just one for
82+
# the wheel smoke test below.
83+
- name: Upload Linux wheel
84+
if: runner.os == 'Linux' && matrix.python == '3.13'
85+
uses: actions/upload-artifact@v4
86+
with:
87+
name: ggsql-wheel
88+
path: target/wheels/ggsql-*.whl
89+
retention-days: 5
90+
91+
# Native SVG/PDF/HEP writers dlopen libfontconfig.so.1 at runtime and
92+
# need installed fonts — neither is expressible in wheel metadata, and
93+
# slim Linux images lack both. Render in a minimal container provisioned
94+
# per the README to catch packaging/runtime regressions.
95+
wheel-smoke-test:
96+
runs-on: ubuntu-latest
97+
needs: test
98+
steps:
99+
# NOTE: checkout must precede the download — it cleans the workspace.
100+
- uses: actions/checkout@v4
101+
102+
- uses: actions/download-artifact@v4
103+
with:
104+
name: ggsql-wheel
105+
path: wheelhouse
106+
107+
- name: Smoke test wheel in a minimal container
108+
run: |
109+
WHEEL=$(basename wheelhouse/*.whl)
110+
docker run --rm \
111+
-v "$GITHUB_WORKSPACE/wheelhouse:/wheels:ro" \
112+
-v "$GITHUB_WORKSPACE/scripts/smoke_test_wheel.py:/smoke_test.py:ro" \
113+
python:3.12-slim bash -euxo pipefail -c "
114+
apt-get update &&
115+
apt-get install -y --no-install-recommends libfontconfig1 fonts-dejavu-core &&
116+
pip install --no-cache-dir '/wheels/$WHEEL' &&
117+
python /smoke_test.py
118+
"

‎.github/workflows/release-python.yml‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,12 @@ jobs:
7070
name: wheels-macos-${{ matrix.target }}
7171
path: dist
7272

73+
# Pinned to windows-2022: bundled duckdb (via ggsql 0.5.2's duckdb
74+
# ~1.10502 pin) does not compile on windows-latest's VS2026/v145 STL.
75+
# See the matching note in python.yml. Revert once ggsql depends on
76+
# duckdb ~1.10506.
7377
windows:
74-
runs-on: windows-latest
78+
runs-on: windows-2022
7579
steps:
7680
- uses: actions/checkout@v4
7781

‎CHANGELOG.md‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
11
# Changelog
22

3-
## UNRELEASED
3+
## 0.5.2
4+
5+
### Added
6+
7+
- New native writers `SVGWriter`, `PDFWriter`, and `HepWriter`, rendered directly by ggsql (via hephaestus) with no JavaScript round-trip. Each exposes `render(spec)` returning `str` (SVG) or `bytes` (PDF/HEP); render warnings are surfaced via `warnings.warn()`.
8+
- New `ggsql.save(spec, file, ...)` helper that writes a spec to disk, picking the writer from the file extension (`.svg`, `.pdf`, `.hep`, or `.json` for Vega-Lite).
9+
- Enabled the crate's `builtin-data` feature, making builtin datasets such as `ggsql:world` available.
10+
11+
### Changed
12+
13+
- 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.
14+
- 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).
16+
17+
## 0.3.3
418

519
### Changed
620

21+
- Upgraded to ggsql Rust crate v0.3.3 (hotfix).
722
- `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).
23+
- When Altair can't deserialize a spec into the expected chart subclass (e.g., `LayerChart`), the converter now falls back to `altair.Chart` instead of raising. The chart still displays correctly; only Altair-level round-tripping (`.to_dict()`) is lost.
824

925
## 0.3.2
1026

1127
### Changed
1228

1329
- Upgraded to ggsql Rust crate v0.3.2.
1430
- `render_altair()` and `VegaLiteWriter.render_chart()` now default to `validate=False` when creating Altair chart objects. This avoids `ValidationError`s for valid specs (e.g., boxplots) that use Vega-Lite features not yet reflected in Altair's schema. Pass `validate=True` to re-enable.
15-
- When Altair can't deserialize a spec into the expected chart subclass (e.g., `LayerChart`), the converter now falls back to `altair.Chart` instead of raising. The chart still displays correctly; only Altair-level round-tripping (`.to_dict()`) is lost.
1631

1732
## 0.3.1
1833

‎CLAUDE.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ To pick up a new version of the upstream `ggsql` Rust crate, bump its version in
3838

3939
**Key design pattern**: Two-stage API — `reader.execute(query) -> Spec`, then `writer.render(spec) -> str` or `writer.render_chart(spec) -> AltairChart`. The `render_altair()` shortcut collapses both stages.
4040

41+
## Releasing
42+
43+
The Python Release workflow (`.github/workflows/release-python.yml`) triggers on tags matching `py/v*`. When creating a GitHub release, use `gh release create py/vX.Y.Z` — a plain `vX.Y.Z` tag won't trigger the build/publish pipeline.
44+
45+
Release checklist (all in the bump commit, before tagging):
46+
47+
1. Rename the `## UNRELEASED` section of `CHANGELOG.md` to the new version.
48+
2. Bump `version` in `pyproject.toml` and `Cargo.toml`, then run `uv sync` to update `uv.lock` and `Cargo.lock`. (`ggsql.__version__` follows automatically via `importlib.metadata`.)
49+
3. After the release workflow finishes, check that the GitHub release notes summarize the changelog section — edit with `gh release edit py/vX.Y.Z` if needed.
50+
4151
## `.cargo/config.toml`
4252

4353
Sets `GGSQL_SKIP_GENERATE=1` so tree-sitter uses its pre-generated parser rather than regenerating from `grammar.js`. Don't remove this.

0 commit comments

Comments
 (0)