Refactor API: Writer-centric rendering and Python module restructure#90
Refactor API: Writer-centric rendering and Python module restructure#90cpsievert wants to merge 7 commits intoposit-dev:rust-apifrom
Conversation
API changes: - Move render() from Prepared to Writer trait for better separation of concerns - Rename Reader::execute() to execute_sql() for clarity - Add Reader::unregister() method for table cleanup - Add GgsqlError::NoVisualise variant for queries without VISUALISE clause The Writer now has primary responsibility for rendering, with render() as the main entry point that delegates to write() internally. This makes the API more intuitive: writer.render(&prepared) instead of prepared.render(&writer). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New module structure: - ggsql.readers: DuckDB reader class with execute() and execute_sql() - ggsql.writers: VegaLite writer with render_json() and render_chart() - ggsql.types: Prepared, Validated, and exception classes Key improvements: - Proper exception hierarchy: GgsqlError base with ParseError, ValidationError, ReaderError, WriterError, NoVisualiseError - DuckDB.execute() auto-registers/unregisters DataFrames for clean API - Narwhals integration moved to Python layer for DataFrame conversion - Type stubs (_ggsql.pyi) for IDE support and type checking - Context manager support for DuckDB reader - Removed render_altair() and prepare() - replaced by cleaner two-stage API Breaking changes: - ggsql.DuckDBReader -> ggsql.readers.DuckDB - ggsql.VegaLiteWriter -> ggsql.writers.VegaLite - ggsql.prepare(query, reader) -> reader.execute(query, data_dict) - prepared.render(writer) -> writer.render_json(prepared) - Custom Python readers no longer supported (use DuckDB with registration) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Test coverage for: - New module paths (ggsql.readers.DuckDB, ggsql.writers.VegaLite) - execute() with data dict registration and auto-cleanup - execute_sql() for plain SQL queries - NoVisualiseError exception handling - Exception hierarchy (GgsqlError as base) - Context manager support - Narwhals DataFrame support (pandas, polars) - __repr__ methods for debugging - render_json() and render_chart() methods Removed tests for: - render_altair() convenience function (removed from API) - Custom Python reader support (removed from API) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- CLAUDE.md: Updated Python bindings section with new module structure, exception hierarchy, and API examples - src/doc/API.md: Updated Rust and Python API reference to reflect Writer::render() pattern and new Python module paths - ggsql-python/README.md: Complete rewrite with new API, examples for execute() with data dicts, exception handling, and narwhals support Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Writer trait import for render() method - Handle NoVisualise variant in error response mapping - Fix collapsible-str-replace: combine consecutive replace() calls - Allow vec_init_then_push for feature-flag dependent version handler Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Reader abstract base class defines the interface that custom readers must implement: - execute(query, data) - Execute ggsql query with optional data registration - execute_sql(sql) - Execute plain SQL and return DataFrame - register(name, df) - Register a DataFrame as a table - unregister(name) - Unregister a table DuckDB now inherits from Reader, providing a complete reference implementation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
@georgestagg something went way off the rails with the Before I spend time untangling the mess, does the overall proposal in the PR description seem sensible to you? Additionally, would it be off base to think we should allow a custom |
This should have already been possible in #89, right? 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(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 prepare()
reader = CSVReader("/path/to/data")
prepared = ggsql.prepare(
"SELECT * FROM data VISUALISE x, y DRAW point",
reader
)
writer = ggsql.VegaLiteWriter()
json_output = prepared.render(writer) |
| return nw_df.to_polars() | ||
|
|
||
|
|
||
| class DuckDB(Reader): |
There was a problem hiding this comment.
Note that there is a performance implication when crossing the Rust/Python barrier. So it might not be a good idea to thinly wrap the duckdb reader like this.
Yea, I missed that on first pass. I'll have to think on it a bit more. I'm gonna close this PR in favor of a set of more focused PRs. |
This PR builds on #89 to improve the Python package with a cleaner API structure, proper IDE support, and better error handling.
API Tweaks
Before
After
A summary of the API changes:
ggsql.DuckDBReader(conn)ggsql.readers.DuckDB(conn)ggsql.VegaLiteWriter()ggsql.writers.VegaLite()ggsql.prepare(query, reader)reader.execute(query, data_dict)prepared.render(writer)writer.render_json(spec)orwriter.render_chart(spec)reader.execute(sql)reader.execute_sql(sql)ValueErrorfor all errorsrender_altair(df, viz)writer.render_chart(spec)Which will require/inspire some Rust API tweaks
Writer::render(&Prepared)writer.render_json(spec)patternReader::execute()→execute_sql()executename for Python's higher-levelexecute(query, data_dict)methodReader::unregister(name)execute()- tables are unregistered after queryGgsqlError::NoVisualiseNoVisualiseErrorexception for queries missing VISUALISE clauseOther Python Improvements
1. Full IDE Support with Type Stubs
The new
_ggsql.pyiprovides complete type information for the native extension:2. Proper Exception Hierarchy
3. Clean Module Structure
4. Auto-Registration with Cleanup
Tables passed to
execute()are automatically:5. Context Manager Support
Commits
Writer::render(),execute_sql(),unregister(),NoVisualiseerrorcargo fmtNoVisualiseerror, clippy warnings🤖 Generated with Claude Code