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 Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ install-uv: ## Install latest version of
.PHONY: install
install: destroy clean ## Install the project, dependencies, and pre-commit
@echo "${INFO} Starting fresh installation..."
@uv python pin 3.12 >/dev/null 2>&1
@uv python pin 3.10 >/dev/null 2>&1
@uv venv >/dev/null 2>&1
@uv sync --all-extras --dev
@echo "${OK} Installation complete! 🎉"

.PHONY: install-compiled
install-compiled: destroy clean ## Install with mypyc compilation for performance
@echo "${INFO} Starting fresh installation with mypyc compilation..."
@uv python pin 3.12 >/dev/null 2>&1
@uv python pin 3.10 >/dev/null 2>&1
@uv venv >/dev/null 2>&1
@echo "${INFO} Installing in editable mode with mypyc compilation..."
@HATCH_BUILD_HOOKS_ENABLE=1 uv pip install -e .
Expand Down
27 changes: 13 additions & 14 deletions docs/examples/litestar_asyncpg.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@

from litestar import Litestar, get

from sqlspec import SQL
from sqlspec import SQLSpec
from sqlspec.adapters.asyncpg import AsyncpgConfig, AsyncpgDriver, AsyncpgPoolConfig
from sqlspec.extensions.litestar import DatabaseConfig, SQLSpec
from sqlspec.core.statement import SQL
from sqlspec.extensions.litestar import SQLSpecPlugin


@get("/")
Expand Down Expand Up @@ -70,19 +71,17 @@ async def get_status() -> dict[str, str]:

