@@ -41,16 +41,21 @@ These are just a few examples that demonstrate SQLSpec's flexibility. Each of th
4141``` python
4242from sqlspec import SQLSpec
4343from sqlspec.adapters.sqlite import SqliteConfig
44- from pydantic import BaseModel
44+
4545# Create SQLSpec instance and configure database
46- sql = SQLSpec()
47- config = sql.add_config(SqliteConfig(database = " :memory:" ))
46+ db_manager = SQLSpec()
47+ config = SqliteConfig(pool_config = {" database" : " :memory:" }) # Thread local pooling
48+ db_manager.add_config(config)
4849
4950# Execute queries with automatic result mapping
50- with sql .provide_session(config) as session:
51+ with db_manager .provide_session(config) as session:
5152 # Simple query
5253 result = session.execute(" SELECT 'Hello, SQLSpec!' as message" )
5354 print (result.get_first()) # {'message': 'Hello, SQLSpec!'}
55+
56+ # Type-safe single row query
57+ row = session.select_one(" SELECT 'Hello, SQLSpec!' as message" )
58+ print (row) # {'message': 'Hello, SQLSpec!'}
5459```
5560
5661### SQL Builder Example (Experimental)
@@ -61,30 +66,94 @@ with sql.provide_session(config) as session:
6166from sqlspec import sql
6267
6368# Build a simple query
64- query = sql.select(" id" , " name" , " email" ).from_(" users" ).where(" active = ?" , True )
65- print (query.build().sql) # SELECT id, name, email FROM users WHERE active = ?
69+ query = sql.select(" id" , " name" , " email" ).from_(" users" ).where(" active = ?" )
70+ statement = query.to_statement()
71+ print (statement.sql) # SELECT id, name, email FROM users WHERE active = ?
6672
6773# More complex example with joins
6874query = (
6975 sql.select(" u.name" , " COUNT(o.id) as order_count" )
7076 .from_(" users u" )
7177 .left_join(" orders o" , " u.id = o.user_id" )
72- .where(" u.created_at > ?" , " 2024-01-01 " )
78+ .where(" u.created_at > ?" )
7379 .group_by(" u.name" )
74- .having(" COUNT(o.id) > ?" , 5 )
80+ .having(" COUNT(o.id) > ?" )
7581 .order_by(" order_count" , desc = True )
7682)
7783
78- # Execute the built query
79- with sql.provide_session(config) as session:
80- results = session.execute(query.build())
84+ # Execute the built query with parameters
85+ with db_manager.provide_session(config) as session:
86+ results = session.execute(query, " 2024-01-01" , 5 )
87+ ```
88+
89+ ### Type-Safe Result Mapping
90+
91+ SQLSpec supports automatic mapping to typed models using popular libraries:
92+
93+ ``` python
94+ from sqlspec import SQLSpec
95+ from sqlspec.adapters.sqlite import SqliteConfig
96+ from pydantic import BaseModel
97+
98+ class User (BaseModel ):
99+ id : int
100+ name: str
101+ email: str
102+
103+ db_manager = SQLSpec()
104+ config = SqliteConfig(pool_config = {" database" : " :memory:" })
105+ db_manager.add_config(config)
106+
107+ with db_manager.provide_session(config) as session:
108+ # Create and populate test data
109+ session.execute_script("""
110+ CREATE TABLE users (id INTEGER, name TEXT, email TEXT);
111+ INSERT INTO users VALUES (1, 'Alice', '[email protected] '); 112+ """ )
113+ # Map single result to typed model
114+ user = session.select_one(" SELECT * FROM users WHERE id = ?" , 1 , schema_type = User)
115+ print (f " User: { user.name} ( { user.email} ) " )
116+
117+ # Map multiple results
118+ users = session.select(" SELECT * FROM users" , schema_type = User)
119+ for user in users:
120+ print (f " User: { user.name} " )
121+ ```
122+
123+ ### Session Methods Overview
124+
125+ SQLSpec provides several convenient methods for executing queries:
126+
127+ ``` python
128+ with db_manager.provide_session(config) as session:
129+ # Execute any SQL and get full result set
130+ result = session.execute(" SELECT * FROM users" )
131+
132+ # Get single row (raises error if not found)
133+ user = session.select_one(" SELECT * FROM users WHERE id = ?" , 1 )
134+
135+ # Get single row or None (no error if not found)
136+ maybe_user = session.select_one_or_none(" SELECT * FROM users WHERE id = ?" , 999 )
137+
138+ # Execute with many parameter sets (bulk operations)
139+ session.execute_many(
140+ " INSERT INTO users (name, email) VALUES (?, ?)" ,
141+ 142+ )
143+
144+ # Execute multiple statements as a script
145+ session.execute_script("""
146+ CREATE TABLE IF NOT EXISTS logs (id INTEGER, message TEXT);
147+ INSERT INTO logs (message) VALUES ('System started');
148+ """ )
81149```
82150
83- ### DuckDB LLM
151+ <details >
152+ <summary >🦆 DuckDB LLM Integration Example</summary >
84153
85154This is a quick implementation using some of the built-in Secret and Extension management features of SQLSpec's DuckDB integration.
86155
87- It allows you to communicate with any compatible OpenAPI conversations endpoint (such as Ollama). This example:
156+ It allows you to communicate with any compatible OpenAI conversations endpoint (such as Ollama). This example:
88157
89158- auto installs the ` open_prompt ` DuckDB extensions
90159- automatically creates the correct ` open_prompt ` compatible secret required to use the extension
@@ -104,11 +173,12 @@ from pydantic import BaseModel
104173class ChatMessage (BaseModel ):
105174 message: str
106175
107- sql = SQLSpec()
108- etl_config = sql.add_config(
109- DuckDBConfig(
110- extensions = [{" name" : " open_prompt" }],
111- secrets = [
176+ db_manager = SQLSpec()
177+ config = DuckDBConfig(
178+ pool_config = {" database" : " :memory:" },
179+ driver_features = {
180+ " extensions" : [{" name" : " open_prompt" }],
181+ " secrets" : [
112182 {
113183 " secret_type" : " open_prompt" ,
114184 " name" : " open_prompt" ,
@@ -119,9 +189,11 @@ etl_config = sql.add_config(
119189 },
120190 }
121191 ],
122- )
192+ },
123193)
124- with sql.provide_session(etl_config) as session:
194+ db_manager.add_config(config)
195+
196+ with db_manager.provide_session(config) as session:
125197 result = session.select_one(
126198 " SELECT open_prompt(?)" ,
127199 " Can you write a haiku about DuckDB?" ,
@@ -130,7 +202,10 @@ with sql.provide_session(etl_config) as session:
130202 print (result) # result is a ChatMessage pydantic model
131203```
132204
133- ### DuckDB Gemini Embeddings
205+ </details >
206+
207+ <details >
208+ <summary >🔗 DuckDB Gemini Embeddings Example</summary >
134209
135210In this example, we are again using DuckDB. However, we are going to use the built-in to call the Google Gemini embeddings service directly from the database.
136211
@@ -157,11 +232,12 @@ API_URL = (
157232 f " https://generativelanguage.googleapis.com/v1beta/models/ { EMBEDDING_MODEL } :embedContent?key=$ { GOOGLE_API_KEY } "
158233)
159234
160- sql = SQLSpec()
161- etl_config = sql.add_config(
162- DuckDBConfig(
163- extensions = [{" name" : " vss" }, {" name" : " http_client" }],
164- on_connection_create = lambda connection : connection.execute(f """
235+ db_manager = SQLSpec()
236+ config = DuckDBConfig(
237+ pool_config = {" database" : " :memory:" },
238+ driver_features = {
239+ " extensions" : [{" name" : " vss" }, {" name" : " http_client" }],
240+ " on_connection_create" : lambda connection : connection.execute(f """
165241 CREATE IF NOT EXISTS MACRO generate_embedding(q) AS (
166242 WITH __request AS (
167243 SELECT http_post(
@@ -180,16 +256,77 @@ etl_config = sql.add_config(
180256 FROM __request,
181257 );
182258 """ ),
183- )
259+ },
184260)
185- with sql.provide_session(etl_config) as session:
261+ db_manager.add_config(config)
262+
263+ with db_manager.provide_session(config) as session:
186264 result = session.execute(" SELECT generate_embedding('example text')" )
187265 print (result.get_first()) # result is a dictionary when `schema_type` is omitted.
188266```
189267
268+ </details >
269+
270+ ### SQL File Loading
271+
272+ SQLSpec can load and manage SQL queries from files using aiosql-style named queries:
273+
274+ ``` python
275+ from sqlspec import SQLSpec
276+ from sqlspec.loader import SQLFileLoader
277+ from sqlspec.adapters.sqlite import SqliteConfig
278+
279+ # Initialize with SQL file loader
280+ db_manager = SQLSpec(loader = SQLFileLoader())
281+ config = SqliteConfig(pool_config = {" database" : " :memory:" })
282+ db_manager.add_config(config)
283+
284+ # Load SQL files from directory
285+ db_manager.load_sql_files(" ./sql" )
286+
287+ # SQL file: ./sql/users.sql
288+ # -- name: get_user
289+ # SELECT * FROM users WHERE id = ?
290+ #
291+ # -- name: create_user
292+ # INSERT INTO users (name, email) VALUES (?, ?)
293+
294+ with db_manager.provide_session(config) as session:
295+ # Use named queries from files
296+ user = session.execute(db_manager.get_sql(" get_user" ), 1 )
297+ session.execute(db_manager.get_sql(
" create_user" ),
" Alice" ,
" [email protected] " )
298+ ```
299+
300+ ### Database Migrations
301+
302+ SQLSpec includes a built-in migration system for managing schema changes. After configuring your database with migration settings, use the CLI commands:
303+
304+ ``` bash
305+ # Initialize migration directory
306+ sqlspec db init migrations
307+
308+ # Generate new migration file
309+ sqlspec db make-migrations " Add user table"
310+
311+ # Apply all pending migrations
312+ sqlspec db upgrade
313+
314+ # Show current migration status
315+ sqlspec db show-current-revision
316+ ```
317+
318+ For Litestar applications, replace ` sqlspec ` with your application command:
319+
320+ ``` bash
321+ # Using Litestar CLI integration
322+ litestar db make-migrations " Add user table"
323+ litestar db upgrade
324+ litestar db show-current-revision
325+ ```
326+
190327### Basic Litestar Integration
191328
192- In this example we are going to demonstrate how to create a basic configuration that integrates into Litestar.
329+ In this example we demonstrate how to create a basic configuration that integrates into Litestar:
193330
194331``` py
195332# /// script
@@ -212,7 +349,7 @@ async def simple_sqlite(db_session: AiosqliteDriver) -> dict[str, str]:
212349
213350sqlspec = SQLSpec(
214351 config = DatabaseConfig(
215- config = AiosqliteConfig(),
352+ config = AiosqliteConfig(pool_config = { " database " : " :memory: " }), # built in local pooling
216353 commit_mode = " autocommit"
217354 )
218355)
@@ -231,6 +368,41 @@ The primary goal at this stage is to establish a **native connectivity interface
231368
232369This list is not final. If you have a driver you'd like to see added, please open an issue or submit a PR!
233370
371+ ### Configuration Examples
372+
373+ Each adapter uses a consistent configuration pattern with ` pool_config ` for connection parameters:
374+
375+ ``` python
376+ # SQLite
377+ SqliteConfig(pool_config = {" database" : " /path/to/database.db" })
378+ AiosqliteConfig(pool_config = {" database" : " /path/to/database.db" }) # Async
379+ AdbcConfig(connection_config = {" uri" : " sqlite:///path/to/database.db" }) # ADBC
380+
381+ # PostgreSQL (multiple drivers available)
382+ PsycopgSyncConfig(pool_config = {" host" : " localhost" , " database" : " mydb" , " user" : " user" , " password" : " pass" })
383+ PsycopgAsyncConfig(pool_config = {" host" : " localhost" , " database" : " mydb" , " user" : " user" , " password" : " pass" }) # Async
384+ AsyncpgConfig(pool_config = {" host" : " localhost" , " database" : " mydb" , " user" : " user" , " password" : " pass" })
385+ PsqlpyConfig(pool_config = {" dsn" : " postgresql://user:pass@localhost/mydb" })
386+ AdbcConfig(connection_config = {" uri" : " postgresql://user:pass@localhost/mydb" }) # ADBC
387+
388+ # DuckDB
389+ DuckDBConfig(pool_config = {" database" : " :memory:" }) # or file path
390+ AdbcConfig(connection_config = {" uri" : " duckdb:///path/to/database.duckdb" }) # ADBC
391+
392+ # MySQL
393+ AsyncmyConfig(pool_config = {" host" : " localhost" , " database" : " mydb" , " user" : " user" , " password" : " pass" }) # Async
394+
395+ # Oracle
396+ OracleSyncConfig(pool_config = {" host" : " localhost" , " service_name" : " XEPDB1" , " user" : " user" , " password" : " pass" })
397+ OracleAsyncConfig(pool_config = {" host" : " localhost" , " service_name" : " XEPDB1" , " user" : " user" , " password" : " pass" }) # Async
398+
399+ # BigQuery
400+ BigQueryConfig(pool_config = {" project" : " my-project" , " dataset" : " my_dataset" })
401+ AdbcConfig(connection_config = {" driver_name" : " adbc_driver_bigquery" , " project_id" : " my-project" , " dataset_id" : " my_dataset" }) # ADBC
402+ ```
403+
404+ ### Supported Drivers
405+
234406| Driver | Database | Mode | Status |
235407| :----------------------------------------------------------------------------------------------------------- | :--------- | :------ | :--------- |
236408| [ ` adbc ` ] ( https://arrow.apache.org/adbc/ ) | Postgres | Sync | ✅ |
@@ -253,21 +425,35 @@ This list is not final. If you have a driver you'd like to see added, please ope
253425| [ ` asyncmy ` ] ( https://github.com/long2ice/asyncmy ) | MySQL | Async | ✅ |
254426| [ ` snowflake ` ] ( https://docs.snowflake.com ) | Snowflake | Sync | 🗓️ |
255427
256- ## Proposed Project Structure
428+ ## Project Structure
257429
258430- ` sqlspec/ ` :
259- - ` adapters/ ` : Contains all database drivers and associated configuration.
260- - ` extensions/ ` :
261- - ` litestar/ ` : Litestar framework integration ✅
262- - ` fastapi/ ` : Future home of ` fastapi ` integration.
263- - ` flask/ ` : Future home of ` flask ` integration.
264- - ` */ ` : Future home of your favorite framework integration
265- - ` base.py ` : Contains base protocols for database configurations.
266- - ` statement/ ` : Contains the SQL statement system with builders, validation, and transformation.
267- - ` storage/ ` : Contains unified storage operations for data import/export.
268- - ` utils/ ` : Contains utility functions used throughout the project.
269- - ` exceptions.py ` : Contains custom exceptions for SQLSpec.
270- - ` typing.py ` : Contains type hints, type guards and several facades for optional libraries that are not required for the core functionality of SQLSpec.
431+ - ` adapters/ ` : Database-specific drivers and configuration classes for all supported databases
432+ - ` extensions/ ` : Framework integrations and external library adapters
433+ - ` litestar/ ` : Litestar web framework integration with dependency injection ✅
434+ - ` aiosql/ ` : Integration with aiosql for SQL file loading ✅
435+ - Future integrations: ` fastapi/ ` , ` flask/ ` , etc.
436+ - ` builder/ ` : Fluent SQL query builder with method chaining and type safety
437+ - ` mixins/ ` : Composable query building operations (WHERE, JOIN, ORDER BY, etc.)
438+ - ` core/ ` : Core query processing infrastructure
439+ - ` statement.py ` : SQL statement wrapper with metadata and type information
440+ - ` parameters.py ` : Parameter style conversion and validation
441+ - ` result.py ` : Result set handling and type mapping
442+ - ` compiler.py ` : SQL compilation and validation using SQLGlot
443+ - ` cache.py ` : Statement caching for performance optimization
444+ - ` driver/ ` : Base driver system with sync/async support and transaction management
445+ - ` mixins/ ` : Shared driver capabilities (result processing, SQL translation)
446+ - ` migrations/ ` : Database migration system with CLI commands
447+ - ` storage/ ` : Unified data import/export operations with multiple backends
448+ - ` backends/ ` : Storage backend implementations (fsspec, obstore)
449+ - ` utils/ ` : Utility functions, type guards, and helper tools
450+ - ` base.py ` : Main SQLSpec registry and configuration manager
451+ - ` loader.py ` : SQL file loading system for ` .sql ` files
452+ - ` cli.py ` : Command-line interface for migrations and database operations
453+ - ` config.py ` : Base configuration classes and protocols
454+ - ` protocols.py ` : Type protocols for runtime type checking
455+ - ` exceptions.py ` : Custom exception hierarchy for SQLSpec
456+ - ` typing.py ` : Type definitions, guards, and optional dependency facades
271457
272458## Get Involved
273459
0 commit comments