Skip to main content

ggsql

Python bindings for ggsql, a SQL extension for declarative data visualization.

This package provides Python bindings to the Rust ggsql crate, enabling Python users to create visualizations using ggsql's VISUALISE syntax with native Altair chart output.

Installation

From PyPI (when published)

pip install ggsql

From source

Building from source requires:

# Clone the repository
git clone https://github.com/posit-dev/ggsql-python.git
cd ggsql-python

# Create a virtual environment
python -m venv .venv
source .venv/bin/activate  # or `.venv\Scripts\activate` on Windows

# Install build dependencies
pip install maturin

# Build and install in development mode
maturin develop

# Or build a wheel
maturin build --release
pip install target/wheels/ggsql-*.whl

Quick Start

Simple Usage with render_altair

For quick visualizations, use the render_altair convenience function. It accepts any narwhals-compatible DataFrame (polars, pandas, pyarrow, etc.):

import ggsql
import pyarrow as pa

# Create a table
table = pa.table({
    "x": [1, 2, 3, 4, 5],
    "y": [10, 20, 15, 30, 25],
    "category": ["A", "B", "A", "B", "A"]
})

# Render to Altair chart
chart = ggsql.render_altair(table, "VISUALISE x, y DRAW point")

# Display or save
chart.display()  # In Jupyter
chart.save("chart.html")  # Save to file

Two-Stage API

For more control, use the two-stage API with explicit reader and writer:

import ggsql
import pyarrow as pa

# 1. Create a DuckDB reader
reader = ggsql.DuckDBReader("duckdb://memory")

# 2. Register your data as a table (accepts pyarrow, polars, pandas, etc.)
table = pa.table({
    "date": ["2024-01-01", "2024-01-02", "2024-01-03"],
    "revenue": [100, 150, 120],
    "region": ["North", "South", "North"]
})
reader.register("sales", table)

# 3. Execute the ggsql query
spec = reader.execute(
    """
    SELECT * FROM sales
    VISUALISE date AS x, revenue AS y, region AS color
    DRAW line
    LABEL title => 'Sales by Region'
    """
)

# 4. Inspect metadata
print(f"Rows: {spec.metadata()['rows']}")
print(f"Columns: {spec.metadata()['columns']}")
print(f"Layers: {spec.layer_count()}")

# 5. Inspect SQL/VISUALISE portions and data
print(f"SQL: {spec.sql()}")
print(f"Visual: {spec.visual()}")
print(spec.layer_data(0))  # Returns pyarrow.Table

# 6. Render to Vega-Lite JSON
writer = ggsql.VegaLiteWriter()
vegalite_json = writer.render(spec)
print(vegalite_json)

Native Writers (SVG, PDF, HEP)

In addition to Vega-Lite/Altair output, ggsql can render plots natively to SVG, PDF, and .hep plot documents — no JavaScript required:

import ggsql

reader = ggsql.DuckDBReader("duckdb://memory")
spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")

# Render with a specific writer
svg = ggsql.SVGWriter(width=600, height=400, dpi=96).render(spec)  # str
pdf = ggsql.PDFWriter(width=600, height=400, dpi=96).render(spec)  # bytes
doc = ggsql.HepWriter().render(spec)                               # bytes

# Or save directly — the format is picked from the file extension
ggsql.save(spec, "plot.svg")
ggsql.save(spec, "plot.pdf")
ggsql.save(spec, "plot.hep")
ggsql.save(spec, "plot.json")  # Vega-Lite JSON

Linux runtime requirement: the native writers load libfontconfig.so.1 at runtime (via dlopen) and need at least one installed font for text layout. Prebuilt wheels do not bundle these. On slim/container images (e.g., python:*-slim), install them explicitly:

apt-get install -y libfontconfig1 fonts-dejavu-core

Without fontconfig, renders silently drop all text.

API Reference

Classes

DuckDBReader(connection: str)

Database reader that executes SQL and manages DataFrames.

