Skip to content

Commit 280f7a9

Browse files
authored
feat: builder api (#36)
Implements an initial builder API and driver execution framework.
1 parent 7c98ba9 commit 280f7a9

File tree

360 files changed

+84135
-15814
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

360 files changed

+84135
-15814
lines changed

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ site/
1717
target/
1818
.idea/
1919
.vscode/
20+
.claude/
2021
.cursor/
22+
.zed/
2123

2224
# files
2325
**/*.so
@@ -31,3 +33,13 @@ target/
3133
/docs/_build/
3234
coverage.*
3335
setup.py
36+
tmp/
37+
*.log
38+
.bugs
39+
.tmp
40+
.todos
41+
todo/
42+
CLAUDE.md
43+
CLAUDE.*.md
44+
TODO*
45+
.claudedocs

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ repos:
1717
- id: mixed-line-ending
1818
- id: trailing-whitespace
1919
- repo: https://github.com/charliermarsh/ruff-pre-commit
20-
rev: "v0.11.9"
20+
rev: "v0.12.0"
2121
hooks:
2222
- id: ruff
2323
args: ["--fix"]
@@ -29,7 +29,7 @@ repos:
2929
additional_dependencies:
3030
- tomli
3131
- repo: https://github.com/python-formate/flake8-dunder-all
32-
rev: v0.4.1
32+
rev: v0.5.0
3333
hooks:
3434
- id: ensure-dunder-all
3535
exclude: "test*|tools"

docs/conf.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@
4646

4747
nitpicky = True
4848
nitpick_ignore: list[str] = []
49-
nitpick_ignore_regex = [
50-
(PY_RE, r"sqlspec.*\.T"),
51-
]
49+
nitpick_ignore_regex = [(PY_RE, r"sqlspec.*\.T")]
5250

5351
napoleon_google_docstring = True
5452
napoleon_include_special_with_doc = True
@@ -79,11 +77,7 @@
7977
html_title = "SQLSpec"
8078
# html_favicon = "_static/logo.png"
8179
# html_logo = "_static/logo.png"
82-
html_context = {
83-
"source_type": "github",
84-
"source_user": "cofin",
85-
"source_repo": project.replace("_", "-"),
86-
}
80+
html_context = {"source_type": "github", "source_user": "cofin", "source_repo": project.replace("_", "-")}
8781

8882
brand_colors = {
8983
"--brand-primary": {"rgb": "245, 0, 87", "hex": "#f50057"},

docs/examples/litestar_asyncpg.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,28 @@
1313
# ]
1414
# ///
1515

16-
from typing import Annotated, Optional
16+
from typing import Annotated, Any
1717

1818
from litestar import Litestar, get
1919
from litestar.params import Dependency
2020

21-
from sqlspec.adapters.asyncpg import AsyncpgConfig, AsyncpgDriver, AsyncpgPoolConfig
21+
from sqlspec.adapters.asyncpg import AsyncpgConfig, AsyncpgDriver
2222
from sqlspec.extensions.litestar import DatabaseConfig, SQLSpec, providers
23-
from sqlspec.filters import FilterTypes
23+
from sqlspec.statement import SQLResult
24+
from sqlspec.statement.filters import FilterTypes
2425

2526

26-
@get(
27-
"/",
28-
dependencies=providers.create_filter_dependencies({"search": "greeting", "search_ignore_case": True}),
29-
)
27+
@get("/", dependencies=providers.create_filter_dependencies({"search": "greeting", "search_ignore_case": True}))
3028
async def simple_asyncpg(
3129
db_session: AsyncpgDriver, filters: Annotated[list[FilterTypes], Dependency(skip_validation=True)]
32-
) -> Optional[dict[str, str]]:
33-
return await db_session.select_one_or_none(
34-
"SELECT greeting FROM (select 'Hello, world!' as greeting) as t", *filters
35-
)
30+
) -> SQLResult[dict[str, Any]]:
31+
return await db_session.execute("SELECT greeting FROM (select 'Hello, world!' as greeting) as t", *filters)
3632

3733

3834
sqlspec = SQLSpec(
3935
config=[
4036
DatabaseConfig(
41-
config=AsyncpgConfig(
42-
pool_config=AsyncpgPoolConfig(dsn="postgres://app:app@localhost:15432/app", min_size=1, max_size=3),
43-
),
37+
config=AsyncpgConfig(dsn="postgres://app:app@localhost:15432/app", min_size=1, max_size=3),
4438
commit_mode="autocommit",
4539
)
4640
]

docs/examples/litestar_duckllm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def duckllm_chat(db_session: DuckDBDriver, data: ChatMessage) -> ChatMessage:
4444
},
4545
}
4646
],
47-
),
47+
)
4848
)
4949
app = Litestar(route_handlers=[duckllm_chat], plugins=[sqlspec], debug=True)
5050

docs/examples/litestar_multi_db.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222

2323
@get("/test", sync_to_thread=True)
2424
def simple_select(etl_session: DuckDBDriver) -> dict[str, str]:
25-
result = etl_session.select_one("SELECT 'Hello, ETL world!' AS greeting")
26-
return {"greeting": result["greeting"]}
25+
result = etl_session.execute("SELECT 'Hello, ETL world!' AS greeting")
26+
greeting = result.get_first()
27+
return {"greeting": greeting["greeting"] if greeting is not None else "hi"}
2728

2829

2930
@get("/")
@@ -42,7 +43,7 @@ async def simple_sqlite(db_session: AiosqliteDriver) -> dict[str, str]:
4243
connection_key="etl_connection",
4344
session_key="etl_session",
4445
),
45-
],
46+
]
4647
)
4748
app = Litestar(route_handlers=[simple_sqlite, simple_select], plugins=[sqlspec])
4849

docs/examples/litestar_psycopg.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,23 @@
1515

1616
from litestar import Litestar, get
1717

18-
from sqlspec.adapters.psycopg import PsycopgAsyncConfig, PsycopgAsyncDriver, PsycopgAsyncPoolConfig
18+
from sqlspec.adapters.psycopg import PsycopgAsyncConfig, PsycopgAsyncDriver
1919
from sqlspec.extensions.litestar import DatabaseConfig, SQLSpec
2020

2121

2222
@get("/")
2323
async def simple_psycopg(db_session: PsycopgAsyncDriver) -> dict[str, str]:
24-
return await db_session.select_one("SELECT 'Hello, world!' AS greeting")
24+
result = await db_session.execute("SELECT 'Hello, world!' AS greeting")
25+
return result.get_first() or {"greeting": "No result found"}
2526

2627

2728
sqlspec = SQLSpec(
2829
config=[
2930
DatabaseConfig(
30-
config=PsycopgAsyncConfig(
31-
pool_config=PsycopgAsyncPoolConfig(
32-
conninfo="postgres://app:app@localhost:15432/app", min_size=1, max_size=3
33-
),
34-
),
31+
config=PsycopgAsyncConfig(conninfo="postgres://app:app@localhost:15432/app", min_size=1, max_size=3),
3532
commit_mode="autocommit",
3633
)
37-
],
34+
]
3835
)
3936
app = Litestar(route_handlers=[simple_psycopg], plugins=[sqlspec])
4037

docs/examples/litestar_single_db.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,6 @@
55
This examples hows how to get the raw connection object from the SQLSpec plugin.
66
"""
77

8-
# /// script
9-
# dependencies = [
10-
# "sqlspec[aiosqlite]",
11-
# "litestar[standard]",
12-
# ]
13-
# ///
14-
158
from aiosqlite import Connection
169
from litestar import Litestar, get
1710

@@ -27,8 +20,8 @@ async def simple_sqlite(db_connection: Connection) -> dict[str, str]:
2720
dict[str, str]: The greeting.
2821
"""
2922
result = await db_connection.execute_fetchall("SELECT 'Hello, world!' AS greeting")
30-
return {"greeting": result[0][0]} # type: ignore
23+
return {"greeting": next(iter(result))[0]}
3124

3225

33-
sqlspec = SQLSpec(config=AiosqliteConfig())
26+
sqlspec = SQLSpec(config=AiosqliteConfig(database=":memory:"))
3427
app = Litestar(route_handlers=[simple_sqlite], plugins=[sqlspec])
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""Example of how to configure logging for SQLSpec.
2+
3+
Since SQLSpec no longer provides a configure_logging function,
4+
users can set up their own logging configuration as needed.
5+
"""
6+
7+
import logging
8+
import sys
9+
10+
from sqlspec.utils.correlation import correlation_context
11+
from sqlspec.utils.logging import StructuredFormatter, get_logger
12+
13+
__all__ = ("demo_correlation_ids", "setup_advanced_logging", "setup_simple_logging", "setup_structured_logging")
14+
15+
16+
# Example 1: Basic logging setup with structured JSON output
17+
def setup_structured_logging() -> None:
18+
"""Set up structured JSON logging for SQLSpec."""
19+
# Get the SQLSpec logger
20+
sqlspec_logger = logging.getLogger("sqlspec")
21+
22+
# Set the logging level
23+
sqlspec_logger.setLevel(logging.INFO)
24+
25+
# Create a console handler with structured formatter
26+
console_handler = logging.StreamHandler(sys.stdout)
27+
console_handler.setFormatter(StructuredFormatter())
28+
29+
# Add the handler to the logger
30+
sqlspec_logger.addHandler(console_handler)
31+
32+
# Don't propagate to the root logger
33+
sqlspec_logger.propagate = False
34+
35+
print("Structured logging configured for SQLSpec")
36+
37+
38+
# Example 2: Simple text logging
39+
def setup_simple_logging() -> None:
40+
"""Set up simple text logging for SQLSpec."""
41+
# Configure basic logging for the entire application
42+
logging.basicConfig(
43+
level=logging.INFO,
44+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
45+
handlers=[logging.StreamHandler(sys.stdout)],
46+
)
47+
48+
print("Simple logging configured")
49+
50+
51+
# Example 3: Advanced setup with file output and custom formatting
52+
def setup_advanced_logging() -> None:
53+
"""Set up advanced logging with multiple handlers."""
54+
sqlspec_logger = logging.getLogger("sqlspec")
55+
sqlspec_logger.setLevel(logging.DEBUG)
56+
57+
# Console handler with simple format
58+
console_handler = logging.StreamHandler(sys.stdout)
59+
console_formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
60+
console_handler.setFormatter(console_formatter)
61+
console_handler.setLevel(logging.INFO) # Only INFO and above to console
62+
63+
# File handler with structured format
64+
file_handler = logging.FileHandler("sqlspec.log")
65+
file_handler.setFormatter(StructuredFormatter())
66+
file_handler.setLevel(logging.DEBUG) # All messages to file
67+
68+
# Add both handlers
69+
sqlspec_logger.addHandler(console_handler)
70+
sqlspec_logger.addHandler(file_handler)
71+
72+
# Don't propagate to avoid duplicate logs
73+
sqlspec_logger.propagate = False
74+
75+
print("Advanced logging configured with console and file output")
76+
77+
78+
# Example 4: Using correlation IDs
79+
def demo_correlation_ids() -> None:
80+
"""Demonstrate using correlation IDs with logging."""
81+
82+
logger = get_logger("example")
83+
84+
# Without correlation ID
85+
logger.info("This log has no correlation ID")
86+
87+
# With correlation ID
88+
with correlation_context() as correlation_id:
89+
logger.info("Starting operation with correlation ID: %s", correlation_id)
90+
logger.info("This log will include the correlation ID automatically")
91+
92+
# Simulate some work
93+
logger.debug("Processing data...")
94+
logger.info("Operation completed")
95+
96+
97+
if __name__ == "__main__":
98+
# Choose your logging setup
99+
print("=== Structured Logging Example ===")
100+
setup_structured_logging()
101+
demo_correlation_ids()
102+
103+
print("\n=== Simple Logging Example ===")
104+
setup_simple_logging()
105+
106+
print("\n=== Advanced Logging Example ===")
107+
setup_advanced_logging()

docs/examples/queries/users.sql

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
-- User Management SQL Queries
2+
-- This file contains all user-related queries using aiosql-style named queries
3+
4+
-- name: get_user_by_id
5+
-- Get a single user by their ID
6+
SELECT
7+
id,
8+
username,
9+
email,
10+
created_at,
11+
updated_at
12+
FROM users
13+
WHERE id = :user_id;
14+
15+
-- name: get_user_by_email
16+
-- Find a user by their email address
17+
SELECT
18+
id,
19+
username,
20+
email,
21+
created_at
22+
FROM users
23+
WHERE LOWER(email) = LOWER(:email);
24+
25+
-- name: list_active_users
26+
-- List all active users with pagination
27+
SELECT
28+
id,
29+
username,
30+
email,
31+
last_login_at
32+
FROM users
33+
WHERE is_active = true
34+
ORDER BY username
35+
LIMIT :limit OFFSET :offset;
36+
37+
-- name: create_user
38+
-- Create a new user and return the created record
39+
INSERT INTO users (
40+
username,
41+
email,
42+
password_hash,
43+
is_active
44+
) VALUES (
45+
:username,
46+
:email,
47+
:password_hash,
48+
:is_active
49+
)
50+
RETURNING id, username, email, created_at;
51+
52+
-- name: update_user_last_login
53+
-- Update the last login timestamp for a user
54+
UPDATE users
55+
SET
56+
last_login_at = CURRENT_TIMESTAMP,
57+
updated_at = CURRENT_TIMESTAMP
58+
WHERE id = :user_id;
59+
60+
-- name: deactivate_user
61+
-- Soft delete a user by setting is_active to false
62+
UPDATE users
63+
SET
64+
is_active = false,
65+
updated_at = CURRENT_TIMESTAMP
66+
WHERE id = :user_id;
67+
68+
-- name: count_users_by_status
69+
-- Count users grouped by their active status
70+
SELECT
71+
is_active,
72+
COUNT(*) as count
73+
FROM users
74+
GROUP BY is_active;

0 commit comments

Comments
 (0)