Skip to content

Commit 0da2546

Browse files
cpsievertclaude
andauthored
fix(py): custom reader tests for new execution pipeline (#131)
* fix(python): update custom reader tests for new execution pipeline The "New Scale syntax and implementation" PR (#82, 7a5ed62) introduced a column renaming pipeline where the execution engine generates SQL like `SELECT "x" AS "__ggsql_aes_x__"` and passes it to readers. This requires custom readers to actually execute the SQL they receive, since the renamed columns are expected downstream during pruning. The custom reader tests were written with static readers that returned hardcoded DataFrames, ignoring the SQL parameter entirely. This worked with the old pipeline but fails with the new one because the returned DataFrames lack the `__ggsql_aes_*` prefixed columns. These failures were never caught on main because the Python CI workflow only triggers on changes to `ggsql-python/**`, and #82 only changed `src/` files. Changes: - Update 4 custom reader tests to use in-memory DuckDB connections that properly execute the SQL they receive - Add duckdb and pyarrow as test dependencies - Fix CI workflow to install the locally-built wheel instead of downloading a stale version from PyPI (pip install with glob instead of --find-links) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ci): fix wheel glob expansion in Python CI install step The glob pattern in `pip install target/wheels/ggsql-*.whl'[test]'` was not expanding because the shell treated `[test]` as a character class, preventing any file from matching. Use `ls` to expand the glob into a variable first. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fcf8c82 commit 0da2546

3 files changed

Lines changed: 37 additions & 19 deletions

File tree

.github/workflows/python.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ jobs:
4747

4848
- name: Install wheel and test dependencies
4949
shell: bash
50-
run: pip install --find-links target/wheels/ ggsql[test]
50+
run: |
51+
WHEEL=$(ls target/wheels/ggsql-*.whl)
52+
pip install "${WHEEL}[test]"
5153
5254
- name: Run tests
5355
shell: bash

ggsql-python/pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ dependencies = [
2121
]
2222

2323
[project.optional-dependencies]
24-
test = ["pytest>=7.0"]
24+
test = ["pytest>=7.0", "duckdb>=1.0", "pyarrow>=14.0"]
2525
dev = ["maturin>=1.4"]
2626

2727
[tool.maturin]
@@ -31,6 +31,8 @@ module-name = "ggsql._ggsql"
3131

3232
[dependency-groups]
3333
dev = [
34+
"duckdb>=1.0",
3435
"maturin>=1.11.5",
36+
"pyarrow>=14.0",
3537
"pytest>=9.0.2",
3638
]

ggsql-python/tests/test_ggsql.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import json
1212

13+
import duckdb
1314
import pytest
1415
import polars as pl
1516
import altair
@@ -399,8 +400,16 @@ def test_simple_custom_reader(self):
399400
"""Custom reader with execute_sql() method works."""
400401

401402
class SimpleReader:
403+
def __init__(self):
404+
self.conn = duckdb.connect()
405+
self.conn.execute(
406+
"CREATE TABLE data AS SELECT * FROM ("
407+
"VALUES (1, 10), (2, 20), (3, 30)"
408+
") AS t(x, y)"
409+
)
410+
402411
def execute_sql(self, sql: str) -> pl.DataFrame:
403-
return pl.DataFrame({"x": [1, 2, 3], "y": [10, 20, 30]})
412+
return self.conn.execute(sql).pl()
404413

405414
reader = SimpleReader()
406415
spec = ggsql.execute("SELECT * FROM data VISUALISE x, y DRAW point", reader)
@@ -411,19 +420,16 @@ def test_custom_reader_with_register(self):
411420

412421
class RegisterReader:
413422
def __init__(self):
414-
self.tables = {}
423+
self.conn = duckdb.connect()
415424

416425
def execute_sql(self, sql: str) -> pl.DataFrame:
417-
# Simple: just return the first registered table
418-
if self.tables:
419-
return next(iter(self.tables.values()))
420-
return pl.DataFrame({"x": [1], "y": [2]})
426+
return self.conn.execute(sql).pl()
421427

422428
def supports_register(self) -> bool:
423429
return True
424430

425431
def register(self, name: str, df: pl.DataFrame) -> None:
426-
self.tables[name] = df
432+
self.conn.register(name, df)
427433

428434
reader = RegisterReader()
429435
spec = ggsql.execute("SELECT 1 AS x, 2 AS y VISUALISE x, y DRAW point", reader)
@@ -460,17 +466,20 @@ def test_native_reader_fast_path(self):
460466
def test_custom_reader_can_render(self):
461467
"""Custom reader result can be rendered to Vega-Lite."""
462468

463-
class StaticReader:
464-
def execute_sql(self, sql: str) -> pl.DataFrame:
465-
return pl.DataFrame(
466-
{
467-
"x": [1, 2, 3, 4, 5],
468-
"y": [10, 40, 20, 50, 30],
469-
"category": ["A", "B", "A", "B", "A"],
470-
}
469+
class DuckDBBackedReader:
470+
def __init__(self):
471+
self.conn = duckdb.connect()
472+
self.conn.execute(
473+
"CREATE TABLE data AS SELECT * FROM ("
474+
"VALUES (1, 10, 'A'), (2, 40, 'B'), (3, 20, 'A'), "
475+
"(4, 50, 'B'), (5, 30, 'A')"
476+
") AS t(x, y, category)"
471477
)
472478

473-
reader = StaticReader()
479+
def execute_sql(self, sql: str) -> pl.DataFrame:
480+
return self.conn.execute(sql).pl()
481+
482+
reader = DuckDBBackedReader()
474483
spec = ggsql.execute(
475484
"SELECT * FROM data VISUALISE x, y, category AS color DRAW point",
476485
reader,
@@ -488,11 +497,16 @@ def test_custom_reader_execute_sql_called(self):
488497

489498
class RecordingReader:
490499
def __init__(self):
500+
self.conn = duckdb.connect()
501+
self.conn.execute(
502+
"CREATE TABLE data AS SELECT * FROM ("
503+
"VALUES (1, 2)) AS t(x, y)"
504+
)
491505
self.execute_calls = []
492506

493507
def execute_sql(self, sql: str) -> pl.DataFrame:
494508
self.execute_calls.append(sql)
495-
return pl.DataFrame({"x": [1], "y": [2]})
509+
return self.conn.execute(sql).pl()
496510

497511
reader = RecordingReader()
498512
ggsql.execute(

0 commit comments

Comments
 (0)