Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

SQLSpec is a SQL execution layer for Python. You write the SQL -- as strings, through a builder API, or loaded from files -- and SQLSpec handles connections, parameter binding, SQL injection prevention, dialect translation, and mapping results back to typed Python objects. It uses [sqlglot](https://github.com/tobymao/sqlglot) under the hood to parse, validate, and optimize your queries before they hit the database.

It works with PostgreSQL (asyncpg, psycopg, psqlpy), SQLite (sqlite3, aiosqlite), DuckDB, MySQL (asyncmy, mysql-connector, pymysql), Oracle (oracledb), CockroachDB, BigQuery, Spanner, and anything ADBC-compatible. Sync or async, same API. It also includes a built-in storage layer, native and bridged Arrow support for all drivers, and integrations for Litestar, FastAPI, Flask, and Starlette.
It works with PostgreSQL (asyncpg, psycopg, psqlpy), SQLite (sqlite3, aiosqlite), DuckDB, MySQL (asyncmy, mysql-connector, pymysql), Oracle (oracledb), CockroachDB, BigQuery, Spanner, and anything ADBC-compatible. Sync or async, same API. It also includes a built-in storage layer, native and bridged Arrow support for all drivers, and integrations for Litestar, FastAPI, Flask, Sanic, and Starlette.

## Quick Start

Expand Down Expand Up @@ -57,7 +57,7 @@ users = session.select(
- **Parameter binding and dialect translation** -- powered by sqlglot, with a fluent query builder and `.sql` file loader
- **Result mapping** -- map rows to Pydantic, msgspec, attrs, or dataclass models, or export to Arrow tables for pandas and Polars
- **Storage layer** -- read and write Arrow tables to local files, fsspec, or object stores
- **Framework integrations** -- Litestar plugin with DI, Starlette/FastAPI middleware, Flask extension
- **Framework integrations** -- Litestar plugin with DI, Starlette/FastAPI/Sanic middleware, Flask extension
- **Observability** -- OpenTelemetry and Prometheus instrumentation, structured logging with correlation IDs
- **Event channels** -- LISTEN/NOTIFY, Oracle AQ, and a portable polling fallback
- **Migrations** -- schema versioning CLI built on Alembic
Expand Down
4 changes: 2 additions & 2 deletions docs/PYPI_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

SQLSpec is a SQL execution layer for Python. You write the SQL -- as strings, through a builder API, or loaded from files -- and SQLSpec handles connections, parameter binding, SQL injection prevention, dialect translation, and mapping results back to typed Python objects. It uses [sqlglot](https://github.com/tobymao/sqlglot) under the hood to parse, validate, and optimize your queries before they hit the database.

It works with PostgreSQL (asyncpg, psycopg, psqlpy), SQLite (sqlite3, aiosqlite), DuckDB, MySQL (asyncmy, mysql-connector, pymysql), Oracle (oracledb), CockroachDB, BigQuery, Spanner, and anything ADBC-compatible. Sync or async, same API. It also includes a built-in storage layer, native and bridged Arrow support for all drivers, and integrations for Litestar, FastAPI, Flask, and Starlette.
It works with PostgreSQL (asyncpg, psycopg, psqlpy), SQLite (sqlite3, aiosqlite), DuckDB, MySQL (asyncmy, mysql-connector, pymysql), Oracle (oracledb), CockroachDB, BigQuery, Spanner, and anything ADBC-compatible. Sync or async, same API. It also includes a built-in storage layer, native and bridged Arrow support for all drivers, and integrations for Litestar, FastAPI, Flask, Sanic, and Starlette.

## Quick Start

Expand Down Expand Up @@ -57,7 +57,7 @@ users = session.select(
- **Parameter binding and dialect translation** -- powered by sqlglot, with a fluent query builder and `.sql` file loader
- **Result mapping** -- map rows to Pydantic, msgspec, attrs, or dataclass models, or export to Arrow tables for pandas and Polars
- **Storage layer** -- read and write Arrow tables to local files, fsspec, or object stores
- **Framework integrations** -- Litestar plugin with DI, Starlette/FastAPI middleware, Flask extension
- **Framework integrations** -- Litestar plugin with DI, Starlette/FastAPI/Sanic middleware, Flask extension
- **Observability** -- OpenTelemetry and Prometheus instrumentation, structured logging with correlation IDs
- **Event channels** -- LISTEN/NOTIFY, Oracle AQ, and a portable polling fallback
- **Migrations** -- schema versioning CLI built on Alembic
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ markers for Sphinx `literalinclude` directives.
Structure overview:

- `quickstart/`: First-time setup and configuration.
- `frameworks/`: Litestar, FastAPI, Flask, and Starlette integration examples.
- `frameworks/`: Litestar, FastAPI, Flask, Sanic, and Starlette integration examples.
- `drivers/`: Adapter configuration and execution patterns.
- `querying/`: Core SQL execution helpers.
- `sql_files/`: SQL file loader and named query examples.
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/frameworks/fastapi/multi_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_fastapi_multi_database() -> None:
AiosqliteConfig(
connection_config={"database": ":memory:"},
extension_config={
"starlette": {"session_key": "db", "connection_key": "db_connection", "pool_key": "db_pool"}
"fastapi": {"session_key": "db", "connection_key": "db_connection", "pool_key": "db_pool"}
},
)
)
Expand All @@ -33,7 +33,7 @@ def test_fastapi_multi_database() -> None:
SqliteConfig(
connection_config={"database": ":memory:"},
extension_config={
"starlette": {"session_key": "etl_db", "connection_key": "etl_connection", "pool_key": "etl_pool"}
"fastapi": {"session_key": "etl_db", "connection_key": "etl_connection", "pool_key": "etl_pool"}
},
)
)
Expand Down
1 change: 1 addition & 0 deletions docs/examples/frameworks/sanic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Sanic framework examples."""
37 changes: 37 additions & 0 deletions docs/examples/frameworks/sanic/basic_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pytest

__all__ = ("test_sanic_basic_setup",)


def test_sanic_basic_setup() -> None:
pytest.importorskip("sanic")
pytest.importorskip("aiosqlite")
# start-example
from sanic import Request, Sanic, response
from sanic.response import HTTPResponse

from sqlspec import SQLSpec
from sqlspec.adapters.aiosqlite import AiosqliteConfig, AiosqliteDriver
from sqlspec.extensions.sanic import SQLSpecPlugin

sqlspec = SQLSpec()
sqlspec.add_config(
AiosqliteConfig(
connection_config={"database": ":memory:"},
extension_config={"sanic": {"commit_mode": "manual", "session_key": "db"}},
)
)

db_plugin = SQLSpecPlugin(sqlspec)
app = Sanic("SQLSpecSanicBasicExample")

@app.get("/health")
async def health(request: Request) -> HTTPResponse:
db: AiosqliteDriver = db_plugin.get_session(request, "db")
result = await db.execute("select 1 as ok")
return response.json(result.one())

db_plugin.init_app(app)
# end-example

assert app is not None
39 changes: 39 additions & 0 deletions docs/examples/frameworks/sanic/commit_modes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import pytest

__all__ = ("test_sanic_commit_modes",)


def test_sanic_commit_modes() -> None:
pytest.importorskip("sanic")
pytest.importorskip("aiosqlite")
# start-example
from sanic import Request, Sanic, response
from sanic.response import HTTPResponse

from sqlspec import SQLSpec
from sqlspec.adapters.aiosqlite import AiosqliteConfig
from sqlspec.extensions.sanic import SQLSpecPlugin

sqlspec = SQLSpec()
sqlspec.add_config(
AiosqliteConfig(
connection_config={"database": "app.db"},
extension_config={
"sanic": {"commit_mode": "autocommit", "extra_rollback_statuses": {409}, "session_key": "db"}
},
)
)

db_plugin = SQLSpecPlugin(sqlspec)
app = Sanic("SQLSpecSanicCommitModesExample")

@app.post("/users")
async def create_user(request: Request) -> HTTPResponse:
db = db_plugin.get_session(request, "db")
await db.execute("insert into users (name) values (:name)", {"name": request.json["name"]})
return response.json({"created": True}, status=201)

db_plugin.init_app(app)
# end-example

assert app is not None
37 changes: 37 additions & 0 deletions docs/examples/frameworks/sanic/disable_di.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pytest

__all__ = ("test_sanic_disable_di",)


def test_sanic_disable_di() -> None:
pytest.importorskip("sanic")
pytest.importorskip("aiosqlite")
# start-example
from sanic import Request, Sanic, response
from sanic.response import HTTPResponse

from sqlspec import SQLSpec
from sqlspec.adapters.aiosqlite import AiosqliteConfig
from sqlspec.extensions.sanic import SQLSpecPlugin

sqlspec = SQLSpec()
config = AiosqliteConfig(
connection_config={"database": "app.db"},
extension_config={"sanic": {"disable_di": True, "pool_key": "db_pool"}},
)
sqlspec.add_config(config)

db_plugin = SQLSpecPlugin(sqlspec)
app = Sanic("SQLSpecSanicDisableDIExample")

@app.get("/health")
async def health(request: Request) -> HTTPResponse:
async with config.provide_connection(request.app.ctx.db_pool) as connection:
db = config.driver_type(connection=connection, statement_config=config.statement_config)
result = await db.execute("select 1 as ok")
return response.json(result.one())

db_plugin.init_app(app)
# end-example

assert app is not None
60 changes: 60 additions & 0 deletions docs/examples/frameworks/sanic/multi_database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import pytest

__all__ = ("test_sanic_multi_database",)


def test_sanic_multi_database() -> None:
pytest.importorskip("sanic")
pytest.importorskip("aiosqlite")
# start-example
from sanic import Request, Sanic, response
from sanic.response import HTTPResponse

from sqlspec import SQLSpec
from sqlspec.adapters.aiosqlite import AiosqliteConfig, AiosqliteDriver
from sqlspec.adapters.sqlite import SqliteConfig, SqliteDriver
from sqlspec.extensions.sanic import SQLSpecPlugin

sqlspec = SQLSpec()
sqlspec.add_config(
AiosqliteConfig(
bind_key="primary",
connection_config={"database": "primary.db"},
extension_config={
"sanic": {
"connection_key": "primary_connection",
"pool_key": "primary_pool",
"session_key": "primary_db",
}
},
)
)
sqlspec.add_config(
SqliteConfig(
bind_key="analytics",
connection_config={"database": "analytics.db"},
extension_config={
"sanic": {
"connection_key": "analytics_connection",
"pool_key": "analytics_pool",
"session_key": "analytics_db",
}
},
)
)

db_plugin = SQLSpecPlugin(sqlspec)
app = Sanic("SQLSpecSanicMultiDatabaseExample")

@app.get("/report")
async def report(request: Request) -> HTTPResponse:
primary: AiosqliteDriver = db_plugin.get_session(request, "primary_db")
analytics: SqliteDriver = db_plugin.get_session(request, "analytics_db")
users = await primary.select("select 1 as id, 'Alice' as name")
metrics = analytics.select("select 'active_users' as name, 100 as value")
return response.json({"users": users, "metrics": metrics})

db_plugin.init_app(app)
# end-example

assert app is not None
43 changes: 43 additions & 0 deletions docs/examples/frameworks/sanic/observability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pytest

__all__ = ("test_sanic_observability",)


def test_sanic_observability() -> None:
pytest.importorskip("sanic")
pytest.importorskip("aiosqlite")
# start-example
from sanic import Request, Sanic, response
from sanic.response import HTTPResponse

from sqlspec import SQLSpec
from sqlspec.adapters.aiosqlite import AiosqliteConfig
from sqlspec.core import StatementConfig
from sqlspec.extensions.sanic import SQLSpecPlugin

sqlspec = SQLSpec()
sqlspec.add_config(
AiosqliteConfig(
connection_config={"database": "app.db"},
statement_config=StatementConfig(enable_sqlcommenter=True),
extension_config={
"sanic": {
"enable_correlation_middleware": True,
"enable_sqlcommenter_middleware": True,
"session_key": "db",
}
},
)
)

db_plugin = SQLSpecPlugin(sqlspec)
app = Sanic("SQLSpecSanicObservabilityExample")

@app.get("/health")
async def health(request: Request) -> HTTPResponse:
return response.json({"correlation_id": request.ctx.correlation_id})

db_plugin.init_app(app)
# end-example

assert app is not None
2 changes: 1 addition & 1 deletion docs/examples/patterns/filter_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def show_filter_dependencies() -> None:
user_filters: FilterConfig = {
"id_filter": str, # Filter by user IDs
"id_field": "id", # Column name for ID filter
"sort_field": "created_at", # Default sort column
"sort_field": ["created_at", "name"], # Allowed sort columns
"sort_order": "desc", # Default sort direction
"pagination_type": "limit_offset", # Enable pagination
"pagination_size": 20, # Default page size
Expand Down
2 changes: 1 addition & 1 deletion docs/recipes/service_layer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ forwarded through the service to the driver:
dependencies = create_filter_dependencies({
"pagination_type": "limit_offset",
"pagination_size": 20,
"sort_field": "created_at",
"sort_field": ["created_at", "name"],
"sort_order": "desc",
"search": "name,email",
})
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/extensions/fastapi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
FastAPI
======

FastAPI integration extending the Starlette plugin with dependency injection
helpers for FastAPI's ``Depends()`` system, including filter dependency builders.
FastAPI integration with dependency injection helpers for FastAPI's ``Depends()``
system, including filter dependency builders.

Plugin
======
Expand Down
9 changes: 8 additions & 1 deletion docs/reference/extensions/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Extensions
==========

SQLSpec extensions integrate the registry with frameworks, event systems,
and services such as Litestar, FastAPI, Flask, Starlette, and Google ADK.
and services such as Litestar, FastAPI, Flask, Sanic, Starlette, and Google ADK.

.. grid:: 2

Expand Down Expand Up @@ -31,6 +31,12 @@ and services such as Litestar, FastAPI, Flask, Starlette, and Google ADK.

Middleware-based session management and connection pooling lifecycle.

.. grid-item-card:: Sanic
:link: sanic
:link-type: doc

App/request context integration with lifecycle listeners and middleware.

.. grid-item-card:: Google ADK
:link: adk
:link-type: doc
Expand All @@ -49,6 +55,7 @@ and services such as Litestar, FastAPI, Flask, Starlette, and Google ADK.
litestar
fastapi
flask
sanic
starlette
adk
events
28 changes: 28 additions & 0 deletions docs/reference/extensions/sanic.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
=====
Sanic
=====

Sanic extension providing app/request context integration, request-scoped
session management, transaction handling, correlation IDs, SQLCommenter, and
connection pool lifecycle management.

Plugin
======

.. autoclass:: sqlspec.extensions.sanic.SQLSpecPlugin
:members:
:show-inheritance:

State
=====

.. autoclass:: sqlspec.extensions.sanic.SanicConfigState
:members:
:show-inheritance:

Helpers
=======

.. autofunction:: sqlspec.extensions.sanic.get_connection_from_request

.. autofunction:: sqlspec.extensions.sanic.get_or_create_session
4 changes: 2 additions & 2 deletions docs/usage/filtering.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Using filters in a Litestar handler:
user_filter_deps = create_filter_dependencies({
"pagination_type": "limit_offset",
"pagination_size": 20,
"sort_field": "created_at",
"sort_field": ["created_at", "name"],
"sort_order": "desc",
"search": "name,email",
})
Expand All @@ -73,7 +73,7 @@ Using filters in a Litestar handler:
data, total = await db_session.select_with_total(query, *filters)
return {"data": data, "total": total}

The generated dependencies automatically handle query parameters like
The generated dependencies automatically handle query parameters for configured fields like
``?currentPage=2&pageSize=10&searchString=alice&orderBy=name&sortOrder=asc``.

Related Guides
Expand Down
Loading
Loading