reader = ggsql.DuckDBReader("duckdb://memory")  # In-memory database
reader = ggsql.DuckDBReader("duckdb:///path/to/file.db")  # File database

Methods:

  • register(name: str, table, replace: bool = False) - Register data as a queryable table (accepts pyarrow.Table, polars.DataFrame, pandas.DataFrame, etc.)
  • unregister(name: str) - Unregister a previously registered table
  • execute_sql(sql: str) -> pyarrow.Table - Execute SQL and return results

VegaLiteWriter()

Writer that generates Vega-Lite v6 JSON specifications.

writer = ggsql.VegaLiteWriter()
json_output = writer.render(spec)

SVGWriter(width: int = 600, height: int = 400, dpi: float = 96.0)

Renders specs to SVG markup using ggsql's native renderer.

  • render(spec: Spec) -> str — Render to an SVG string.

PDFWriter(width: int = 600, height: int = 400, dpi: float = 96.0)

Renders specs to PDF documents using ggsql's native renderer.

  • render(spec: Spec) -> bytes — Render to a PDF document.

HepWriter()

Renders specs to .hep plot documents (self-contained plot descriptions a compatible consumer can re-layout at any size) using ggsql's native renderer.

  • render(spec: Spec) -> bytes — Render to a .hep document.

Validated

Result of validate() containing query analysis without SQL execution.

Methods:

  • valid() -> bool - Whether the query is syntactically and semantically valid
  • has_visual() -> bool - Whether the query contains a VISUALISE clause
  • sql() -> str - The SQL portion (before VISUALISE)
  • visual() -> str - The VISUALISE portion
  • errors() -> list[dict] - Validation errors with messages and locations
  • warnings() -> list[dict] - Validation warnings

Spec

Result of reader.execute(), containing resolved visualization ready for rendering.

Methods:

  • metadata() -> dict - Get {"rows": int, "columns": list[str], "layer_count": int}
  • sql() -> str - The executed SQL query
  • visual() -> str - The VISUALISE clause
  • layer_count() -> int - Number of DRAW layers
  • data() -> pyarrow.Table | None - Main query result data
  • layer_data(index: int) -> pyarrow.Table | None - Layer-specific data (if filtered)
  • stat_data(index: int) -> pyarrow.Table | None - Statistical transform data
  • layer_sql(index: int) -> str | None - Layer filter SQL
  • stat_sql(index: int) -> str | None - Stat transform SQL
  • warnings() -> list[dict] - Validation warnings from execution

Functions

validate(query: str) -> Validated

Validate query syntax and semantics without executing SQL.

validated = ggsql.validate("SELECT x, y FROM data VISUALISE x, y DRAW point")
if validated.valid():
    print("Query is valid!")
else:
    for error in validated.errors():
        print(f"Error: {error['message']}")

reader.execute(query: str) -> Spec

Execute a ggsql query and return the visualization specification.

reader = ggsql.DuckDBReader("duckdb://memory")
spec = reader.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point")

render_altair(df, viz: str, **kwargs) -> altair.Chart

Convenience function to render a DataFrame with a VISUALISE spec to an Altair chart.

Parameters:

  • df - Any narwhals-compatible DataFrame (polars, pandas, etc.). LazyFrames are collected automatically.
  • viz - The VISUALISE specification string
  • **kwargs - Additional arguments passed to altair.Chart.from_json() (e.g., validate=False)

Returns: An Altair chart object (Chart, LayerChart, FacetChart, etc.)

import pyarrow as pa
import ggsql

table = pa.table({"x": [1, 2, 3], "y": [10, 20, 30]})
chart = ggsql.render_altair(table, "VISUALISE x, y DRAW point")

save(spec, file, *, width=600, height=400, dpi=96) -> Path

Save a spec to a file. The extension picks the format: .svg, .pdf, .hep (native writers) or .json (Vega-Lite).

Examples

Mapping Styles

import pyarrow as pa

