Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cbd7023
docs: externalise migration CLI and add psycopg literalinclude
euri10 Nov 4, 2025
31cbf31
put import in tests and correct the lines accordingly
euri10 Nov 4, 2025
5dcc426
lint
euri10 Nov 4, 2025
da7a9ad
more correct lines
euri10 Nov 4, 2025
0ff5c15
delete
euri10 Nov 4, 2025
2d28f08
Revert "delete"
euri10 Nov 4, 2025
2ca1def
add leftovers
euri10 Nov 4, 2025
a6256a6
fix tests, one not passing when using provide_session by bind_key
euri10 Nov 5, 2025
b85c152
linter issues
euri10 Nov 5, 2025
642816a
12
euri10 Nov 5, 2025
5dd81fa
one test per block
euri10 Nov 5, 2025
27364bb
Merge branch 'main' into 173_usage_configuration
euri10 Nov 5, 2025
9c7c689
wrong psygopg note, replaced by correct config
euri10 Nov 5, 2025
e0b1082
Merge branch '173_usage_configuration' of github.com:euri10/sqlspec i…
euri10 Nov 5, 2025
cdca6b3
Merge branch 'main' into 173_usage_configuration
cofin Nov 5, 2025
a65c6cb
Merge branch 'main' into 173_usage_configuration
cofin Nov 5, 2025
f6d748f
Update docs/examples/usage/test_configuration_12.py
euri10 Nov 7, 2025
fffec01
Update docs/examples/usage/test_configuration_13.py
euri10 Nov 7, 2025
91c6d0e
Update docs/examples/usage/test_configuration_19.py
euri10 Nov 7, 2025
6aec1d2
Merge branch 'main' into 173_usage_configuration
cofin Nov 10, 2025
5c5d60d
Merge branch 'main' into 173_usage_configuration
cofin Nov 10, 2025
8d2c24c
Refactor example configurations and tests
cofin Nov 10, 2025
4629000
ruff
euri10 Nov 10, 2025
05a1b8f
Revert "ruff"
euri10 Nov 10, 2025
c254d32
ruff
euri10 Nov 10, 2025
93154b9
make fix
euri10 Nov 10, 2025
2fcd5c1
feat: oracle update
cofin Nov 10, 2025
93250d4
chore: update package versions for pydantic-settings, shibuya, and sp…
cofin Nov 10, 2025
8d7f4e8
Merge branch 'main' into 173_usage_configuration
euri10 Nov 11, 2025
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
26 changes: 26 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,24 @@ When processing user input that may be incomplete or malformed, use a two-tier a

**Implementation Pattern:**

## Recent edit: Configuration examples (2025-11-04)

- Updated docs/examples/usage examples: consolidated example filenames to
docs/examples/usage/test_configuration_*.py and ensured the documentation
references match the renamed examples.
- Added an explicit :lines: range and a :dedent: directive to the
literalinclude for test_configuration_23.py so Sphinx renders the snippet
with correct indentation.
- Built the Sphinx documentation (make docs) and verified HTML output was
generated successfully. Two minor warnings were reported (dedent and a
missing stylesheet copy) but they did not prevent the build.
- Updated project TODOs to reflect completed steps.

This summary documents the small documentation and example maintenance
performed on the configuration usage guide and can be expanded into a
longer changelog entry if desired.


