Skip to content

Commit 482a6f9

Browse files
authored
feat: implements a driver query protocol (#21)
This change implements the first iteration of a "Driver". This is a class designed to make interacting with database connections more consistent across the various adapters.
1 parent 722e571 commit 482a6f9

File tree

96 files changed

+9537
-2425
lines changed

Some content is hidden

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

96 files changed

+9537
-2425
lines changed

.github/workflows/ci.yml

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -108,51 +108,51 @@ jobs:
108108
- name: Test
109109
run: uv run pytest -m ""
110110

111-
test-windows:
112-
runs-on: windows-latest
113-
strategy:
114-
fail-fast: true
115-
matrix:
116-
python-version: ["3.12", "3.13"]
117-
timeout-minutes: 30
118-
steps:
119-
- name: Check out repository
120-
uses: actions/checkout@v4
121-
122-
- name: Install uv
123-
uses: astral-sh/setup-uv@v3
124-
125-
- name: Set up Python
126-
run: uv python install ${{ matrix.python-version }}
127-
128-
- name: Install dependencies
129-
run: uv sync --all-extras --dev
130-
131-
- name: Test
132-
run: uv run pytest -m ""
133-
134-
test-osx:
135-
runs-on: macos-latest
136-
strategy:
137-
fail-fast: true
138-
matrix:
139-
python-version: ["3.11", "3.12", "3.13"]
140-
timeout-minutes: 30
141-
steps:
142-
- name: Check out repository
143-
uses: actions/checkout@v4
144-
145-
- name: Install uv
146-
uses: astral-sh/setup-uv@v3
147-
148-
- name: Set up Python
149-
run: uv python install ${{ matrix.python-version }}
150-
151-
- name: Install dependencies
152-
run: uv sync --all-extras --dev
153-
154-
- name: Test
155-
run: uv run pytest -m ""
111+
# test-windows:
112+
# runs-on: windows-latest
113+
# strategy:
114+
# fail-fast: true
115+
# matrix:
116+
# python-version: ["3.12", "3.13"]
117+
# timeout-minutes: 30
118+
# steps:
119+
# - name: Check out repository
120+
# uses: actions/checkout@v4
121+
122+
# - name: Install uv
123+
# uses: astral-sh/setup-uv@v3
124+
125+
# - name: Set up Python
126+
# run: uv python install ${{ matrix.python-version }}
127+
128+
# - name: Install dependencies
129+
# run: uv sync --all-extras --dev
130+
131+
# - name: Test
132+
# run: uv run pytest -m ""
133+
134+
# test-osx:
135+
# runs-on: macos-latest
136+
# strategy:
137+
# fail-fast: true
138+
# matrix:
139+
# python-version: ["3.11", "3.12", "3.13"]
140+
# timeout-minutes: 30
141+
# steps:
142+
# - name: Check out repository
143+
# uses: actions/checkout@v4
144+
145+
# - name: Install uv
146+
# uses: astral-sh/setup-uv@v3
147+
148+
# - name: Set up Python
149+
# run: uv python install ${{ matrix.python-version }}
150+
151+
# - name: Install dependencies
152+
# run: uv sync --all-extras --dev
153+
154+
# - name: Test
155+
# run: uv run pytest -m ""
156156

157157
build-docs:
158158
needs:

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
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.10.0"
20+
rev: "v0.11.6"
2121
hooks:
2222
- id: ruff
2323
args: ["--fix"]

docs/examples/litestar_duckllm.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Litestar DuckLLM
2+
3+
This example demonstrates how to use the Litestar framework with the DuckLLM extension.
4+
5+
The example uses the `SQLSpec` extension to create a connection to the DuckDB database.
6+
The `DuckDB` adapter is used to create a connection to the database.
7+
"""
8+
9+
# /// script
10+
# dependencies = [
11+
# "sqlspec[duckdb,performance] @ git+https://github.com/litestar-org/sqlspec.git@query-service",
12+
# "litestar[standard]",
13+
# ]
14+
# ///
15+
16+
from duckdb import DuckDBPyConnection
17+
from litestar import Litestar, post
18+
from msgspec import Struct
19+
20+
from sqlspec.adapters.duckdb import DuckDB
21+
from sqlspec.extensions.litestar import SQLSpec
22+
23+
24+
class ChatMessage(Struct):
25+
message: str
26+
27+
28+
@post("/chat", sync_to_thread=True)
29+
def duckllm_chat(db_connection: DuckDBPyConnection, data: ChatMessage) -> ChatMessage:
30+
result = db_connection.execute("SELECT open_prompt(?)", (data.message,)).fetchall()
31+
return ChatMessage(message=result[0][0])
32+
33+
34+
sqlspec = SQLSpec(
35+
config=DuckDB(
36+
extensions=[{"name": "open_prompt"}],
37+
secrets=[
38+
{
39+
"secret_type": "open_prompt",
40+
"name": "open_prompt",
41+
"value": {
42+
"api_url": "http://127.0.0.1:11434/v1/chat/completions",
43+
"model_name": "gemma3:1b",
44+
"api_timeout": "120",
45+
},
46+
}
47+
],
48+
),
49+
)
50+
app = Litestar(route_handlers=[duckllm_chat], plugins=[sqlspec], debug=True)
51+
52+
if __name__ == "__main__":
53+
import uvicorn
54+
55+
uvicorn.run(app, host="0.0.0.0", port=8000)

docs/examples/litestar_gemini.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""Litestar DuckLLM
2+
3+
This example demonstrates how to use the Litestar framework with the DuckLLM extension.
4+
5+
The example uses the `SQLSpec` extension to create a connection to the DuckDB database.
6+
The `DuckDB` adapter is used to create a connection to the database.
7+
"""
8+
9+
# /// script
10+
# dependencies = [
11+
# "sqlspec[duckdb,performance] @ git+https://github.com/litestar-org/sqlspec.git@query-service",
12+
# "litestar[standard]",
13+
# ]
14+
# ///
15+
16+
import os
17+
18+
from sqlspec import SQLSpec
19+
from sqlspec.adapters.duckdb import DuckDB
20+
21+
EMBEDDING_MODEL = "gemini-embedding-exp-03-07"
22+
GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
23+
API_URL = (
24+
f"https://generativelanguage.googleapis.com/v1beta/models/{EMBEDDING_MODEL}:embedContent?key=${GOOGLE_API_KEY}"
25+
)
26+
27+
sql = SQLSpec()
28+
etl_config = sql.add_config(
29+
DuckDB(
30+
extensions=[{"name": "vss"}, {"name": "http_client"}],
31+
on_connection_create=lambda connection: connection.execute(f"""
32+
CREATE IF NOT EXISTS MACRO generate_embedding(q) AS (
33+
WITH __request AS (
34+
SELECT http_post(
35+
'{API_URL}',
36+
headers => MAP {{
37+
'accept': 'application/json',
38+
}},
39+
params => MAP {{
40+
'model': 'models/{EMBEDDING_MODEL}',
41+
'parts': [{{ 'text': q }}],
42+
'taskType': 'SEMANTIC_SIMILARITY'
43+
}}
44+
) AS response
45+
)
46+
SELECT *
47+
FROM __request,
48+
);
49+
"""),
50+
)
51+
)
52+
53+
54+
if __name__ == "__main__":
55+
with sql.get_connection(etl_config) as connection:
56+
result = connection.execute("SELECT generate_embedding('example text')")
57+
print(result.fetchall())

docs/examples/litestar_multi_db.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,33 @@
22
from duckdb import DuckDBPyConnection
33
from litestar import Litestar, get
44

5-
from sqlspec.adapters.aiosqlite import AiosqliteConfig
6-
from sqlspec.adapters.duckdb import DuckDBConfig
5+
from sqlspec.adapters.aiosqlite import Aiosqlite
6+
from sqlspec.adapters.duckdb import DuckDB
77
from sqlspec.extensions.litestar import DatabaseConfig, SQLSpec
88

99

1010
@get("/test", sync_to_thread=True)
11-
def simple_select(etl_session: DuckDBPyConnection) -> dict[str, str]:
12-
result = etl_session.execute("SELECT 'Hello, world!' AS greeting").fetchall()
11+
def simple_select(etl_connection: DuckDBPyConnection) -> dict[str, str]:
12+
result = etl_connection.execute("SELECT 'Hello, world!' AS greeting").fetchall()
1313
return {"greeting": result[0][0]}
1414

1515

1616
@get("/")
1717
async def simple_sqlite(db_connection: Connection) -> dict[str, str]:
1818
result = await db_connection.execute_fetchall("SELECT 'Hello, world!' AS greeting")
19-
return {"greeting": result[0][0]} # type: ignore # noqa: PGH003
19+
return {"greeting": result[0][0]} # type: ignore
2020

2121

2222
sqlspec = SQLSpec(
2323
config=[
24-
DatabaseConfig(config=AiosqliteConfig(), commit_mode="autocommit"),
25-
DatabaseConfig(config=DuckDBConfig(), connection_key="etl_session"),
24+
DatabaseConfig(config=Aiosqlite(), commit_mode="autocommit"),
25+
DatabaseConfig(
26+
config=DuckDB(
27+
extensions=[{"name": "vss", "force_install": True}],
28+
secrets=[{"secret_type": "s3", "name": "s3_secret", "value": {"key_id": "abcd"}}],
29+
),
30+
connection_key="etl_connection",
31+
),
2632
],
2733
)
2834
app = Litestar(route_handlers=[simple_sqlite, simple_select], plugins=[sqlspec])
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
from aiosqlite import Connection
22
from litestar import Litestar, get
33

4-
from sqlspec.adapters.aiosqlite import AiosqliteConfig
4+
from sqlspec.adapters.aiosqlite import Aiosqlite
55
from sqlspec.extensions.litestar import SQLSpec
66

77

88
@get("/")
9-
async def simple_sqlite(db_session: Connection) -> dict[str, str]:
9+
async def simple_sqlite(db_connection: Connection) -> dict[str, str]:
1010
"""Simple select statement.
1111
1212
Returns:
1313
dict[str, str]: The greeting.
1414
"""
15-
result = await db_session.execute_fetchall("SELECT 'Hello, world!' AS greeting")
16-
return {"greeting": result[0][0]} # type: ignore # noqa: PGH003
15+
result = await db_connection.execute_fetchall("SELECT 'Hello, world!' AS greeting")
16+
return {"greeting": result[0][0]} # type: ignore
1717

1818

19-
sqlspec = SQLSpec(config=AiosqliteConfig())
19+
sqlspec = SQLSpec(config=Aiosqlite())
2020
app = Litestar(route_handlers=[simple_sqlite], plugins=[sqlspec])

pyproject.toml

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ requires-python = ">=3.9, <4.0"
1010
version = "0.7.1"
1111

1212
[project.optional-dependencies]
13-
adbc = ["adbc-driver-manager", "pyarrow"]
13+
adbc = ["adbc_driver_manager", "pyarrow"]
1414
aioodbc = ["aioodbc"]
1515
aiosqlite = ["aiosqlite"]
1616
asyncmy = ["asyncmy"]
@@ -35,9 +35,7 @@ uuid = ["uuid-utils>=0.6.1"]
3535
[dependency-groups]
3636
build = ["bump-my-version"]
3737
dev = [
38-
"adbc-driver-sqlite",
39-
"adbc-driver-postgresql",
40-
"adbc-driver-flightsql",
38+
{ include-group = "extras" },
4139
{ include-group = "lint" },
4240
{ include-group = "doc" },
4341
{ include-group = "test" },
@@ -59,6 +57,15 @@ doc = [
5957
"myst-parser",
6058
"sphinx-autodoc-typehints",
6159
]
60+
extras = [
61+
"adbc_driver_manager",
62+
"pyarrow",
63+
"polars",
64+
"adbc_driver_sqlite",
65+
"adbc_driver_postgresql",
66+
"adbc_driver_flightsql",
67+
"adbc_driver_bigquery",
68+
]
6269
lint = [
6370
"mypy>=1.13.0",
6471
"pre-commit>=3.5.0",
@@ -75,7 +82,7 @@ test = [
7582
"pytest>=8.0.0",
7683
"pytest-asyncio>=0.23.8",
7784
"pytest-cov>=5.0.0",
78-
"pytest-databases>=0.10.0",
85+
"pytest-databases[postgres,oracle,mysql,bigquery,spanner]>=0.12.2",
7986
"pytest-mock>=3.14.0",
8087
"pytest-sugar>=1.0.0",
8188
"pytest-xdist>=3.6.1",
@@ -217,10 +224,17 @@ module = [
217224

218225
[tool.pyright]
219226
disableBytesTypePromotions = true
220-
exclude = ["tools", "docs"]
227+
exclude = ["**/node_modules", "**/__pycache__", ".venv", "tools", "docs"]
221228
include = ["sqlspec", "tests"]
222229
pythonVersion = "3.9"
230+
reportMissingTypeStubs = false
231+
reportPrivateImportUsage = false
232+
reportPrivateUsage = false
233+
reportUnknownArgumentType = false
234+
reportUnknownMemberType = false
235+
reportUnknownVariableType = false
223236
reportUnnecessaryTypeIgnoreComments = true
237+
root = "."
224238

225239

226240
[tool.slotscheck]
@@ -269,6 +283,7 @@ ignore = [
269283
"CPY001", # pycodestyle - Missing Copywrite notice at the top of the file
270284
"RUF029", # Ruff - function is declared as async but has no awaitable calls
271285
"COM812", # flake8-comma - Missing trailing comma
286+
"PGH003", # Use Specific ignore for pyright
272287
]
273288
select = ["ALL"]
274289

sqlspec/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from sqlspec import adapters, base, exceptions, extensions, filters, typing, utils
2+
from sqlspec.__metadata__ import __version__
3+
from sqlspec.base import SQLSpec
4+
5+
__all__ = (
6+
"SQLSpec",
7+
"__version__",
8+
"adapters",
9+
"base",
10+
"exceptions",
11+
"extensions",
12+
"filters",
13+
"typing",
14+
"utils",
15+
)

0 commit comments

Comments
 (0)