table = pa.table({"x": [1, 2, 3], "y": [10, 20, 30], "category": ["A", "B", "A"]})

# Explicit mapping
ggsql.render_altair(table, "VISUALISE x AS x, y AS y DRAW point")

# Implicit mapping (column name = aesthetic name)
ggsql.render_altair(table, "VISUALISE x, y DRAW point")

# Wildcard mapping (map all matching columns)
ggsql.render_altair(table, "VISUALISE * DRAW point")

# With color encoding
ggsql.render_altair(table, "VISUALISE x, y, category AS color DRAW point")

Custom Readers

You can use any Python object with an execute_sql(sql: str) method as a reader. The method should return a pyarrow.Table (or any type that pyarrow.table() can convert, such as a polars.DataFrame).

import ggsql
import pyarrow as pa
import pyarrow.csv

class CSVReader:
    """Custom reader that loads data from CSV files."""

    def __init__(self, data_dir: str):
        self.data_dir = data_dir

    def execute_sql(self, sql: str) -> pa.Table:
        # Simple implementation: ignore SQL and return fixed data
        # A real implementation would parse SQL to determine which file to load
        return pyarrow.csv.read_csv(f"{self.data_dir}/data.csv")

# Use custom reader with ggsql.execute()
reader = CSVReader("/path/to/data")
spec = ggsql.execute(
    "SELECT * FROM data VISUALISE x, y DRAW point",
    reader
)
writer = ggsql.VegaLiteWriter()
json_output = writer.render(spec)

Additional methods for custom readers:

  • register(name: str, table, replace: bool = False) -> None - Register data as a queryable table (required). Receives a pyarrow.Table.
  • unregister(name: str) -> None - Unregister a previously registered table (optional)
class AdvancedReader:
    """Custom reader with registration support."""

    def __init__(self):
        self.tables = {}

    def execute_sql(self, sql: str) -> pa.Table:
        # Your SQL execution logic here
        ...

    def register(self, name: str, table: pa.Table, replace: bool = False) -> None:
        self.tables[name] = table

    def unregister(self, name: str) -> None:
        del self.tables[name]

Native readers like DuckDBReader use an optimized fast path, while custom Python readers are automatically bridged via IPC serialization.

Ibis Reader Example

Ibis provides a unified Python API for SQL operations across multiple backends. Here's how to create an ibis-based custom reader:

import ggsql
import pyarrow as pa
import ibis

class IbisReader:
    """Custom reader using ibis as the SQL backend."""

    def __init__(self, backend="duckdb"):
        if backend == "duckdb":
            self.con = ibis.duckdb.connect()
        elif backend == "sqlite":
            self.con = ibis.sqlite.connect()
        # Add other backends as needed

    def execute_sql(self, sql: str) -> pa.Table:
        return self.con.con.execute(sql).arrow()

    def register(self, name: str, table: pa.Table, replace: bool = False) -> None:
        self.con.create_table(name, table, overwrite=replace)

    def unregister(self, name: str) -> None:
        self.con.drop_table(name)

# Usage
reader = IbisReader()
table = pa.table({
    "date": ["2024-01-01", "2024-01-02", "2024-01-03"],
    "revenue": [100, 150, 120],
})
reader.register("sales", table)

spec = ggsql.execute(
    "SELECT * FROM sales VISUALISE date AS x, revenue AS y DRAW line",
    reader
)
writer = ggsql.VegaLiteWriter()
print(writer.render(spec))

Development

Building

# Rebuild after Rust changes
maturin develop

To pick up a new version of the upstream ggsql Rust crate, bump its version in Cargo.toml and re-run maturin develop.

Running tests

# Install test dependencies
pip install pytest

# Run all tests
pytest tests/ -v

Requirements

  • Python >= 3.10
  • altair >= 5.0
  • narwhals >= 2.15
  • pyarrow >= 14.0

License

MIT

Metadata

