Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0d6d157
feat(python): add ggsql-python package with PyO3 bindings
cpsievert Jan 20, 2026
994062e
Add dev dependency group
cpsievert Jan 21, 2026
9b3370d
fix: exclude ggsql-python from default cargo build
cpsievert Jan 22, 2026
a7b331c
style: format ggsql-python
cpsievert Jan 22, 2026
0f725c2
refactor: use pyproject.toml extras for CI dependencies
cpsievert Jan 22, 2026
757da46
fix: Python CI workflow improvements
cpsievert Jan 22, 2026
a374a66
fix: handle narwhals DataFrames and use correct global data key
cpsievert Jan 22, 2026
ba4633e
feat: use narwhals for DataFrame conversion
cpsievert Jan 22, 2026
f0cf267
fix: correct wheel path in Python CI workflow
cpsievert Jan 22, 2026
f19ecc4
chore: drop pyarrow dependency
cpsievert Jan 22, 2026
9a46b9a
fix: restore pyarrow dependency required by pyo3-polars
cpsievert Jan 22, 2026
cbc08e2
fix: commit tree-sitter generated files for Windows CI
cpsievert Jan 22, 2026
f951bfc
Add a basic .gitignore
cpsievert Jan 22, 2026
4b4e7aa
feat(python): return altair.Chart from render()
cpsievert Jan 22, 2026
23053a2
fix(python): add runtime validation for writer parameter
cpsievert Jan 22, 2026
abc2ff6
refactor(python): consolidate tests and focus on Python logic
cpsievert Jan 22, 2026
67cc299
refactor(python): rename render() to render_altair()
cpsievert Jan 22, 2026
0d0d5f3
refactor(python): remove pyarrow dependency, use IPC for data transfer
cpsievert Jan 22, 2026
e351fab
style(python): fix Rust formatting
cpsievert Jan 22, 2026
9c974d5
docs: add Python bindings section to CLAUDE.md
cpsievert Jan 22, 2026
8ffdc1a
fix: add tree-sitter-cli to CI workflows for Windows compatibility
cpsievert Jan 27, 2026
d0bb9fe
fix(ci): skip doc tests to avoid linker memory issues
cpsievert Jan 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ jobs:
sudo docker image prune --all --force
sudo docker builder prune -a

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install tree-sitter-cli
run: npm install -g tree-sitter-cli

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ jobs:
sudo docker image prune --all --force
sudo docker builder prune -a

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install tree-sitter-cli
run: npm install -g tree-sitter-cli

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

Expand All @@ -39,7 +47,7 @@ jobs:

- name: Run Rust tests
if: success()
run: cargo test
run: cargo test --lib --bins

- name: Run Rust formatting check
if: success()
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ jobs:
sudo docker image prune --all --force
sudo docker builder prune -a

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install tree-sitter-cli
run: npm install -g tree-sitter-cli

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

Expand Down
72 changes: 72 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Python

on:
push:
paths: ['ggsql-python/**', '.github/workflows/python.yml']
pull_request:
paths: ['ggsql-python/**', '.github/workflows/python.yml']

jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python: ['3.10', '3.11', '3.12', '3.13']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}

- uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install tree-sitter-cli
run: npm install -g tree-sitter-cli

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: ggsql-python
shared-key: ${{ matrix.os }}-python

- name: Build wheel
uses: PyO3/maturin-action@v1
with:
working-directory: ggsql-python
command: build
args: --release
sccache: true

- name: Install wheel and test dependencies
shell: bash
run: pip install --find-links target/wheels/ ggsql[test]

- name: Run tests
shell: bash
run: pytest ggsql-python/tests/ -v

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install tree-sitter-cli
run: npm install -g tree-sitter-cli

- name: Check Rust formatting
run: cargo fmt --package ggsql-python -- --check

- name: Clippy
run: cargo clippy --package ggsql-python -- -D warnings
63 changes: 60 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,63 @@ When running in Positron IDE, the extension provides enhanced functionality:

---

### 8. Python Bindings (`ggsql-python/`)

**Responsibility**: Python bindings for ggsql, enabling Python users to render Altair charts using ggsql's VISUALISE syntax.

**Features**:

- PyO3-based Rust bindings compiled to a native Python extension
- Works with any narwhals-compatible DataFrame (polars, pandas, etc.)
- LazyFrames are collected automatically
- Returns native `altair.Chart` objects for easy display and customization
- Query splitting to separate SQL from VISUALISE portions

**Installation**:

```bash
# From source (requires Rust toolchain and maturin)
cd ggsql-python
pip install maturin
maturin develop
```

**API**:

```python
import ggsql
import polars as pl

# Split a ggSQL query into SQL and VISUALISE portions
sql, viz = ggsql.split_query("""
SELECT date, revenue FROM sales
VISUALISE date AS x, revenue AS y
DRAW line
""")

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

# Display or save
chart.display() # In Jupyter
chart.save("chart.html")
```

**Functions**:

- `split_query(query: str) -> tuple[str, str]` - Split ggSQL query into SQL and VISUALISE portions
- `render_altair(df, viz, **kwargs) -> altair.Chart` - Render DataFrame with VISUALISE spec to Altair chart

**Dependencies**:

- Python >= 3.10
- altair >= 5.0
- narwhals >= 2.15
- polars >= 1.0

---

## Feature Flags and Build Configuration

ggsql uses Cargo feature flags to enable optional functionality and reduce binary size.
Expand Down Expand Up @@ -822,9 +879,9 @@ ggsql uses Cargo feature flags to enable optional functionality and reduce binar
- Includes: `axum`, `tokio`, `tower-http`, `tracing`, `duckdb`, `vegalite`
- Required for building `ggsql-rest` server

**Future features**:
**Python bindings**:

- `python` - Python bindings via PyO3 (planned)
- `ggsql-python` - Python bindings via PyO3 (separate crate, not a feature flag)

### Building with Custom Features

Expand All @@ -850,7 +907,7 @@ cargo build --all-features
- `postgres` → `postgres` crate (future)
- `sqlite` → `rusqlite` crate (future)
- `rest-api` → `axum`, `tokio`, `tower-http`, `tracing`, `tracing-subscriber`
- `python` → `pyo3` crate (future)
- `ggsql-python` → `pyo3`, `narwhals`, `altair` (separate workspace crate)

---

Expand Down
10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
[workspace]
members = [
"tree-sitter-ggsql",
"src",
"ggsql-jupyter",
"ggsql-python"
]
# ggsql-python is excluded from default builds because it's a PyO3 extension
# that requires Python dev headers. Build it separately with maturin.
default-members = [
"tree-sitter-ggsql",
"src",
"ggsql-jupyter"
Expand All @@ -21,7 +29,7 @@ tree-sitter = "0.25"
csscolorparser = "0.8.1"

# Data processing
polars = { version = "0.52", features = ["lazy", "sql"] }
polars = { version = "0.52", features = ["lazy", "sql", "ipc"] }

# Readers
duckdb = { version = "1.1", features = ["bundled"] }
Expand Down
3 changes: 3 additions & 0 deletions ggsql-python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.vscode/

uv.lock
18 changes: 18 additions & 0 deletions ggsql-python/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "ggsql-python"
version = "0.1.0"
edition = "2021"
license = "MIT"
description = "Python bindings for ggsql"

[lib]
name = "_ggsql"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.26", features = ["extension-module"] }
polars = { workspace = true, features = ["ipc"] }
ggsql = { path = "../src", default-features = false, features = ["vegalite"] }

[features]
default = []
Loading