@@ -864,17 +864,18 @@ When running in Positron IDE, the extension provides enhanced functionality:
864864
865865### 8. Python Bindings (` ggsql-python/ ` )
866866
867- ** Responsibility** : Python bindings for ggsql, enabling Python users to render Altair charts using ggsql's VISUALISE syntax.
867+ ** Responsibility** : Python bindings for ggsql, enabling Python users to create visualizations using ggsql's VISUALISE syntax.
868868
869869** Features** :
870870
871871- PyO3-based Rust bindings compiled to a native Python extension
872+ - Two-stage API mirroring the Rust API: ` prepare() ` → ` render() `
873+ - DuckDB reader with DataFrame registration
874+ - Custom Python reader support: any object with ` execute(sql) -> DataFrame ` method
872875- Works with any narwhals-compatible DataFrame (polars, pandas, etc.)
873876- LazyFrames are collected automatically
874- - Returns native ` altair.Chart ` objects for easy display and customization
875- - Two-stage API: ` prepare() ` → ` render() `
876- - DuckDB reader with DataFrame registration
877- - Query introspection (SQL, layer queries, stat queries)
877+ - Returns native ` altair.Chart ` objects via ` render_altair() ` convenience function
878+ - Query validation and introspection (SQL, layer queries, stat queries)
878879
879880** Installation** :
880881
@@ -902,30 +903,105 @@ prepared = ggsql.prepare(
902903 reader
903904)
904905
905- # Inspect
906+ # Inspect metadata
906907print (f " Rows: { prepared.metadata()[' rows' ]} " )
908+ print (f " Columns: { prepared.metadata()[' columns' ]} " )
907909print (f " SQL: { prepared.sql()} " )
908910
909911# Render to Vega-Lite JSON
910912writer = ggsql.VegaLiteWriter()
911913json_output = prepared.render(writer)
912914```
913915
916+ ** Convenience Function** (` render_altair ` ):
917+
918+ For quick visualizations without explicit reader setup:
919+
920+ ``` python
921+ import ggsql
922+ import polars as pl
923+
924+ df = pl.DataFrame({" x" : [1 , 2 , 3 ], " y" : [10 , 20 , 30 ]})
925+
926+ # Render DataFrame to Altair chart in one call
927+ chart = ggsql.render_altair(df, " VISUALISE x, y DRAW point" )
928+ chart.display() # In Jupyter
929+ ```
930+
931+ ** Query Validation** :
932+
933+ ``` python
934+ # Validate syntax without execution
935+ validated = ggsql.validate(
936+ " SELECT x, y FROM data VISUALISE x, y DRAW point"
937+ )
938+ print (f " Valid: { validated.valid()} " )
939+ print (f " Has VISUALISE: { validated.has_visual()} " )
940+ print (f " SQL portion: { validated.sql()} " )
941+ print (f " Errors: { validated.errors()} " )
942+ ```
943+
914944** Classes** :
915945
916- | Class | Description |
917- | -------------------------- | ---------------------------- |
918- | ` DuckDBReader(connection) ` | Database reader |
919- | ` VegaLiteWriter() ` | Vega-Lite JSON output writer |
920- | ` Validated ` | Result of ` validate() ` |
946+ | Class | Description |
947+ | -------------------------- | -------------------------------------------- |
948+ | ` DuckDBReader(connection) ` | Database reader with DataFrame registration |
949+ | ` VegaLiteWriter() ` | Vega-Lite JSON output writer |
950+ | ` Validated ` | Result of ` validate() ` with query inspection |
951+ | ` Prepared ` | Result of ` prepare() ` , ready for rendering |
921952
922953** Functions** :
923954
924- | Function | Description |
925- | ------------------------ | ------------------------------------------------ |
926- | ` validate(query) ` | Syntax/semantic validation with query inspection |
927- | ` prepare(query, reader) ` | Full preparation pipeline |
928- | ` render_altair(df, viz) ` | Render DataFrame to Altair chart |
955+ | Function | Description |
956+ | ------------------------ | ------------------------------------------------- |
957+ | ` validate(query) ` | Syntax/semantic validation with query inspection |
958+ | ` prepare(query, reader) ` | Full preparation (reader can be native or custom) |
959+ | ` render_altair(df, viz) ` | Convenience: render DataFrame to Altair chart |
960+
961+ ** Prepared Object Methods** :
962+
963+ | Method | Description |
964+ | ---------------- | -------------------------------------------- |
965+ | ` render(writer) ` | Generate Vega-Lite JSON |
966+ | ` metadata() ` | Get rows, columns, layer_count |
967+ | ` sql() ` | Get the SQL portion |
968+ | ` visual() ` | Get the VISUALISE portion |
969+ | ` layer_count() ` | Number of DRAW layers |
970+ | ` data() ` | Get the main DataFrame |
971+ | ` layer_data(i) ` | Get layer-specific DataFrame (if filtered) |
972+ | ` stat_data(i) ` | Get stat transform DataFrame (if applicable) |
973+ | ` layer_sql(i) ` | Get layer filter SQL (if applicable) |
974+ | ` stat_sql(i) ` | Get stat transform SQL (if applicable) |
975+ | ` warnings() ` | Get validation warnings |
976+
977+ ** Custom Python Readers** :
978+
979+ Any Python object with an ` execute(sql: str) -> polars.DataFrame ` method can be used as a reader:
980+
981+ ``` python
982+ import ggsql
983+ import polars as pl
984+
985+ class MyReader :
986+ """ Custom reader that returns static data."""
987+
988+ def execute (self , sql : str ) -> pl.DataFrame:
989+ return pl.DataFrame({" x" : [1 , 2 , 3 ], " y" : [10 , 20 , 30 ]})
990+
991+ # Use custom reader with prepare()
992+ reader = MyReader()
993+ prepared = ggsql.prepare(
994+ " SELECT * FROM data VISUALISE x, y DRAW point" ,
995+ reader
996+ )
997+ ```
998+
999+ Optional methods for custom readers:
1000+
1001+ - ` supports_register() -> bool ` - Return ` True ` if registration is supported
1002+ - ` register(name: str, df: polars.DataFrame) -> None ` - Register a DataFrame as a table
1003+
1004+ Native readers (e.g., ` DuckDBReader ` ) use an optimized fast path, while custom Python readers are automatically bridged via IPC serialization.
9291005
9301006** Dependencies** :
9311007
0 commit comments