Release files for ggsql 0.5.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ggsql 0.5.2
File Size Uploaded
ggsql-0.5.2.tar.gz 210.5 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for ggsql 0.5.2
File
ggsql-0.5.2-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
ggsql-0.5.2-cp310-abi3-manylinux_2_28_x86_64.whl CPython 3.10 abi3 Linux glibc 2.28+ x86-64 Details
ggsql-0.5.2-cp310-abi3-manylinux_2_28_aarch64.whl CPython 3.10 abi3 Linux glibc 2.28+ ARM64 Details
ggsql-0.5.2-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
ggsql-0.5.2-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 100.3 MB

Release files / ggsql-0.5.2.tar.gz

Download URL ggsql-0.5.2.tar.gz
Size 210.5 kB
Tags Source
SHA-256 checksum
How to use checksums
daa0833a1a4a99c393161e53d56fa7ed4c3d5f2e76de641cf7f2b1560d9452e6
BLAKE2b-256 checksum
How to use checksums
bd4413fffd15fb3760e5bcde090f0cc5301899e78f10eb1cbe1f3add408ed740
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / ggsql-0.5.2-cp310-abi3-win_amd64.whl

Download URL ggsql-0.5.2-cp310-abi3-win_amd64.whl
Size 16.8 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
02f2fcd93c881658133e4bf0ed32956046ae9979aa9969f9a9bc8f5563be3086
BLAKE2b-256 checksum
How to use checksums
beddb0d9c167b4e36758b6498e50dcef6cf76e04969c37cb75421159e79710d9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / ggsql-0.5.2-cp310-abi3-manylinux_2_28_x86_64.whl

Download URL ggsql-0.5.2-cp310-abi3-manylinux_2_28_x86_64.whl
Size 22.8 MB
Tags CPython 3.10 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
1402b3f7a8a01ee5823558acc016f1d5d39bb8c2c6613d063fb6c1afb100425b
BLAKE2b-256 checksum
How to use checksums
aebc2f9fce5e0b04a4966f95429bc654049009a39faf75df8ef90c794481b6e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / ggsql-0.5.2-cp310-abi3-manylinux_2_28_aarch64.whl

Download URL ggsql-0.5.2-cp310-abi3-manylinux_2_28_aarch64.whl
Size 21.4 MB
Tags CPython 3.10 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
32360a440cee0ee6459beb66c9cc9b6b78e85f3960a12d9b0c4c05daa4d942c8
BLAKE2b-256 checksum
How to use checksums
cd0c113108c7a05ae53a5661e9703490e942b2c26e5b724c8b7af5f28ab15166
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / ggsql-0.5.2-cp310-abi3-macosx_11_0_arm64.whl

Download URL ggsql-0.5.2-cp310-abi3-macosx_11_0_arm64.whl
Size 19.0 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
e6b11d0d731a97a2974f835900a8a3ef71749d0a8a9a8fee35b24aa52dad8602
BLAKE2b-256 checksum
How to use checksums
7db0eacb9c7b477a7951f64350f49c8380e1638d8d1e8481d76b59b5221f26c9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / ggsql-0.5.2-cp310-abi3-macosx_10_12_x86_64.whl

Download URL ggsql-0.5.2-cp310-abi3-macosx_10_12_x86_64.whl
Size 20.1 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
3e13ffdb96da5b69492f636093ac4408dcbc09f2784c321b136e7a33c6908193
BLAKE2b-256 checksum
How to use checksums
ca080d44b0f81d210454da0070c87db372edc2da41a4f141c9380ecca89d9ed7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.2 This release

6 release files

0.3.3

6 release files

0.3.2

6 release files

0.3.1

6 release files

0.2.7

6 release files

0.2.6

6 release files

0.2.5

6 release files

0.2.4

6 release files

0.2.3

6 release files

0.2.2

6 release files

0.2.0

6 release files

0.1.9

6 release files

0.1.8

6 release files

0.1.5

6 release files

0.1.4

6 release files

0.1.3

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page