Skip to main content

SQL extension for declarative data visualization

Project description

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 monorepo
git clone https://github.com/georgestagg/ggsql.git
cd ggsql/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:

import ggsql
import polars as pl

# Create a DataFrame
df = pl.DataFrame({
    "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(df, "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 polars as pl

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

# 2. Register your DataFrame as a table
df = pl.DataFrame({
    "date": ["2024-01-01", "2024-01-02", "2024-01-03"],
    "revenue": [100, 150, 120],
    "region": ["North", "South", "North"]
})
reader.register("sales", df)

# 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 polars DataFrame

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

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, df: polars.DataFrame, replace: bool = False) - Register a DataFrame as a queryable table
  • unregister(name: str) - Unregister a previously registered table
  • execute_sql(sql: str) -> polars.DataFrame - Execute SQL and return results

VegaLiteWriter()

Writer that generates Vega-Lite v6 JSON specifications.

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

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() -> polars.DataFrame | None - Main query result DataFrame
  • layer_data(index: int) -> polars.DataFrame | None - Layer-specific data (if filtered)
  • stat_data(index: int) -> polars.DataFrame | 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 polars as pl
import ggsql

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

Examples

Mapping Styles

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

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

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

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

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

Custom Readers

You can use any Python object with an execute_sql(sql: str) -> polars.DataFrame method as a reader. This enables integration with any data source.

import ggsql
import polars as pl

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) -> pl.DataFrame:
        # Simple implementation: ignore SQL and return fixed data
        # A real implementation would parse SQL to determine which file to load
        return pl.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, df: polars.DataFrame, replace: bool = False) -> None - Register a DataFrame as a queryable table (required)
  • 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) -> pl.DataFrame:
        # Your SQL execution logic here
        ...

    def register(self, name: str, df: pl.DataFrame, replace: bool = False) -> None:
        self.tables[name] = df

    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 polars as pl
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) -> pl.DataFrame:
        return self.con.con.execute(sql).pl()

    def register(self, name: str, df: pl.DataFrame, replace: bool = False) -> None:
        self.con.create_table(name, df.to_arrow(), overwrite=replace)

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

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

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

Development

Keeping in sync with the monorepo

The ggsql-python package is part of the ggsql monorepo and depends on the Rust ggsql crate via a path dependency. When the Rust crate is updated, you may need to rebuild:

cd ggsql-python

# Rebuild after Rust changes
maturin develop

# If tree-sitter grammar changed, clean and rebuild
cd .. && cargo clean -p tree-sitter-ggsql && cd ggsql-python
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
  • polars >= 1.0

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ggsql-0.2.6.tar.gz (881.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

ggsql-0.2.6-cp310-abi3-win_amd64.whl (31.0 MB view details)

Uploaded CPython 3.10+Windows x86-64

ggsql-0.2.6-cp310-abi3-manylinux_2_28_x86_64.whl (38.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ x86-64

ggsql-0.2.6-cp310-abi3-manylinux_2_28_aarch64.whl (37.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ ARM64

ggsql-0.2.6-cp310-abi3-macosx_11_0_arm64.whl (32.6 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

ggsql-0.2.6-cp310-abi3-macosx_10_12_x86_64.whl (35.1 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file ggsql-0.2.6.tar.gz.

File metadata

  • Download URL: ggsql-0.2.6.tar.gz
  • Upload date:
  • Size: 881.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ggsql-0.2.6.tar.gz
Algorithm Hash digest
SHA256 34bbca1e31a63ba6dc4b59b800dc23d06e9d21720f573e5eb1c9bddd31c4d3ae
MD5 c935c83b62fa6bb878e4aff85898ee48
BLAKE2b-256 7ac463436dd0ac005e1bd90c0c8858bc006e296219620ccd4ed7f9c0acbe021e

See more details on using hashes here.

Provenance

The following attestation bundles were made for ggsql-0.2.6.tar.gz:

Publisher: release-python.yml on posit-dev/ggsql

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ggsql-0.2.6-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: ggsql-0.2.6-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 31.0 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ggsql-0.2.6-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 5724a0c71d783016e388f360b6d292849cd0f0c15cec22e02496bbbf8174605f
MD5 b4f19c0817bec1b29e585ccc68ee0541
BLAKE2b-256 4ed0f0dbbe5ca467ef8242717b0ebc16fa2fad2bfa6c4a149d9071456b8a148f

See more details on using hashes here.

Provenance

The following attestation bundles were made for ggsql-0.2.6-cp310-abi3-win_amd64.whl:

Publisher: release-python.yml on posit-dev/ggsql

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ggsql-0.2.6-cp310-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for ggsql-0.2.6-cp310-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 605c03e7efe2cfa6d621e2a01a045bb84eda3f582583f2da2c1fcba00c4bad6c
MD5 4a0ccafa69645560fc1febe3c59bd811
BLAKE2b-256 a8993744140bedb0207243269e1ebea90095333a2a1c2321a7d3e9bac3fe0afe

See more details on using hashes here.

Provenance

The following attestation bundles were made for ggsql-0.2.6-cp310-abi3-manylinux_2_28_x86_64.whl:

Publisher: release-python.yml on posit-dev/ggsql

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ggsql-0.2.6-cp310-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for ggsql-0.2.6-cp310-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 99b20b3d610bebc7f6713897b991941b645ca7ecea33d6a834acab20bc989b73
MD5 9cdb9062231d5f31187aa0feaf3a9933
BLAKE2b-256 dcc7d92c9c557f35a59864c10010e6cf08220a26a246f38f3e7946b59223072a

See more details on using hashes here.

Provenance

The following attestation bundles were made for ggsql-0.2.6-cp310-abi3-manylinux_2_28_aarch64.whl:

Publisher: release-python.yml on posit-dev/ggsql

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ggsql-0.2.6-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

  • Download URL: ggsql-0.2.6-cp310-abi3-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 32.6 MB
  • Tags: CPython 3.10+, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ggsql-0.2.6-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 79c3b7be71849e2892dcf1efd007bd0d8587f0764b2f03baf70bdcf91dbd20b6
MD5 fc4c02c28f068164c76a702ec4784943
BLAKE2b-256 e1742393ac6df07b547e2f9f3b1ccca169006b71054a7a588b4a6d344eee3dcb

See more details on using hashes here.

Provenance

The following attestation bundles were made for ggsql-0.2.6-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release-python.yml on posit-dev/ggsql

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ggsql-0.2.6-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for ggsql-0.2.6-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 ca7bce79da2b62b5c68690c2b00f0116d7e847b56084378acfd3735f1bdbe770
MD5 a47462ecd6e324758e58fc9f73fb500b
BLAKE2b-256 0c8b41c0966aef4a46624eb719c461eac80b00cb1f14d4311274a0485a12c127

See more details on using hashes here.

Provenance

The following attestation bundles were made for ggsql-0.2.6-cp310-abi3-macosx_10_12_x86_64.whl:

Publisher: release-python.yml on posit-dev/ggsql

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page