```python
def parse_user_input(content: str, source: str) -> "dict[str, Result]":
"""Parse user input with two-tier error handling.
Expand Down Expand Up @@ -2025,6 +2043,14 @@ class GoodDriverFeatures(TypedDict):

### Compliance Table

### Change log: configuration examples

- Renamed documentation example references to use docs/examples/usage/test_configuration_*.py
- Added explicit :lines: ranges and :dedent: directive for the literalinclude at the top of docs/usage/configuration.rst
- Rebuilt documentation to verify the changes (make docs). Build completed with 2 warnings about dedent and a missing stylesheet; output HTML written to docs/_build/html

### Compliance Table

Current state of all adapters (as of type-cleanup branch):

| Adapter | TypedDict | Auto-Detect | enable_ Prefix | Defaults | Grade | Notes |
Expand Down
Empty file added docs/examples/usage/__init__.py
Empty file.
14 changes: 14 additions & 0 deletions docs/examples/usage/test_configuration_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
def test_sqlite_memory_db() -> None:
from sqlspec import SQLSpec
from sqlspec.adapters.sqlite import SqliteConfig

# Create SQLSpec instance
spec = SQLSpec()

# Add database configuration
db = spec.add_config(SqliteConfig(pool_config={"database": ":memory:"}))

# Use the database
with spec.provide_session(db) as session:
result = session.execute("SELECT 1")
assert result[0] == {"1": 1}
9 changes: 9 additions & 0 deletions docs/examples/usage/test_configuration_10.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
POOL_INSTANCE = 20


def test_manual_pool() -> None:
from sqlspec.adapters.asyncpg import AsyncpgConfig

pool = {"dsn": "postgresql://localhost/db", "min_size": 10, "max_size": POOL_INSTANCE}
db = AsyncpgConfig(pool_instance=pool)
assert db.pool_instance["max_size"] == POOL_INSTANCE
5 changes: 5 additions & 0 deletions docs/examples/usage/test_configuration_11.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def test_thread_local_connections() -> None:
from sqlspec.adapters.sqlite import SqliteConfig

config = SqliteConfig(pool_config={"database": "test.db"})
assert config.pool_config["database"] == "test.db"
16 changes: 16 additions & 0 deletions docs/examples/usage/test_configuration_12.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
def test_basic_statement_config() -> None:
from sqlspec.adapters.asyncpg import AsyncpgConfig
from sqlspec.core.statement import StatementConfig

statement_config = StatementConfig(
dialect="postgres", # SQLGlot dialect
enable_parsing=True, # Parse SQL into AST
enable_validation=True, # Run security/performance validators
enable_transformations=True, # Apply AST transformations
enable_caching=True, # Enable multi-tier caching
)

# Apply to adapter
config = AsyncpgConfig(pool_config={"dsn": "postgresql://localhost/db"}, statement_config=statement_config)
assert config.statement_config.dialect == "postgres"
assert config.statement_config.enable_parsing is True
16 changes: 16 additions & 0 deletions docs/examples/usage/test_configuration_13.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
def test_parameter_style_config() -> None:
from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
from sqlspec.core.statement import StatementConfig

param_config = ParameterStyleConfig(
default_parameter_style=ParameterStyle.NUMERIC, # $1, $2, ...
supported_parameter_styles={
ParameterStyle.NUMERIC,
ParameterStyle.NAMED_COLON, # :name
},
has_native_list_expansion=False,
needs_static_script_compilation=False,
)

statement_config = StatementConfig(dialect="postgres", parameter_config=param_config)
assert statement_config.parameter_config.default_parameter_style == ParameterStyle.NUMERIC
19 changes: 19 additions & 0 deletions docs/examples/usage/test_configuration_14.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
def test_parameter_styles() -> None:
from sqlspec.core.parameters import ParameterStyle

# Question mark (SQLite, DuckDB)
qmark = ParameterStyle.QMARK # WHERE id = ?

# Numeric (PostgreSQL, asyncpg)
numeric = ParameterStyle.NUMERIC # WHERE id = $1

# Named colon (Oracle, SQLite)
named_colon = ParameterStyle.NAMED_COLON # WHERE id = :id

# Named at (BigQuery)

# Format/pyformat (psycopg, MySQL)

assert qmark == ParameterStyle.QMARK
assert numeric == ParameterStyle.NUMERIC
assert named_colon == ParameterStyle.NAMED_COLON
23 changes: 23 additions & 0 deletions docs/examples/usage/test_configuration_15.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Test configuration example: Global cache configuration."""

SQL_CACHE_SIZE = 1000


def test_global_cache_config() -> None:
"""Test global cache configuration."""
from sqlspec.core.cache import CacheConfig, update_cache_config

cache_config = CacheConfig(
compiled_cache_enabled=True, # Cache compiled SQL
sql_cache_enabled=True, # Cache SQL strings
fragment_cache_enabled=True, # Cache SQL fragments
optimized_cache_enabled=True, # Cache optimized AST
sql_cache_size=SQL_CACHE_SIZE, # Maximum cached SQL items
)

# Update global cache configuration
update_cache_config(cache_config)

# Verify config applied
assert cache_config.sql_cache_enabled is True
assert cache_config.sql_cache_size == SQL_CACHE_SIZE
23 changes: 23 additions & 0 deletions docs/examples/usage/test_configuration_16.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Test configuration example: Per-instance cache configuration."""


def test_per_instance_cache_config() -> None:
"""Test per-instance cache configuration."""
import tempfile

from sqlspec import SQLSpec
from sqlspec.adapters.sqlite import SqliteConfig
from sqlspec.core.cache import CacheConfig

with tempfile.NamedTemporaryFile(suffix=".db", delete=True) as tmp:
# Configure cache for specific SQLSpec instance
spec = SQLSpec()
spec.update_cache_config(CacheConfig(sql_cache_enabled=True, sql_cache_size=500))

# Add database config
db = spec.add_config(SqliteConfig(pool_config={"database": tmp.name}))

# Use the configured spec
with spec.provide_session(db) as session:
result = session.execute("SELECT 1")
assert result is not None
27 changes: 27 additions & 0 deletions docs/examples/usage/test_configuration_17.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Test configuration example: Cache statistics tracking."""


def test_cache_statistics() -> None:
"""Test cache statistics tracking."""
import tempfile

from sqlspec import SQLSpec
from sqlspec.adapters.sqlite import SqliteConfig
from sqlspec.core.cache import get_cache_statistics, log_cache_stats

with tempfile.NamedTemporaryFile(suffix=".db", delete=True) as tmp:
spec = SQLSpec()
db = spec.add_config(SqliteConfig(pool_config={"database": tmp.name}))

# Execute some queries to generate cache activity
with spec.provide_session(db) as session:
session.execute("SELECT 1")
session.execute("SELECT 1") # Should hit cache

# Get statistics
stats = get_cache_statistics()
assert isinstance(stats, dict)
assert "multi_level" in stats

# Log statistics (logs to configured logger)
log_cache_stats()
18 changes: 18 additions & 0 deletions docs/examples/usage/test_configuration_18.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Test configuration example: Cache clearing operations."""


def test_clear_cache() -> None:
"""Test cache clearing operations."""
from sqlspec.core.cache import clear_all_caches, get_cache_statistics

# Get initial statistics
stats_before = get_cache_statistics()
assert isinstance(stats_before, dict)

# Clear all caches and reset statistics
clear_all_caches()

# Verify caches were cleared
stats_after = get_cache_statistics()
assert isinstance(stats_after, dict)
assert "multi_level" in stats_after
24 changes: 24 additions & 0 deletions docs/examples/usage/test_configuration_19.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Test configuration example: Binding multiple database configurations."""


def test_binding_multiple_configs() -> None:
"""Test binding multiple database configurations."""
import tempfile

from sqlspec import SQLSpec
from sqlspec.adapters.asyncpg import AsyncpgConfig
from sqlspec.adapters.sqlite import SqliteConfig

with tempfile.NamedTemporaryFile(suffix=".db", delete=True) as tmp:
spec = SQLSpec()

# Add multiple configurations
sqlite_db = spec.add_config(SqliteConfig(pool_config={"database": tmp.name}))
spec.add_config(AsyncpgConfig(pool_config={"dsn": "postgresql://..."}))

# Use specific configuration
with spec.provide_session(sqlite_db) as session:
session.execute("SELECT 1")

assert spec.configs[SqliteConfig].pool_config["database"] == tmp.name
assert spec.configs[AsyncpgConfig].pool_config["dsn"] == "postgresql://..."
13 changes: 13 additions & 0 deletions docs/examples/usage/test_configuration_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
def test_sqlite_config_setup() -> None:
from sqlspec.adapters.sqlite import SqliteConfig

config = SqliteConfig(
pool_config={
"database": "myapp.db", # Database file path
"timeout": 5.0, # Lock timeout in seconds
"check_same_thread": False, # Allow multi-thread access
"cached_statements": 100, # Statement cache size
"uri": False, # Enable URI mode
}
)
assert config.pool_config["database"] == "myapp.db"
24 changes: 24 additions & 0 deletions docs/examples/usage/test_configuration_20.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Test configuration example: Named database bindings."""


def test_named_bindings() -> None:
"""Test named database bindings."""
import tempfile

from sqlspec import SQLSpec
from sqlspec.adapters.asyncpg import AsyncpgConfig
from sqlspec.adapters.sqlite import SqliteConfig

with tempfile.NamedTemporaryFile(suffix=".db", delete=True) as tmp:
spec = SQLSpec()

# Add with bind keys
spec.add_config(SqliteConfig(pool_config={"database": tmp.name}, bind_key="cache_db"))
spec.add_config(AsyncpgConfig(pool_config={"dsn": "postgresql://..."}, bind_key="main_db"))

# Access by bind key
with spec.provide_session("cache_db") as session:
session.execute("SELECT 1")

assert spec.configs[SqliteConfig].pool_config["database"] == tmp.name
assert spec.configs[AsyncpgConfig].pool_config["dsn"] == "postgresql://..."
26 changes: 26 additions & 0 deletions docs/examples/usage/test_configuration_21.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Test configuration example: Basic migration configuration."""

import pytest


@pytest.mark.skipif(
not pytest.importorskip("asyncpg", reason="AsyncPG not installed"), reason="AsyncPG integration tests disabled"
)
def test_basic_migration_config() -> None:
"""Test basic migration configuration."""
from sqlspec.adapters.asyncpg import AsyncpgConfig

config = AsyncpgConfig(
pool_config={"dsn": "postgresql://localhost/db"},
extension_config={
"litestar": {"session_table": "custom_sessions"} # Extension settings
},
migration_config={
"script_location": "migrations", # Migration directory
"version_table": "alembic_version", # Version tracking table
"include_extensions": ["litestar"], # Simple string list only
},
)

assert config.migration_config["script_location"] == "migrations"
assert "litestar" in config.migration_config["include_extensions"]
16 changes: 16 additions & 0 deletions docs/examples/usage/test_configuration_22.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
def test_basic_migration_config() -> None:
from sqlspec.adapters.asyncpg import AsyncpgConfig

config = AsyncpgConfig(
pool_config={"dsn": "postgresql://localhost/db"},
extension_config={
"litestar": {"session_table": "custom_sessions"} # Extension settings
},
migration_config={
"script_location": "migrations", # Migration directory
"version_table": "alembic_version", # Version tracking table
"include_extensions": ["litestar"], # Simple string list only
},
)
assert config.migration_config["script_location"] == "migrations"
assert "litestar" in config.migration_config["include_extensions"]
27 changes: 27 additions & 0 deletions docs/examples/usage/test_configuration_23.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Test configuration example: Environment-based configuration."""


def test_extension_config() -> None:
from sqlspec.adapters.asyncpg import AsyncpgConfig

config = AsyncpgConfig(
pool_config={"dsn": "postgresql://localhost/db"},
extension_config={
"litestar": {
"connection_key": "db_connection",
"session_key": "db_session",
"pool_key": "db_pool",
"commit_mode": "autocommit",
"enable_correlation_middleware": True,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@euri10 i meant to bring this up you and @provinzkraut. I've removed it from sqlspec, but I think it needs to be something we include as an official middleware in Litestar. This will let us and others leverage the a correlation ID in their logs. I can add it back here if it's something we don't want in Litestar.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

honestly i dont have any form stand on this

}
},
)
assert config.extension_config == {
"litestar": {
"connection_key": "db_connection",
"session_key": "db_session",
"pool_key": "db_pool",
"commit_mode": "autocommit",
"enable_correlation_middleware": True,
}
}
41 changes: 41 additions & 0 deletions docs/examples/usage/test_configuration_24.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Test configuration example: Environment-based configuration."""

import os
from unittest.mock import patch

import pytest

POSTGRES_PORT = 5433


@pytest.mark.skipif(os.getenv("TEST_ASYNCPG", "0") != "1", reason="AsyncPG integration tests disabled")
def test_environment_based_configuration() -> None:
"""Test environment-based configuration pattern."""

# Mock environment variables
env_vars = {
"DB_HOST": "testhost",
"DB_PORT": "5433",
"DB_USER": "testuser",
"DB_PASSWORD": "testpass",
"DB_NAME": "testdb",
}

with patch.dict(os.environ, env_vars, clear=False):
from sqlspec.adapters.asyncpg import AsyncpgConfig

config = AsyncpgConfig(
pool_config={
"host": os.getenv("DB_HOST", "localhost"),
"port": int(os.getenv("DB_PORT", "5432")),
"user": os.getenv("DB_USER"),
"password": os.getenv("DB_PASSWORD"),
"database": os.getenv("DB_NAME"),
}
)

assert config.pool_config["host"] == "testhost"
assert config.pool_config["port"] == POSTGRES_PORT
assert config.pool_config["user"] == "testuser"
assert config.pool_config["password"] == "testpass"
assert config.pool_config["database"] == "testdb"
Loading
Loading