# Configure SQLSpec with AsyncPG
# Note: Modify this DSN to match your database configuration
sqlspec = SQLSpec(
config=[
DatabaseConfig(
config=AsyncpgConfig(
pool_config=AsyncpgPoolConfig(
dsn="postgresql://postgres:postgres@localhost:5433/postgres", min_size=5, max_size=5
)
),
commit_mode="autocommit",
)
]
sql = SQLSpec()
sql.add_config(
AsyncpgConfig(
pool_config=AsyncpgPoolConfig(
dsn="postgresql://postgres:postgres@localhost:5433/postgres", min_size=5, max_size=5
),
extension_config={"litestar": {"commit_mode": "autocommit"}},
)
)
app = Litestar(route_handlers=[hello_world, get_version, list_tables, get_status], plugins=[sqlspec], debug=True)
plugin = SQLSpecPlugin(sqlspec=sql)
app = Litestar(route_handlers=[hello_world, get_version, list_tables, get_status], plugins=[plugin], debug=True)

if __name__ == "__main__":
import os
Expand Down
11 changes: 7 additions & 4 deletions docs/examples/litestar_duckllm.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
from litestar import Litestar, post
from msgspec import Struct

from sqlspec import SQLSpec
from sqlspec.adapters.duckdb import DuckDBConfig, DuckDBDriver
from sqlspec.extensions.litestar import SQLSpec
from sqlspec.extensions.litestar import SQLSpecPlugin


class ChatMessage(Struct):
Expand All @@ -30,8 +31,9 @@ def duckllm_chat(db_session: DuckDBDriver, data: ChatMessage) -> ChatMessage:
return db_session.to_schema(results or {"message": "No response from DuckLLM"}, schema_type=ChatMessage)


sqlspec = SQLSpec(
config=DuckDBConfig(
sql = SQLSpec()
sql.add_config(
DuckDBConfig(
driver_features={
"extensions": [{"name": "open_prompt"}],
"secrets": [
Expand All @@ -48,7 +50,8 @@ def duckllm_chat(db_session: DuckDBDriver, data: ChatMessage) -> ChatMessage:
}
)
)
app = Litestar(route_handlers=[duckllm_chat], plugins=[sqlspec], debug=True)
plugin = SQLSpecPlugin(sqlspec=sql)
app = Litestar(route_handlers=[duckllm_chat], plugins=[plugin], debug=True)

if __name__ == "__main__":
import uvicorn
Expand Down
30 changes: 14 additions & 16 deletions docs/examples/litestar_multi_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@

from litestar import Litestar, get

from sqlspec import SQLSpec
from sqlspec.adapters.aiosqlite import AiosqliteConfig, AiosqliteDriver
from sqlspec.adapters.duckdb import DuckDBConfig, DuckDBDriver
from sqlspec.core.statement import SQL
from sqlspec.extensions.litestar import DatabaseConfig, SQLSpec
from sqlspec.extensions.litestar import SQLSpecPlugin


@get("/test", sync_to_thread=True)
Expand All @@ -35,22 +36,19 @@ async def simple_sqlite(db_session: AiosqliteDriver) -> dict[str, str]:
return {"greeting": greeting["greeting"] if greeting is not None else "hi"}


sqlspec = SQLSpec(
config=[
DatabaseConfig(config=AiosqliteConfig(), commit_mode="autocommit"),
DatabaseConfig(
config=DuckDBConfig(
driver_features={
"extensions": [{"name": "vss", "force_install": True}],
"secrets": [{"secret_type": "s3", "name": "s3_secret", "value": {"key_id": "abcd"}}],
}
),
connection_key="etl_connection",
session_key="etl_session",
),
]
sql = SQLSpec()
sql.add_config(AiosqliteConfig(extension_config={"litestar": {"commit_mode": "autocommit"}}))
sql.add_config(
DuckDBConfig(
driver_features={
"extensions": [{"name": "vss", "force_install": True}],
"secrets": [{"secret_type": "s3", "name": "s3_secret", "value": {"key_id": "abcd"}}],
},
extension_config={"litestar": {"connection_key": "etl_connection", "session_key": "etl_session"}},
)
)
app = Litestar(route_handlers=[simple_sqlite, simple_select], plugins=[sqlspec])
plugin = SQLSpecPlugin(sqlspec=sql)
app = Litestar(route_handlers=[simple_sqlite, simple_select], plugins=[plugin])

if __name__ == "__main__":
import os
Expand Down
24 changes: 11 additions & 13 deletions docs/examples/litestar_psycopg.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,27 @@

from litestar import Litestar, get

from sqlspec import SQLSpec
from sqlspec.adapters.psycopg import PsycopgAsyncConfig, PsycopgAsyncDriver
from sqlspec.extensions.litestar import DatabaseConfig, SQLSpec
from sqlspec.core.statement import SQL
from sqlspec.extensions.litestar import SQLSpecPlugin


@get("/")
async def simple_psycopg(db_session: PsycopgAsyncDriver) -> dict[str, str]:
from sqlspec.core.statement import SQL

result = await db_session.execute(SQL("SELECT 'Hello, world!' AS greeting"))
return result.get_first() or {"greeting": "No result found"}


sqlspec = SQLSpec(
config=[
DatabaseConfig(
config=PsycopgAsyncConfig(
pool_config={"conninfo": "postgres://app:app@localhost:15432/app", "min_size": 1, "max_size": 3}
),
commit_mode="autocommit",
)
]
sql = SQLSpec()
sql.add_config(
PsycopgAsyncConfig(
pool_config={"conninfo": "postgres://app:app@localhost:15432/app", "min_size": 1, "max_size": 3},
extension_config={"litestar": {"commit_mode": "autocommit"}},
)
)
app = Litestar(route_handlers=[simple_psycopg], plugins=[sqlspec])
plugin = SQLSpecPlugin(sqlspec=sql)
app = Litestar(route_handlers=[simple_psycopg], plugins=[plugin])

if __name__ == "__main__":
import os
Expand Down
9 changes: 6 additions & 3 deletions docs/examples/litestar_single_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
from aiosqlite import Connection
from litestar import Litestar, get

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


@get("/")
Expand All @@ -23,5 +24,7 @@ async def simple_sqlite(db_connection: Connection) -> dict[str, str]:
return {"greeting": next(iter(result))[0]}


sqlspec = SQLSpec(config=AiosqliteConfig())
app = Litestar(route_handlers=[simple_sqlite], plugins=[sqlspec])
sql = SQLSpec()
sql.add_config(AiosqliteConfig())
plugin = SQLSpecPlugin(sqlspec=sql)
app = Litestar(route_handlers=[simple_sqlite], plugins=[plugin])
9 changes: 9 additions & 0 deletions sqlspec/adapters/aiosqlite/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ def __init__(
if "database" not in config_dict or config_dict["database"] == ":memory:":
config_dict["database"] = "file::memory:?cache=shared"
config_dict["uri"] = True
elif "database" in config_dict:
database_path = str(config_dict["database"])
if database_path.startswith("file:") and not config_dict.get("uri"):
logger.debug(
"Database URI detected (%s) but uri=True not set. "
"Auto-enabling URI mode to prevent physical file creation.",
database_path,
)
config_dict["uri"] = True

super().__init__(
pool_config=config_dict,
Expand Down
12 changes: 12 additions & 0 deletions sqlspec/adapters/sqlite/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""SQLite database configuration with thread-local connections."""

import logging
import uuid
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
Expand All @@ -11,6 +12,8 @@
from sqlspec.adapters.sqlite.pool import SqliteConnectionPool
from sqlspec.config import SyncDatabaseConfig

logger = logging.getLogger(__name__)

if TYPE_CHECKING:
from collections.abc import Generator

Expand Down Expand Up @@ -64,6 +67,15 @@ def __init__(
if "database" not in pool_config or pool_config["database"] == ":memory:":
pool_config["database"] = f"file:memory_{uuid.uuid4().hex}?mode=memory&cache=private"
pool_config["uri"] = True
elif "database" in pool_config:
database_path = str(pool_config["database"])
if database_path.startswith("file:") and not pool_config.get("uri"):
logger.debug(
"Database URI detected (%s) but uri=True not set. "
"Auto-enabling URI mode to prevent physical file creation.",
database_path,
)
pool_config["uri"] = True

super().__init__(
bind_key=bind_key,
Expand Down
9 changes: 9 additions & 0 deletions sqlspec/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,15 @@ def get_config(
logger.debug("Retrieved configuration: %s", self._get_config_name(name))
return config

@property
def configs(self) -> "dict[type, DatabaseConfigProtocol[Any, Any, Any]]":
"""Access the registry of database configurations.

Returns:
Dictionary mapping config types to config instances.
"""
return self._configs

@overload
def get_connection(
self,
Expand Down
24 changes: 5 additions & 19 deletions sqlspec/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,6 @@ def get_config_by_bind_key(
console.print(f"[red]No config found for bind key: {bind_key}[/]")
sys.exit(1)

# Extract the actual config from DatabaseConfig wrapper if needed
from sqlspec.extensions.litestar.config import DatabaseConfig

if isinstance(config, DatabaseConfig):
config = config.config

return cast("AsyncDatabaseConfig[Any, Any, Any] | SyncDatabaseConfig[Any, Any, Any]", config)

def get_configs_with_migrations(ctx: "click.Context", enabled_only: bool = False) -> "list[tuple[str, Any]]":
Expand All @@ -195,18 +189,13 @@ def get_configs_with_migrations(ctx: "click.Context", enabled_only: bool = False
configs = ctx.obj["configs"]
migration_configs = []

from sqlspec.extensions.litestar.config import DatabaseConfig

for config in configs:
# Extract the actual config from DatabaseConfig wrapper if needed
actual_config = config.config if isinstance(config, DatabaseConfig) else config

migration_config = actual_config.migration_config
migration_config = config.migration_config
if migration_config:
enabled = migration_config.get("enabled", True)
if not enabled_only or enabled:
config_name = actual_config.bind_key or str(type(actual_config).__name__)
migration_configs.append((config_name, actual_config))
config_name = config.bind_key or str(type(config).__name__)
migration_configs.append((config_name, config))

return migration_configs

Expand Down Expand Up @@ -506,7 +495,6 @@ def init_sqlspec( # pyright: ignore[reportUnusedFunction]
"""Initialize the database migrations."""
from rich.prompt import Confirm

from sqlspec.extensions.litestar.config import DatabaseConfig
from sqlspec.migrations.commands import create_migration_commands
from sqlspec.utils.sync_tools import run_

Expand All @@ -527,13 +515,11 @@ async def _init_sqlspec() -> None:
)

for config in configs:
# Extract the actual config from DatabaseConfig wrapper if needed
actual_config = config.config if isinstance(config, DatabaseConfig) else config
migration_config = getattr(actual_config, "migration_config", {})
migration_config = getattr(config, "migration_config", {})
target_directory = (
migration_config.get("script_location", "migrations") if directory is None else directory
)
migration_commands = create_migration_commands(config=actual_config)
migration_commands = create_migration_commands(config=config)
await maybe_await(migration_commands.init(directory=cast("str", target_directory), package=package))

run_(_init_sqlspec)()
Expand Down
21 changes: 17 additions & 4 deletions sqlspec/extensions/litestar/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
from sqlspec.extensions.litestar import handlers, providers
from sqlspec.extensions.litestar.cli import database_group
from sqlspec.extensions.litestar.config import DatabaseConfig
from sqlspec.extensions.litestar.plugin import SQLSpec
from sqlspec.extensions.litestar.plugin import (
DEFAULT_COMMIT_MODE,
DEFAULT_CONNECTION_KEY,
DEFAULT_POOL_KEY,
DEFAULT_SESSION_KEY,
CommitMode,
SQLSpecPlugin,
)

__all__ = ("DatabaseConfig", "SQLSpec", "database_group", "handlers", "providers")
__all__ = (
"DEFAULT_COMMIT_MODE",
"DEFAULT_CONNECTION_KEY",
"DEFAULT_POOL_KEY",
"DEFAULT_SESSION_KEY",
"CommitMode",
"SQLSpecPlugin",
"database_group",
)
8 changes: 4 additions & 4 deletions sqlspec/extensions/litestar/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
if TYPE_CHECKING:
from litestar import Litestar

from sqlspec.extensions.litestar.plugin import SQLSpec
from sqlspec.extensions.litestar.plugin import SQLSpecPlugin


def get_database_migration_plugin(app: "Litestar") -> "SQLSpec":
def get_database_migration_plugin(app: "Litestar") -> "SQLSpecPlugin":
"""Retrieve the SQLSpec plugin from the Litestar application's plugins.

Args:
Expand All @@ -31,10 +31,10 @@ def get_database_migration_plugin(app: "Litestar") -> "SQLSpec":
ImproperConfigurationError: If the SQLSpec plugin is not found
"""
from sqlspec.exceptions import ImproperConfigurationError
from sqlspec.extensions.litestar.plugin import SQLSpec
from sqlspec.extensions.litestar.plugin import SQLSpecPlugin

with suppress(KeyError):
return app.plugins.get(SQLSpec)
return app.plugins.get(SQLSpecPlugin)
msg = "Failed to initialize database migrations. The required SQLSpec plugin is missing."
raise ImproperConfigurationError(msg)

Expand Down
Loading