diff --git a/AGENTS.md b/AGENTS.md index 4f26d18a..1134fa17 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -507,6 +507,23 @@ 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. @@ -2025,6 +2042,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 | diff --git a/docs/examples/quickstart/quickstart_1.py b/docs/examples/quickstart/quickstart_1.py index b19c3d7d..1987e0aa 100644 --- a/docs/examples/quickstart/quickstart_1.py +++ b/docs/examples/quickstart/quickstart_1.py @@ -1,17 +1,17 @@ -from sqlspec import SQLSpec -from sqlspec.adapters.sqlite import SqliteConfig - __all__ = ("test_quickstart_1",) def test_quickstart_1() -> None: - # Create SQLSpec instance and configure database + # start-example + from sqlspec import SQLSpec + from sqlspec.adapters.sqlite import SqliteConfig + db_manager = SQLSpec() db = db_manager.add_config(SqliteConfig(pool_config={"database": ":memory:"})) - # Execute a query with db_manager.provide_session(db) as session: result = session.execute("SELECT 'Hello, SQLSpec!' as message") - print(result.get_first()) # {'message': 'Hello, SQLSpec!'} + print(result.get_first()) + # end-example assert result.get_first() == {"message": "Hello, SQLSpec!"} diff --git a/docs/examples/quickstart/quickstart_2.py b/docs/examples/quickstart/quickstart_2.py index 857a05c5..60fb2ffb 100644 --- a/docs/examples/quickstart/quickstart_2.py +++ b/docs/examples/quickstart/quickstart_2.py @@ -1,44 +1,42 @@ -from sqlspec import SQLSpec -from sqlspec.adapters.sqlite import SqliteConfig - __all__ = ("test_quickstart_2",) def test_quickstart_2() -> None: + # start-example + from sqlspec import SQLSpec + from sqlspec.adapters.sqlite import SqliteConfig + db_manager = SQLSpec() db = db_manager.add_config(SqliteConfig(pool_config={"database": ":memory:"})) with db_manager.provide_session(db) as session: - # Create a table - _ = session.execute(""" + _ = session.execute( + """ CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL ) - """) + """ + ) - # Insert data _ = session.execute("INSERT INTO users (name, email) VALUES (?, ?)", "Alice", "alice@example.com") - # Insert multiple rows _ = session.execute_many( "INSERT INTO users (name, email) VALUES (?, ?)", [("Bob", "bob@example.com"), ("Charlie", "charlie@example.com")], ) - # Query all users users = session.select("SELECT * FROM users") print(f"All users: {users}") - # Query single user - alice = session.select_one("SELECT * FROM users WHERE name = ?", "Alice") + alice = session.select_one_or_none("SELECT * FROM users WHERE name = ?", "Alice") print(f"Alice: {alice}") - # Query scalar value count = session.select_value("SELECT COUNT(*) FROM users") print(f"Total users: {count}") + # end-example - assert len(users) == 3 # noqa: PLR2004 + assert len(users) == 3 assert alice == {"id": 1, "name": "Alice", "email": "alice@example.com"} - assert count == 3 # noqa: PLR2004 + assert count == 3 diff --git a/docs/examples/quickstart/quickstart_3.py b/docs/examples/quickstart/quickstart_3.py index 98f7fe5f..5917342f 100644 --- a/docs/examples/quickstart/quickstart_3.py +++ b/docs/examples/quickstart/quickstart_3.py @@ -1,38 +1,36 @@ -from pydantic import BaseModel +__all__ = ("test_quickstart_3",) -from sqlspec import SQLSpec -from sqlspec.adapters.sqlite import SqliteConfig - -__all__ = ("User",) +def test_quickstart_3() -> None: + # start-example + from pydantic import BaseModel -class User(BaseModel): - id: int - name: str - email: str + from sqlspec import SQLSpec + from sqlspec.adapters.sqlite import SqliteConfig + class User(BaseModel): + id: int + name: str + email: str -def test_quickstart_3() -> None: db_manager = SQLSpec() db = db_manager.add_config(SqliteConfig(pool_config={"database": ":memory:"})) with db_manager.provide_session(db) as session: - # Setup - _ = session.execute(""" + _ = session.execute( + """ CREATE TABLE users (id INTEGER, name TEXT, email TEXT) - """) + """ + ) _ = session.execute("INSERT INTO users VALUES (?, ?, ?)", 1, "Alice", "alice@example.com") - # Type-safe query - returns User instance user = session.select_one("SELECT * FROM users WHERE id = ?", 1, schema_type=User) + print(f"User: {user.name} ({user.email})") - # Now you have type hints and autocomplete! - print(f"User: {user.name} ({user.email})") # IDE knows these fields exist - - # Multiple results all_users = session.select("SELECT * FROM users", schema_type=User) - for u in all_users: - print(f"User: {u.name}") # Each item is a typed User + for typed_user in all_users: + print(f"User: {typed_user.name}") + # end-example assert user == User(id=1, name="Alice", email="alice@example.com") assert len(all_users) == 1 diff --git a/docs/examples/quickstart/quickstart_4.py b/docs/examples/quickstart/quickstart_4.py index 535d9574..d6b0db81 100644 --- a/docs/examples/quickstart/quickstart_4.py +++ b/docs/examples/quickstart/quickstart_4.py @@ -1,36 +1,36 @@ -from pydantic import BaseModel +"""Async quickstart example.""" -from sqlspec import SQLSpec -from sqlspec.adapters.aiosqlite import AiosqliteConfig +import pytest -__all__ = ("User", "test_quickstart_4") +__all__ = ("test_quickstart_4",) -class User(BaseModel): - id: int - name: str - email: str +@pytest.mark.asyncio +async def test_quickstart_4() -> None: + """Demonstrate async SQLSpec usage.""" + # start-example + from pydantic import BaseModel + from sqlspec import SQLSpec + from sqlspec.adapters.aiosqlite import AiosqliteConfig + + class User(BaseModel): + id: int + name: str + email: str -async def test_quickstart_4() -> None: db_manager = SQLSpec() db = db_manager.add_config(AiosqliteConfig(pool_config={"database": ":memory:"})) async with db_manager.provide_session(db) as session: - # Create table - _ = await session.execute(""" + await session.execute( + """ CREATE TABLE users (id INTEGER, name TEXT, email TEXT) - """) - - # Insert data - _ = await session.execute("INSERT INTO users VALUES (?, ?, ?)", 1, "Alice", "alice@example.com") - - # Type-safe async query + """ + ) + await session.execute("INSERT INTO users VALUES (?, ?, ?)", 1, "Alice", "alice@example.com") user = await session.select_one("SELECT * FROM users WHERE id = ?", 1, schema_type=User) - print(f"User: {user.name}") + # end-example assert user == User(id=1, name="Alice", email="alice@example.com") - assert isinstance(user, User) - assert user.name == "Alice" - assert user.email == "alice@example.com" diff --git a/docs/examples/quickstart/quickstart_5.py b/docs/examples/quickstart/quickstart_5.py index 2629df54..4572e55e 100644 --- a/docs/examples/quickstart/quickstart_5.py +++ b/docs/examples/quickstart/quickstart_5.py @@ -1,53 +1,58 @@ -import os -from typing import Any +"""Async PostgreSQL quickstart example.""" -from pydantic import BaseModel +import pytest -from sqlspec import SQLSpec -from sqlspec.adapters.asyncpg import AsyncpgConfig +__all__ = ("test_quickstart_5",) -__all__ = ("User", "test_quickstart_5") - -class User(BaseModel): - id: int - name: str - email: str - - -def _pool_config() -> "dict[str, Any]": - return { - "host": os.getenv("SQLSPEC_QUICKSTART_PG_HOST", "localhost"), - "port": int(os.getenv("SQLSPEC_QUICKSTART_PG_PORT", "5432")), - "user": os.getenv("SQLSPEC_QUICKSTART_PG_USER", "postgres"), - "password": os.getenv("SQLSPEC_QUICKSTART_PG_PASSWORD", "postgres"), - "database": os.getenv("SQLSPEC_QUICKSTART_PG_DATABASE", "mydb"), - } - - -async def _seed_users(session: Any) -> None: - await session.execute( - """ - CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, - name TEXT NOT NULL, - email TEXT NOT NULL +@pytest.mark.asyncio +async def test_quickstart_5() -> None: + """Demonstrate async PostgreSQL usage with SQLSpec.""" + # start-example + import os + from typing import Any + + from pydantic import BaseModel + + from sqlspec import SQLSpec + from sqlspec.adapters.asyncpg import AsyncpgConfig + + class User(BaseModel): + id: int + name: str + email: str + + def pool_config() -> "dict[str, Any]": + return { + "host": os.getenv("SQLSPEC_QUICKSTART_PG_HOST", "localhost"), + "port": int(os.getenv("SQLSPEC_QUICKSTART_PG_PORT", "5432")), + "user": os.getenv("SQLSPEC_QUICKSTART_PG_USER", "postgres"), + "password": os.getenv("SQLSPEC_QUICKSTART_PG_PASSWORD", "postgres"), + "database": os.getenv("SQLSPEC_QUICKSTART_PG_DATABASE", "mydb"), + } + + async def seed_users(session: Any) -> None: + await session.execute( + """ + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT NOT NULL + ) + """ + ) + await session.execute("TRUNCATE TABLE users") + await session.execute( + "INSERT INTO users (id, name, email) VALUES ($1, $2, $3)", 1, "Alice", "alice@example.com" ) - """ - ) - await session.execute("TRUNCATE TABLE users") - await session.execute("INSERT INTO users (id, name, email) VALUES ($1, $2, $3)", 1, "Alice", "alice@example.com") - -async def test_quickstart_5() -> None: db_manager = SQLSpec() - db = db_manager.add_config(AsyncpgConfig(pool_config=_pool_config())) + db = db_manager.add_config(AsyncpgConfig(pool_config=pool_config())) async with db_manager.provide_session(db) as session: - await _seed_users(session) - - # PostgreSQL uses $1, $2 for parameters instead of ? + await seed_users(session) user = await session.select_one("SELECT * FROM users WHERE id = $1", 1, schema_type=User) print(f"User: {user.name}") + # end-example assert user == User(id=1, name="Alice", email="alice@example.com") diff --git a/docs/examples/quickstart/quickstart_6.py b/docs/examples/quickstart/quickstart_6.py index 4f4c6bfb..6d75a77f 100644 --- a/docs/examples/quickstart/quickstart_6.py +++ b/docs/examples/quickstart/quickstart_6.py @@ -1,23 +1,22 @@ -from sqlspec import SQLSpec -from sqlspec.adapters.duckdb import DuckDBConfig -from sqlspec.adapters.sqlite import SqliteConfig - __all__ = ("test_quickstart_6",) def test_quickstart_6() -> None: - db_manager = SQLSpec() + # start-example + from sqlspec import SQLSpec + from sqlspec.adapters.duckdb import DuckDBConfig + from sqlspec.adapters.sqlite import SqliteConfig - # Register multiple databases + db_manager = SQLSpec() sqlite_db = db_manager.add_config(SqliteConfig(pool_config={"database": "app.db"})) duckdb_db = db_manager.add_config(DuckDBConfig(pool_config={"database": "analytics.duckdb"})) - # Use different databases with db_manager.provide_session(sqlite_db) as sqlite_session: users = sqlite_session.select("SELECT 1") with db_manager.provide_session(duckdb_db) as duckdb_session: analytics = duckdb_session.select("SELECT 1") + # end-example assert isinstance(users, list) assert isinstance(analytics, list) diff --git a/docs/examples/quickstart/quickstart_7.py b/docs/examples/quickstart/quickstart_7.py index 68d6ac94..cdd3551c 100644 --- a/docs/examples/quickstart/quickstart_7.py +++ b/docs/examples/quickstart/quickstart_7.py @@ -1,43 +1,47 @@ -from sqlspec import SQLSpec -from sqlspec.adapters.sqlite import SqliteConfig - __all__ = ("test_quickstart_7",) def test_quickstart_7() -> None: - db_manager = SQLSpec() + # start-example + from sqlspec import SQLSpec + from sqlspec.adapters.sqlite import SqliteConfig + db_manager = SQLSpec() db = db_manager.add_config(SqliteConfig(pool_config={"database": ":memory:"})) - # Transaction committed on successful exit with db_manager.provide_session(db) as session: session.begin() - _ = session.execute(""" + session.execute( + """ CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ) - """) - _ = session.execute(""" + """ + ) + session.execute( + """ CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY, - user_name TEXT NOT NULL - ) - """) - _ = session.execute("DELETE FROM users") - _ = session.execute("DELETE FROM orders") - _ = session.execute("INSERT INTO users (name) VALUES (?)", "Alice") - _ = session.execute("INSERT INTO orders (user_name) VALUES (?)", "Alice") + user_name TEXT NOT NULL + ) + """ + ) + session.execute("DELETE FROM users") + session.execute("DELETE FROM orders") + session.execute("INSERT INTO users (name) VALUES (?)", "Alice") + session.execute("INSERT INTO orders (user_name) VALUES (?)", "Alice") session.commit() with db_manager.provide_session(db) as session: session.begin() - _ = session.execute("INSERT INTO users (name) VALUES (?)", "Bob") + session.execute("INSERT INTO users (name) VALUES (?)", "Bob") session.rollback() with db_manager.provide_session(db) as session: alice = session.select_one_or_none("SELECT * FROM users WHERE name = ?", "Alice") bob = session.select_one_or_none("SELECT * FROM users WHERE name = ?", "Bob") + # end-example assert alice is not None assert alice["name"] == "Alice" diff --git a/docs/examples/quickstart/quickstart_8.py b/docs/examples/quickstart/quickstart_8.py index bf1127ab..c2c7c862 100644 --- a/docs/examples/quickstart/quickstart_8.py +++ b/docs/examples/quickstart/quickstart_8.py @@ -1,26 +1,26 @@ -from sqlspec import SQLSpec, sql -from sqlspec.adapters.sqlite import SqliteConfig - __all__ = ("test_quickstart_8",) def test_quickstart_8() -> None: - # Build a query programmatically + # start-example + from sqlspec import SQLSpec, sql + from sqlspec.adapters.sqlite import SqliteConfig + query = sql.select("id", "name", "email").from_("users").where("age > ?").order_by("name") db_manager = SQLSpec() db = db_manager.add_config(SqliteConfig(pool_config={"database": ":memory:"})) with db_manager.provide_session(db) as session: - # Setup - _ = session.execute(""" + session.execute( + """ CREATE TABLE users (id INTEGER, name TEXT, email TEXT, age INTEGER) - """) - _ = session.execute("INSERT INTO users VALUES (?, ?, ?, ?)", 1, "Alice", "alice@example.com", 30) - - # Execute built query + """ + ) + session.execute("INSERT INTO users VALUES (?, ?, ?, ?)", 1, "Alice", "alice@example.com", 30) results = session.select(query, 25) print(results) + # end-example assert len(results) == 1 assert results[0] == {"id": 1, "name": "Alice", "email": "alice@example.com"} diff --git a/docs/examples/usage/conftest.py b/docs/examples/usage/conftest.py new file mode 100644 index 00000000..4f247707 --- /dev/null +++ b/docs/examples/usage/conftest.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from collections.abc import Generator + +import pytest +from pytest_databases.docker.postgres import PostgresService + +pytest_plugins = ["pytest_databases.docker.postgres"] + + +@pytest.fixture(scope="session", autouse=True) +def usage_postgres_env(postgres_service: PostgresService) -> Generator[None, None, None]: + """Expose Postgres connection settings via env vars for docs examples.""" + + patcher = pytest.MonkeyPatch() + dsn = ( + f"postgresql://{postgres_service.user}:{postgres_service.password}" + f"@{postgres_service.host}:{postgres_service.port}/{postgres_service.database}" + ) + patcher.setenv("SQLSPEC_USAGE_PG_DSN", dsn) + patcher.setenv("SQLSPEC_USAGE_PG_HOST", postgres_service.host) + patcher.setenv("SQLSPEC_USAGE_PG_PORT", str(postgres_service.port)) + patcher.setenv("SQLSPEC_USAGE_PG_USER", postgres_service.user) + patcher.setenv("SQLSPEC_USAGE_PG_PASSWORD", postgres_service.password) + patcher.setenv("SQLSPEC_USAGE_PG_DATABASE", postgres_service.database) + yield + patcher.undo() diff --git a/docs/examples/usage/usage_configuration_1.py b/docs/examples/usage/usage_configuration_1.py new file mode 100644 index 00000000..71e154d0 --- /dev/null +++ b/docs/examples/usage/usage_configuration_1.py @@ -0,0 +1,20 @@ +__all__ = ("test_sqlite_memory_db",) + + +def test_sqlite_memory_db() -> None: + + # start-example + from sqlspec import SQLSpec + from sqlspec.adapters.sqlite import SqliteConfig + + # Create SQLSpec instance + db_manager = SQLSpec() + + # Add database configuration + db = db_manager.add_config(SqliteConfig(pool_config={"database": ":memory:"})) + + # Use the database + with db_manager.provide_session(db) as session: + result = session.execute("SELECT 1") + # end-example + assert result[0] == {"1": 1} diff --git a/docs/examples/usage/usage_configuration_10.py b/docs/examples/usage/usage_configuration_10.py new file mode 100644 index 00000000..59f934c7 --- /dev/null +++ b/docs/examples/usage/usage_configuration_10.py @@ -0,0 +1,18 @@ +__all__ = ("test_manual_pool",) + + +def test_manual_pool() -> None: + + # start-example + import os + + import asyncpg + + from sqlspec.adapters.asyncpg import AsyncpgConfig + + pool = asyncpg.create_pool( + dsn=os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db"), min_size=10, max_size=20 + ) + db = AsyncpgConfig(pool_instance=pool) + # end-example + assert db.pool_instance is pool diff --git a/docs/examples/usage/usage_configuration_11.py b/docs/examples/usage/usage_configuration_11.py new file mode 100644 index 00000000..6d6415ee --- /dev/null +++ b/docs/examples/usage/usage_configuration_11.py @@ -0,0 +1,11 @@ +__all__ = ("test_thread_local_connections",) + + +def test_thread_local_connections() -> None: + + # start-example + from sqlspec.adapters.sqlite import SqliteConfig + + config = SqliteConfig(pool_config={"database": "test.db"}) + # end-example + assert config.pool_config["database"] == "test.db" diff --git a/docs/examples/usage/usage_configuration_12.py b/docs/examples/usage/usage_configuration_12.py new file mode 100644 index 00000000..00f019ac --- /dev/null +++ b/docs/examples/usage/usage_configuration_12.py @@ -0,0 +1,25 @@ +__all__ = ("test_basic_statement_config",) + + +def test_basic_statement_config() -> None: + + # start-example + import os + + from sqlspec import StatementConfig + from sqlspec.adapters.asyncpg import AsyncpgConfig + + 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 + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") + config = AsyncpgConfig(pool_config={"dsn": dsn}, statement_config=statement_config) + # end-example + assert config.statement_config.dialect == "postgres" + assert config.statement_config.enable_parsing is True diff --git a/docs/examples/usage/usage_configuration_13.py b/docs/examples/usage/usage_configuration_13.py new file mode 100644 index 00000000..b93a6e62 --- /dev/null +++ b/docs/examples/usage/usage_configuration_13.py @@ -0,0 +1,21 @@ +__all__ = ("test_parameter_style_config",) + + +def test_parameter_style_config() -> None: + + # start-example + from sqlspec import ParameterStyle, ParameterStyleConfig, 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) + # end-example + assert statement_config.parameter_config.default_parameter_style == ParameterStyle.NUMERIC diff --git a/docs/examples/usage/usage_configuration_14.py b/docs/examples/usage/usage_configuration_14.py new file mode 100644 index 00000000..2d59a620 --- /dev/null +++ b/docs/examples/usage/usage_configuration_14.py @@ -0,0 +1,25 @@ +__all__ = ("test_parameter_styles",) + + +def test_parameter_styles() -> None: + + # start-example + from sqlspec 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) + + # end-example + assert qmark == ParameterStyle.QMARK + assert numeric == ParameterStyle.NUMERIC + assert named_colon == ParameterStyle.NAMED_COLON diff --git a/docs/examples/usage/usage_configuration_15.py b/docs/examples/usage/usage_configuration_15.py new file mode 100644 index 00000000..c8c2cdab --- /dev/null +++ b/docs/examples/usage/usage_configuration_15.py @@ -0,0 +1,25 @@ +"""Test configuration example: Global cache configuration.""" + +__all__ = ("test_global_cache_config",) + + +def test_global_cache_config() -> None: + """Test global cache configuration.""" + # start-example + 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=1000, # Maximum cached SQL items + ) + + # Update global cache configuration + update_cache_config(cache_config) + + # Verify config applied + # end-example + assert cache_config.sql_cache_enabled is True + assert cache_config.sql_cache_size == 1000 diff --git a/docs/examples/usage/usage_configuration_16.py b/docs/examples/usage/usage_configuration_16.py new file mode 100644 index 00000000..3f257a91 --- /dev/null +++ b/docs/examples/usage/usage_configuration_16.py @@ -0,0 +1,26 @@ +"""Test configuration example: Per-instance cache configuration.""" + +__all__ = ("test_per_instance_cache_config",) + + +def test_per_instance_cache_config() -> None: + """Test per-instance cache configuration.""" + # start-example + import tempfile + + from sqlspec import CacheConfig, SQLSpec + from sqlspec.adapters.sqlite import SqliteConfig + + with tempfile.NamedTemporaryFile(suffix=".db", delete=True) as tmp: + # Configure cache for specific SQLSpec instance + db_manager = SQLSpec() + db_manager.update_cache_config(CacheConfig(sql_cache_enabled=True, sql_cache_size=500)) + + # Add database config + db = db_manager.add_config(SqliteConfig(pool_config={"database": tmp.name})) + + # Use the configured spec + with db_manager.provide_session(db) as session: + result = session.execute("SELECT 1") + # end-example + assert result is not None diff --git a/docs/examples/usage/usage_configuration_17.py b/docs/examples/usage/usage_configuration_17.py new file mode 100644 index 00000000..21b6e178 --- /dev/null +++ b/docs/examples/usage/usage_configuration_17.py @@ -0,0 +1,31 @@ +"""Test configuration example: Cache statistics tracking.""" + +__all__ = ("test_cache_statistics",) + + +def test_cache_statistics() -> None: + """Test cache statistics tracking.""" + # start-example + import tempfile + + from sqlspec import SQLSpec + from sqlspec.adapters.sqlite import SqliteConfig + from sqlspec.core import get_cache_statistics, log_cache_stats + + with tempfile.NamedTemporaryFile(suffix=".db", delete=True) as tmp: + db_manager = SQLSpec() + db = db_manager.add_config(SqliteConfig(pool_config={"database": tmp.name})) + + # Execute some queries to generate cache activity + with db_manager.provide_session(db) as session: + session.execute("SELECT 1") + session.execute("SELECT 1") # Should hit cache + + # Get statistics + stats = get_cache_statistics() + # end-example + assert isinstance(stats, dict) + assert "multi_level" in stats + + # Log statistics (logs to configured logger) + log_cache_stats() diff --git a/docs/examples/usage/usage_configuration_18.py b/docs/examples/usage/usage_configuration_18.py new file mode 100644 index 00000000..ecd519d6 --- /dev/null +++ b/docs/examples/usage/usage_configuration_18.py @@ -0,0 +1,22 @@ +"""Test configuration example: Cache clearing operations.""" + +__all__ = ("test_clear_cache",) + + +def test_clear_cache() -> None: + """Test cache clearing operations.""" + # start-example + from sqlspec.core import clear_all_caches, get_cache_statistics + + # Get initial statistics + stats_before = get_cache_statistics() + # end-example + 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 diff --git a/docs/examples/usage/usage_configuration_19.py b/docs/examples/usage/usage_configuration_19.py new file mode 100644 index 00000000..64698980 --- /dev/null +++ b/docs/examples/usage/usage_configuration_19.py @@ -0,0 +1,33 @@ +"""Test configuration example: Binding multiple database configurations.""" + +__all__ = ("test_binding_multiple_configs",) + + +def test_binding_multiple_configs() -> None: + """Test binding multiple database configurations.""" + # start-example + import os + 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: + db_manager = SQLSpec() + + # Add multiple configurations + sqlite_key = db_manager.add_config(SqliteConfig(pool_config={"database": tmp.name})) + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") + asyncpg_key = db_manager.add_config(AsyncpgConfig(pool_config={"dsn": dsn})) + + # Use specific configuration + with db_manager.provide_session(sqlite_key) as session: + session.execute("SELECT 1") + + sqlite_config = db_manager.get_config(sqlite_key) + pg_config = db_manager.get_config(asyncpg_key) + + # end-example + assert sqlite_config.pool_config["database"] == tmp.name + assert pg_config.pool_config["dsn"] == os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") diff --git a/docs/examples/usage/usage_configuration_2.py b/docs/examples/usage/usage_configuration_2.py new file mode 100644 index 00000000..c438621a --- /dev/null +++ b/docs/examples/usage/usage_configuration_2.py @@ -0,0 +1,19 @@ +__all__ = ("test_sqlite_config_setup",) + + +def test_sqlite_config_setup() -> None: + + # start-example + 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 + } + ) + # end-example + assert config.pool_config["database"] == "myapp.db" diff --git a/docs/examples/usage/usage_configuration_20.py b/docs/examples/usage/usage_configuration_20.py new file mode 100644 index 00000000..5dcf01c1 --- /dev/null +++ b/docs/examples/usage/usage_configuration_20.py @@ -0,0 +1,33 @@ +"""Test configuration example: Named database bindings.""" + +__all__ = ("test_named_bindings",) + + +def test_named_bindings() -> None: + """Test named database bindings.""" + # start-example + import os + 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: + db_manager = SQLSpec() + + # Add with bind keys + cache_key = db_manager.add_config(SqliteConfig(pool_config={"database": tmp.name}, bind_key="cache_db")) + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") + main_key = db_manager.add_config(AsyncpgConfig(pool_config={"dsn": dsn}, bind_key="main_db")) + + # Access by bind key + with db_manager.provide_session(cache_key) as session: + session.execute("SELECT 1") + + cache_config = db_manager.get_config(cache_key) + main_config = db_manager.get_config(main_key) + + # end-example + assert cache_config.bind_key == "cache_db" + assert main_config.bind_key == "main_db" diff --git a/docs/examples/usage/usage_configuration_21.py b/docs/examples/usage/usage_configuration_21.py new file mode 100644 index 00000000..270a3952 --- /dev/null +++ b/docs/examples/usage/usage_configuration_21.py @@ -0,0 +1,28 @@ +"""Test configuration example: Basic migration configuration.""" + +__all__ = ("test_basic_migration_config",) + + +def test_basic_migration_config() -> None: + """Test basic migration configuration.""" + # start-example + import os + + from sqlspec.adapters.asyncpg import AsyncpgConfig + + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") + config = AsyncpgConfig( + pool_config={"dsn": dsn}, + 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 + }, + ) + + # end-example + assert config.migration_config["script_location"] == "migrations" + assert "litestar" in config.migration_config["include_extensions"] diff --git a/docs/examples/usage/usage_configuration_22.py b/docs/examples/usage/usage_configuration_22.py new file mode 100644 index 00000000..c5ea078e --- /dev/null +++ b/docs/examples/usage/usage_configuration_22.py @@ -0,0 +1,25 @@ +__all__ = ("test_basic_migration_config",) + + +def test_basic_migration_config() -> None: + + # start-example + import os + + from sqlspec.adapters.asyncpg import AsyncpgConfig + + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") + config = AsyncpgConfig( + pool_config={"dsn": dsn}, + 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 + }, + ) + # end-example + assert config.migration_config["script_location"] == "migrations" + assert "litestar" in config.migration_config["include_extensions"] diff --git a/docs/examples/usage/usage_configuration_23.py b/docs/examples/usage/usage_configuration_23.py new file mode 100644 index 00000000..533dbde6 --- /dev/null +++ b/docs/examples/usage/usage_configuration_23.py @@ -0,0 +1,35 @@ +"""Test configuration example: Environment-based configuration.""" + +__all__ = ("test_extension_config",) + + +def test_extension_config() -> None: + # start-example + import os + + from sqlspec.adapters.asyncpg import AsyncpgConfig + + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") + config = AsyncpgConfig( + pool_config={"dsn": dsn}, + extension_config={ + "litestar": { + "connection_key": "db_connection", + "session_key": "db_session", + "pool_key": "db_pool", + "commit_mode": "autocommit_include_redirect", + "extra_commit_statuses": {201}, + "extra_rollback_statuses": {422}, + "enable_correlation_middleware": True, + "correlation_header": "x-request-id", + "correlation_headers": ["traceparent", "x-correlation-id"], + "auto_trace_headers": False, + "disable_di": False, + } + }, + ) + + # end-example + assert config.extension_config["litestar"]["commit_mode"] == "autocommit_include_redirect" + assert config.extension_config["litestar"]["extra_commit_statuses"] == {201} + assert config.extension_config["litestar"]["correlation_header"] == "x-request-id" diff --git a/docs/examples/usage/usage_configuration_24.py b/docs/examples/usage/usage_configuration_24.py new file mode 100644 index 00000000..bfcbd7b0 --- /dev/null +++ b/docs/examples/usage/usage_configuration_24.py @@ -0,0 +1,40 @@ +"""Test configuration example: Environment-based configuration.""" + +__all__ = ("test_environment_based_configuration",) + + +def test_environment_based_configuration() -> None: + """Test environment-based configuration pattern.""" + + # start-example + import os + from unittest.mock import patch + + # 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"), + } + ) + + # end-example + assert config.pool_config["host"] == "testhost" + assert config.pool_config["port"] == 5433 + assert config.pool_config["user"] == "testuser" + assert config.pool_config["password"] == "testpass" + assert config.pool_config["database"] == "testdb" diff --git a/docs/examples/usage/usage_configuration_25.py b/docs/examples/usage/usage_configuration_25.py new file mode 100644 index 00000000..3286c59e --- /dev/null +++ b/docs/examples/usage/usage_configuration_25.py @@ -0,0 +1,19 @@ +"""Test configuration example: Best practice - Use connection pooling.""" + +__all__ = ("test_connection_pooling_best_practice",) + + +def test_connection_pooling_best_practice() -> None: + """Test connection pooling best practice configuration.""" + # start-example + import os + + from sqlspec.adapters.asyncpg import AsyncpgConfig + + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") + config = AsyncpgConfig(pool_config={"dsn": dsn, "min_size": 10, "max_size": 20}) + + # end-example + assert config.pool_config["min_size"] == 10 + assert config.pool_config["max_size"] == 20 + assert config.supports_connection_pooling is True diff --git a/docs/examples/usage/usage_configuration_26.py b/docs/examples/usage/usage_configuration_26.py new file mode 100644 index 00000000..ccb4a27b --- /dev/null +++ b/docs/examples/usage/usage_configuration_26.py @@ -0,0 +1,15 @@ +"""Test configuration example: Best practice - Enable caching.""" + +__all__ = ("test_enable_caching_best_practice",) + + +def test_enable_caching_best_practice() -> None: + """Test caching best practice configuration.""" + # start-example + from sqlspec import StatementConfig + + statement_config = StatementConfig(dialect="postgres", enable_caching=True) + + # end-example + assert statement_config.enable_caching is True + assert statement_config.dialect == "postgres" diff --git a/docs/examples/usage/usage_configuration_27.py b/docs/examples/usage/usage_configuration_27.py new file mode 100644 index 00000000..2101d90c --- /dev/null +++ b/docs/examples/usage/usage_configuration_27.py @@ -0,0 +1,25 @@ +"""Test configuration example: Best practice - Tune pool sizes.""" + +__all__ = ("test_tune_pool_sizes_best_practice",) + + +MIN_POOL_SIZE_CPU = 5 +MAX_POOL_SIZE_CPU = 10 +MIN_IO_BOUND_POOL_SIZE = 20 +MAX_IO_BOUND_POOL_SIZE = 50 + + +def test_tune_pool_sizes_best_practice() -> None: + """Test pool sizing best practices for different workloads.""" + + # start-example + # CPU-bound workload - smaller pool + cpu_bound_pool_config = {"min_size": 5, "max_size": 10} + # end-example + assert cpu_bound_pool_config["min_size"] == 5 + assert cpu_bound_pool_config["max_size"] == 10 + + # I/O-bound workload - larger pool + io_bound_pool_config = {"min_size": 20, "max_size": 50} + assert io_bound_pool_config["min_size"] == 20 + assert io_bound_pool_config["max_size"] == 50 diff --git a/docs/examples/usage/usage_configuration_28.py b/docs/examples/usage/usage_configuration_28.py new file mode 100644 index 00000000..f862b184 --- /dev/null +++ b/docs/examples/usage/usage_configuration_28.py @@ -0,0 +1,18 @@ +"""Test configuration example: Best practice - Tune pool sizes.""" + +__all__ = ("test_disable_security_checks_best_practice",) + + +def test_disable_security_checks_best_practice() -> None: + """Test disabling security checks when necessary.""" + + # start-example + from sqlspec import StatementConfig + + # Example: Disabling security checks for trusted internal queries + statement_config = StatementConfig( + dialect="postgres", + enable_validation=False, # Skip security checks + ) + # end-example + assert statement_config.enable_validation is False diff --git a/docs/examples/usage/usage_configuration_29.py b/docs/examples/usage/usage_configuration_29.py new file mode 100644 index 00000000..002a0e45 --- /dev/null +++ b/docs/examples/usage/usage_configuration_29.py @@ -0,0 +1,31 @@ +"""Test configuration example: Best practice - Clean up resources.""" + +__all__ = ("test_cleanup_resources_best_practice",) + + +import pytest + + +@pytest.mark.asyncio +async def test_cleanup_resources_best_practice() -> None: + """Test resource cleanup best practice.""" + # start-example + import tempfile + + from sqlspec import SQLSpec + from sqlspec.adapters.aiosqlite import AiosqliteConfig + + with tempfile.NamedTemporaryFile(suffix=".db", delete=True) as tmp: + db_manager = SQLSpec() + db = db_manager.add_config(AiosqliteConfig(pool_config={"database": tmp.name})) + + # Use the connection + async with db_manager.provide_session(db) as session: + await session.execute("CREATE TABLE test (id INTEGER)") + + # Clean up resources - important for async adapters + await db_manager.close_all_pools() + + # Verify pools are closed + # end-example + assert db.pool_instance is None or not hasattr(db.pool_instance, "_pool") diff --git a/docs/examples/usage/usage_configuration_3.py b/docs/examples/usage/usage_configuration_3.py new file mode 100644 index 00000000..77f9f165 --- /dev/null +++ b/docs/examples/usage/usage_configuration_3.py @@ -0,0 +1,16 @@ +__all__ = ("test_memory_databases",) + + +def test_memory_databases() -> None: + + # start-example + from sqlspec.adapters.sqlite import SqliteConfig + + # In-memory database (isolated per connection) + config = SqliteConfig(pool_config={"database": ":memory:"}) + # end-example + assert ":memory_" in config.pool_config["database"] + + # Shared memory database + shared_config = SqliteConfig(pool_config={"database": "file:memdb1?mode=memory&cache=shared", "uri": True}) + assert shared_config.pool_config["database"] == "file:memdb1?mode=memory&cache=shared" diff --git a/docs/examples/usage/usage_configuration_30.py b/docs/examples/usage/usage_configuration_30.py new file mode 100644 index 00000000..47e74a35 --- /dev/null +++ b/docs/examples/usage/usage_configuration_30.py @@ -0,0 +1,21 @@ +"""Telemetry snapshot example.""" + +__all__ = ("test_telemetry_snapshot",) + + +def test_telemetry_snapshot() -> None: + """Demonstrate SQLSpec.telemetry_snapshot().""" + # start-example + from sqlspec import SQLSpec + from sqlspec.adapters.sqlite import SqliteConfig + + db_manager = SQLSpec() + db = db_manager.add_config(SqliteConfig(pool_config={"database": ":memory:"})) + + with db_manager.provide_session(db) as session: + session.execute("SELECT 1") + + snapshot = db_manager.telemetry_snapshot() + # end-example + assert "SqliteConfig.lifecycle.query_start" in snapshot + _ = snapshot.get("storage_bridge.bytes_written", 0) diff --git a/docs/examples/usage/usage_configuration_4.py b/docs/examples/usage/usage_configuration_4.py new file mode 100644 index 00000000..909ff3cd --- /dev/null +++ b/docs/examples/usage/usage_configuration_4.py @@ -0,0 +1,31 @@ +__all__ = ("test_asyncpg_config_setup",) + + +def test_asyncpg_config_setup() -> None: + + # start-example + import os + + from sqlspec.adapters.asyncpg import AsyncpgConfig + + host = os.getenv("SQLSPEC_USAGE_PG_HOST", "localhost") + port = int(os.getenv("SQLSPEC_USAGE_PG_PORT", "5432")) + user = os.getenv("SQLSPEC_USAGE_PG_USER", "user") + password = os.getenv("SQLSPEC_USAGE_PG_PASSWORD", "password") + database = os.getenv("SQLSPEC_USAGE_PG_DATABASE", "db") + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", f"postgresql://{user}:{password}@{host}:{port}/{database}") + + config = AsyncpgConfig( + pool_config={ + "dsn": dsn, + "min_size": 10, + "max_size": 20, + "host": host, + "port": port, + "user": user, + "password": password, + "database": database, + } + ) + # end-example + assert config.pool_config["host"] == host diff --git a/docs/examples/usage/usage_configuration_5.py b/docs/examples/usage/usage_configuration_5.py new file mode 100644 index 00000000..c617ace2 --- /dev/null +++ b/docs/examples/usage/usage_configuration_5.py @@ -0,0 +1,35 @@ +__all__ = ("test_psycopg_config_setup",) + + +def test_psycopg_config_setup() -> None: + + # start-example + import os + + from sqlspec.adapters.psycopg import PsycopgAsyncConfig + + host = os.getenv("SQLSPEC_USAGE_PG_HOST", "localhost") + port = int(os.getenv("SQLSPEC_USAGE_PG_PORT", "5432")) + database = os.getenv("SQLSPEC_USAGE_PG_DATABASE", "db") + user = os.getenv("SQLSPEC_USAGE_PG_USER", "user") + password = os.getenv("SQLSPEC_USAGE_PG_PASSWORD", "password") + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", f"postgresql://{user}:{password}@{host}:{port}/{database}") + + # Async version + config = PsycopgAsyncConfig( + pool_config={ + "conninfo": dsn, + # Or keyword arguments: + "host": host, + "port": port, + "dbname": database, + "user": user, + "password": password, + # Pool settings + "min_size": 5, + "max_size": 10, + "timeout": 30.0, + } + ) + # end-example + assert config.pool_config is not None diff --git a/docs/examples/usage/usage_configuration_6.py b/docs/examples/usage/usage_configuration_6.py new file mode 100644 index 00000000..4fd65624 --- /dev/null +++ b/docs/examples/usage/usage_configuration_6.py @@ -0,0 +1,22 @@ +__all__ = ("test_asyncmy_config_setup",) + + +def test_asyncmy_config_setup() -> None: + # start-example + from sqlspec.adapters.asyncmy import AsyncmyConfig + + config = AsyncmyConfig( + pool_config={ + "host": "localhost", + "port": 3306, + "user": "myuser", + "password": "mypassword", + "database": "mydb", + "charset": "utf8mb4", + "minsize": 1, + "maxsize": 10, + "pool_recycle": 3600, + } + ) + # end-example + assert config.pool_config["port"] == 3306 diff --git a/docs/examples/usage/usage_configuration_7.py b/docs/examples/usage/usage_configuration_7.py new file mode 100644 index 00000000..13aed2c0 --- /dev/null +++ b/docs/examples/usage/usage_configuration_7.py @@ -0,0 +1,14 @@ +__all__ = ("test_duckdb_config_setup",) + + +def test_duckdb_config_setup() -> None: + + # start-example + from sqlspec.adapters.duckdb import DuckDBConfig + + in_memory_config = DuckDBConfig() + # end-example + assert in_memory_config.pool_config.get("database") == ":memory:shared_db" + + persistent_config = DuckDBConfig(pool_config={"database": "analytics.duckdb", "read_only": False}) + assert persistent_config.pool_config["read_only"] is False diff --git a/docs/examples/usage/usage_configuration_8.py b/docs/examples/usage/usage_configuration_8.py new file mode 100644 index 00000000..39fdfcd9 --- /dev/null +++ b/docs/examples/usage/usage_configuration_8.py @@ -0,0 +1,22 @@ +__all__ = ("test_asyncpg_pool_setup",) + + +def test_asyncpg_pool_setup() -> None: + # start-example + import os + + from sqlspec.adapters.asyncpg import AsyncpgConfig + + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") + + config = AsyncpgConfig( + pool_config={ + "dsn": dsn, + "min_size": 10, + "max_size": 20, + "max_queries": 50000, + "max_inactive_connection_lifetime": 300.0, + } + ) + # end-example + assert config.pool_config["min_size"] == 10 diff --git a/docs/examples/usage/usage_configuration_9.py b/docs/examples/usage/usage_configuration_9.py new file mode 100644 index 00000000..40407297 --- /dev/null +++ b/docs/examples/usage/usage_configuration_9.py @@ -0,0 +1,18 @@ +__all__ = ("test_pool_lifecycle",) + + +def test_pool_lifecycle() -> None: + + # start-example + import os + + from sqlspec import SQLSpec + from sqlspec.adapters.asyncpg import AsyncpgConfig + + db_manager = SQLSpec() + dsn = os.getenv("SQLSPEC_USAGE_PG_DSN", "postgresql://localhost/db") + asyncpg_key = db_manager.add_config(AsyncpgConfig(pool_config={"dsn": dsn})) + + asyncpg_config = db_manager.get_config(asyncpg_key) + # end-example + assert asyncpg_config.pool_config["dsn"] == dsn diff --git a/docs/examples/usage/usage_index_1.py b/docs/examples/usage/usage_index_1.py index a8a48516..89b8e0d1 100644 --- a/docs/examples/usage/usage_index_1.py +++ b/docs/examples/usage/usage_index_1.py @@ -1,15 +1,16 @@ -from sqlspec import SQLSpec -from sqlspec.adapters.sqlite import SqliteConfig - __all__ = ("test_index_1",) def test_index_1() -> None: + # start-example + from sqlspec import SQLSpec + from sqlspec.adapters.sqlite import SqliteConfig + db_manager = SQLSpec() db = db_manager.add_config(SqliteConfig()) with db_manager.provide_session(db) as session: - _ = session.execute( + session.execute( """ CREATE TABLE users ( id INTEGER PRIMARY KEY, @@ -17,9 +18,10 @@ def test_index_1() -> None: ) """ ) - _ = session.execute("INSERT INTO users VALUES (?, ?)", 1, "alice") + session.execute("INSERT INTO users VALUES (?, ?)", 1, "alice") with db_manager.provide_session(db) as session: user = session.select_one("SELECT * FROM users WHERE id = ?", 1) + # end-example assert user == {"id": 1, "name": "alice"} diff --git a/docs/examples/usage/usage_index_2.py b/docs/examples/usage/usage_index_2.py index aac40079..551d3e71 100644 --- a/docs/examples/usage/usage_index_2.py +++ b/docs/examples/usage/usage_index_2.py @@ -1,16 +1,17 @@ -from sqlspec import SQLSpec, sql -from sqlspec.adapters.sqlite import SqliteConfig - __all__ = ("test_index_2",) def test_index_2() -> None: + # start-example + from sqlspec import SQLSpec, sql + from sqlspec.adapters.sqlite import SqliteConfig + db_manager = SQLSpec() db = db_manager.add_config(SqliteConfig()) query = sql.select("id", "name", "email").from_("users").where("active = ?") with db_manager.provide_session(db) as session: - _ = session.execute( + session.execute( """ CREATE TABLE users ( id INTEGER PRIMARY KEY, @@ -20,7 +21,7 @@ def test_index_2() -> None: ) """ ) - _ = session.execute( + session.execute( """ INSERT INTO users VALUES (1, 'alice', 'alice@example.com', 1), @@ -29,6 +30,7 @@ def test_index_2() -> None: """ ) users = session.select(query, True) # noqa: FBT003 + # end-example names = [user["name"] for user in users] assert names == ["alice", "carol"] diff --git a/docs/examples/usage/usage_index_3.py b/docs/examples/usage/usage_index_3.py index 3e7db4bf..dbca3c07 100644 --- a/docs/examples/usage/usage_index_3.py +++ b/docs/examples/usage/usage_index_3.py @@ -1,24 +1,24 @@ -from pathlib import Path - -from sqlspec import SQLSpec -from sqlspec.adapters.sqlite import SqliteConfig -from sqlspec.loader import SQLFileLoader - __all__ = ("test_index_3",) -QUERIES_PATH = Path(__file__).parent.parent / "queries" / "users.sql" +def test_index_3() -> None: + # start-example + from pathlib import Path + from sqlspec import SQLSpec + from sqlspec.adapters.sqlite import SqliteConfig + from sqlspec.loader import SQLFileLoader + + queries_path = Path(__file__).parent.parent / "queries" / "users.sql" -def test_index_3() -> None: db_manager = SQLSpec() db = db_manager.add_config(SqliteConfig()) loader = SQLFileLoader() - loader.load_sql(QUERIES_PATH) + loader.load_sql(queries_path) get_user_by_id = loader.get_sql("get_user_by_id") with db_manager.provide_session(db) as session: - _ = session.execute( + session.execute( """ CREATE TABLE users ( id INTEGER PRIMARY KEY, @@ -29,7 +29,7 @@ def test_index_3() -> None: ) """ ) - _ = session.execute( + session.execute( """ INSERT INTO users(id, username, email) VALUES (1, 'alice', 'alice@example.com'), @@ -37,6 +37,7 @@ def test_index_3() -> None: """ ) user = session.select_one(get_user_by_id, user_id=2) + # end-example assert user["username"] == "bob" assert user["email"] == "bob@example.com" diff --git a/docs/getting_started/quickstart.rst b/docs/getting_started/quickstart.rst index 3c9e0823..87f64ec5 100644 --- a/docs/getting_started/quickstart.rst +++ b/docs/getting_started/quickstart.rst @@ -12,8 +12,10 @@ Let's start with the simplest possible example - executing a query and getting r .. literalinclude:: /examples/quickstart/quickstart_1.py :language: python :caption: ``first sqlspec query`` - :lines: 6-13 + :start-after: # start-example + :end-before: # end-example :dedent: 4 + :no-upgrade: What's happening here? @@ -30,8 +32,10 @@ Let's create a table, insert some data, and query it: .. literalinclude:: /examples/quickstart/quickstart_2.py :language: python :caption: ``working with real data`` - :lines: 6-38 + :start-after: # start-example + :end-before: # end-example :dedent: 4 + :no-upgrade: Session Methods Cheat Sheet ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -54,8 +58,10 @@ The real power of SQLSpec comes from type-safe result mapping. Define your data .. literalinclude:: /examples/quickstart/quickstart_3.py :language: python :caption: ``type-safe results`` - :lines: 15-34 + :start-after: # start-example + :end-before: # end-example :dedent: 4 + :no-upgrade: .. note:: @@ -69,8 +75,10 @@ SQLSpec supports async/await for non-blocking database operations. Here's the sa .. literalinclude:: /examples/quickstart/quickstart_4.py :language: python :caption: ``async support`` - :lines: 15-30 - :dedent: 2 + :start-after: # start-example + :end-before: # end-example + :dedent: 4 + :no-upgrade: The API is identical - just add ``await`` and use async config/drivers! @@ -82,8 +90,10 @@ One of SQLSpec's strengths is the consistent API across databases. Here's the sa .. literalinclude:: /examples/quickstart/quickstart_5.py :language: python :caption: ``switching databases`` - :lines: 18-58 + :start-after: # start-example + :end-before: # end-example :dedent: 4 + :no-upgrade: .. note:: @@ -110,8 +120,10 @@ Need to work with multiple databases? Register multiple configs: .. literalinclude:: /examples/quickstart/quickstart_6.py :language: python :caption: ``multiple databases`` - :lines: 7-18 + :start-after: # start-example + :end-before: # end-example :dedent: 4 + :no-upgrade: Transaction Support @@ -123,8 +135,10 @@ boundaries explicitly and keep examples deterministic: .. literalinclude:: /examples/quickstart/quickstart_7.py :language: python :caption: ``transaction support`` - :lines: 8-55 + :start-after: # start-example + :end-before: # end-example :dedent: 4 + :no-upgrade: The snippet seeds data in a temporary SQLite database, intentionally triggers a failure, and uses ``contextlib.suppress`` so the docs stay readable while the companion test verifies @@ -142,8 +156,10 @@ For those who prefer programmatic query construction, SQLSpec includes an experi .. literalinclude:: /examples/quickstart/quickstart_8.py :language: python :caption: ``query builder`` - :lines: 6-21 + :start-after: # start-example + :end-before: # end-example :dedent: 4 + :no-upgrade: .. warning:: diff --git a/docs/usage/configuration.rst b/docs/usage/configuration.rst index a53af5aa..2e4810d0 100644 --- a/docs/usage/configuration.rst +++ b/docs/usage/configuration.rst @@ -18,145 +18,83 @@ Basic Configuration The simplest way to use SQLSpec is with default configuration: -.. code-block:: python - - 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") +.. literalinclude:: /examples/usage/usage_configuration_1.py + :language: python + :caption: `basic configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Database Configurations ----------------------- Each database adapter has its own configuration class with adapter-specific settings. -SQLite Configuration -^^^^^^^^^^^^^^^^^^^^ +.. note:: -.. code-block:: python + Async PostgreSQL examples in this guide read their connection details from + ``SQLSPEC_USAGE_PG_*`` environment variables. The test suite populates these + variables (host, port, user, password, database, DSN) automatically so the + literalincluded snippets can stay identical to the documentation. - from sqlspec.adapters.sqlite import SqliteConfig +SQLite Configuration +^^^^^^^^^^^^^^^^^^^^ - 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 - } - ) +.. literalinclude:: /examples/usage/usage_configuration_2.py + :language: python + :caption: `sqlite configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 **Memory Databases** -.. code-block:: python - - # In-memory database (isolated per connection) - config = SqliteConfig(pool_config={"database": ":memory:"}) - - # Shared memory database - config = SqliteConfig( - pool_config={ - "database": "file:memdb1?mode=memory&cache=shared", - "uri": True - } - ) +.. literalinclude:: /examples/usage/usage_configuration_3.py + :language: python + :caption: `memory sqlite configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 PostgreSQL Configuration (asyncpg) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: python - - from sqlspec.adapters.asyncpg import AsyncpgConfig - - config = AsyncpgConfig( - pool_config={ - "dsn": "postgresql://user:pass@localhost:5432/dbname", - # Or individual parameters: - "host": "localhost", - "port": 5432, - "user": "myuser", - "password": "mypassword", - "database": "mydb", - # Pool settings - "min_size": 10, - "max_size": 20, - "max_queries": 50000, - "max_inactive_connection_lifetime": 300.0, - } - ) +.. literalinclude:: /examples/usage/usage_configuration_4.py + :language: python + :caption: `postgres asyncpg configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 PostgreSQL Configuration (psycopg) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: python - - from sqlspec.adapters.psycopg import PsycopgConfig - - # Async version - config = PsycopgConfig( - pool_config={ - "conninfo": "postgresql://user:pass@localhost/db", - # Or keyword arguments: - "host": "localhost", - "port": 5432, - "dbname": "mydb", - "user": "myuser", - "password": "mypassword", - # Pool settings - "min_size": 5, - "max_size": 10, - "timeout": 30.0, - } - ) +.. literalinclude:: /examples/usage/usage_configuration_5.py + :language: python + :caption: `psycopg async configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 MySQL Configuration (asyncmy) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: python - - from sqlspec.adapters.asyncmy import AsyncmyConfig - - config = AsyncmyConfig( - pool_config={ - "host": "localhost", - "port": 3306, - "user": "myuser", - "password": "mypassword", - "database": "mydb", - "charset": "utf8mb4", - # Pool settings - "minsize": 1, - "maxsize": 10, - "pool_recycle": 3600, - } - ) +.. literalinclude:: /examples/usage/usage_configuration_6.py + :language: python + :caption: `mysql asyncmy configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 DuckDB Configuration ^^^^^^^^^^^^^^^^^^^^ -.. code-block:: python - - from sqlspec.adapters.duckdb import DuckDBConfig - - # In-memory database - config = DuckDBConfig() - - # Persistent database - config = DuckDBConfig( - pool_config={ - "database": "analytics.duckdb", - "read_only": False, - } - ) +.. literalinclude:: /examples/usage/usage_configuration_7.py + :language: python + :caption: `duckdb configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Connection Pooling ------------------ @@ -166,65 +104,41 @@ Connection pooling improves performance by reusing database connections. SQLSpec Pool Configuration ^^^^^^^^^^^^^^^^^^ -.. code-block:: python - - from sqlspec.adapters.asyncpg import AsyncpgConfig - - config = AsyncpgConfig( - pool_config={ - "dsn": "postgresql://localhost/db", - "min_size": 10, # Minimum connections to maintain - "max_size": 20, # Maximum connections allowed - "max_queries": 50000, # Max queries per connection before recycling - "max_inactive_connection_lifetime": 300.0, # Idle timeout - } - ) +.. literalinclude:: /examples/usage/usage_configuration_8.py + :language: python + :caption: `pool configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 **Pool Lifecycle Management** -.. code-block:: python - - # SQLSpec manages pool lifecycle automatically - spec = SQLSpec() - db = spec.add_config(AsyncpgConfig(pool_config={...})) - - # Pool is created on first use - async with spec.provide_session(db) as session: - await session.execute("SELECT 1") - - # Clean up all pools on shutdown - await spec.close_all_pools() +.. literalinclude:: /examples/usage/usage_configuration_9.py + :language: python + :caption: `pool lifecycle management` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Using Pre-Created Pools ^^^^^^^^^^^^^^^^^^^^^^^^ -You can create and manage pools manually: - -.. code-block:: python - - import asyncpg - - # Create pool manually - pool = await asyncpg.create_pool( - dsn="postgresql://localhost/db", - min_size=10, - max_size=20 - ) - - # Pass to config and add to SQLSpec - db = spec.add_config(AsyncpgConfig(pool_instance=pool)) +.. literalinclude:: /examples/usage/usage_configuration_10.py + :language: python + :caption: `using pre-created pools` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 No-Pooling Configuration ^^^^^^^^^^^^^^^^^^^^^^^^^ -For simple use cases or testing, disable pooling: - -.. code-block:: python - - from sqlspec.adapters.sqlite import SqliteConfig - - # SQLite uses thread-local connections (no traditional pooling) - config = SqliteConfig(pool_config={"database": "test.db"}) +.. literalinclude:: /examples/usage/usage_configuration_11.py + :language: python + :caption: `no-pooling configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Statement Configuration ----------------------- @@ -234,72 +148,33 @@ Statement configuration controls SQL processing pipeline behavior. Basic Statement Config ^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: python - - from sqlspec.core import StatementConfig - from sqlspec.core import ParameterStyle, ParameterStyleConfig - - 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={...}, - statement_config=statement_config - ) +.. literalinclude:: /examples/usage/usage_configuration_12.py + :language: python + :caption: `basic statement config` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Parameter Style Configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Control how parameters are handled: - -.. code-block:: python - - from sqlspec.core import ParameterStyle, ParameterStyleConfig - - 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 - ) +.. literalinclude:: /examples/usage/usage_configuration_13.py + :language: python + :caption: `parameter style configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 **Parameter Styles** SQLSpec supports multiple parameter placeholder styles: -.. code-block:: python - - from sqlspec.core import ParameterStyle - - # Question mark (SQLite, DuckDB) - ParameterStyle.QMARK # WHERE id = ? - - # Numeric (PostgreSQL, asyncpg) - ParameterStyle.NUMERIC # WHERE id = $1 - - # Named colon (Oracle, SQLite) - ParameterStyle.NAMED_COLON # WHERE id = :id - - # Named at (BigQuery) - ParameterStyle.NAMED_AT # WHERE id = @id - - # Format/pyformat (psycopg, MySQL) - ParameterStyle.POSITIONAL_PYFORMAT # WHERE id = %s - ParameterStyle.NAMED_PYFORMAT # WHERE id = %(id)s +.. literalinclude:: /examples/usage/usage_configuration_14.py + :language: python + :caption: `parameter styles` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Validation Configuration ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -308,13 +183,12 @@ Configure security and performance validation. Disable validation for performance-critical paths where input is trusted: -.. code-block:: python - - statement_config = StatementConfig( - dialect="postgres", - enable_validation=False, # Skip validation - enable_transformations=False, # Skip transformations - ) +.. literalinclude:: /examples/usage/usage_configuration_15.py + :language: python + :caption: `validation configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Cache Configuration ------------------- @@ -324,61 +198,44 @@ SQLSpec uses multi-tier caching to avoid recompiling SQL statements. Global Cache Configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: python - - from sqlspec.core import CacheConfig, update_cache_config - - cache_config = CacheConfig( - enable_sql_cache=True, # Cache compiled SQL strings - enable_optimized_cache=True, # Cache optimized AST - enable_builder_cache=True, # Cache QueryBuilder state - enable_file_cache=True, # Cache loaded SQL files - max_cache_size=1000, # Maximum cached items - ) - - # Update global cache configuration - update_cache_config(cache_config) +.. literalinclude:: /examples/usage/usage_configuration_15.py + :language: python + :caption: `global cache configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Per-Instance Cache Configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: python - - # Configure cache for specific SQLSpec instance - spec = SQLSpec() - spec.update_cache_config( - CacheConfig( - enable_sql_cache=True, - max_cache_size=500 - ) - ) +.. literalinclude:: /examples/usage/usage_configuration_16.py + :language: python + :caption: `per-instance cache configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Cache Statistics ^^^^^^^^^^^^^^^^ Monitor cache statistics: -.. code-block:: python - - from sqlspec.core import get_cache_statistics, log_cache_stats - - # Get statistics - stats = get_cache_statistics() - print(f"SQL Cache hits: {stats['sql_cache_hits']}") - print(f"File Cache hits: {stats['file_cache_hits']}") - - # Log statistics - log_cache_stats() # Logs to configured logger +.. literalinclude:: /examples/usage/usage_configuration_17.py + :language: python + :caption: `cache statistics` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Clear Cache ^^^^^^^^^^^ -.. code-block:: python - - from sqlspec.core import reset_cache_stats - - # Clear all caches and reset statistics - reset_cache_stats() +.. literalinclude:: /examples/usage/usage_configuration_18.py + :language: python + :caption: `clear cache` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Multiple Database Configurations --------------------------------- @@ -388,39 +245,31 @@ SQLSpec supports multiple database configurations in a single application. Binding Multiple Configs ^^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: python - - from sqlspec import SQLSpec - from sqlspec.adapters.sqlite import SqliteConfig - from sqlspec.adapters.asyncpg import AsyncpgConfig - - spec = SQLSpec() - - # Add multiple configurations - sqlite_db = spec.add_config(SqliteConfig(pool_config={"database": "cache.db"})) - postgres_db = spec.add_config(AsyncpgConfig(pool_config={"dsn": "postgresql://..."})) - - # Use specific configuration - with spec.provide_session(sqlite_db) as session: - session.execute("SELECT * FROM cache") - - async with spec.provide_session(postgres_db) as session: - await session.execute("SELECT * FROM users") +.. literalinclude:: /examples/usage/usage_configuration_19.py + :language: python + :caption: `binding multiple configurations` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Named Bindings ^^^^^^^^^^^^^^ Use bind keys for clearer configuration management: -.. code-block:: python - - # Add with bind keys - cache_db = spec.add_config(SqliteConfig(pool_config={"database": "cache.db"}), bind_key="cache_db") - main_db = spec.add_config(AsyncpgConfig(pool_config={"dsn": "postgresql://..."}), bind_key="main_db") +.. literalinclude:: /examples/usage/usage_configuration_20.py + :language: python + :caption: `named bindings` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 - # Access by bind key - with spec.provide_session("cache_db") as session: - session.execute("SELECT 1") +``SQLSpec.add_config()`` returns a key for the registered configuration. +Store that value (as shown above) and reuse it when calling +``provide_session()`` or ``get_config()``. The config registry holds a single +instance per config type, so creating multiple variants of the same adapter +requires defining lightweight subclasses or binding unique config classes for +each database. Migration Configuration ----------------------- @@ -430,34 +279,27 @@ SQLSpec includes a migration system for schema management. Basic Migration Config ^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: python - - 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 - } - ) +.. literalinclude:: /examples/usage/usage_configuration_22.py + :language: python + :caption: `basic migration config` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 **Migration CLI** .. code-block:: bash - # Create migration - sqlspec --config myapp.config create-migration -m "Add users table" + # Create migration + sqlspec --config myapp.config create-migration -m "Add users table" + + # Apply migrations + sqlspec --config myapp.config upgrade + + # Rollback + sqlspec --config myapp.config downgrade -1 - # Apply migrations - sqlspec --config myapp.config upgrade - # Rollback - sqlspec --config myapp.config downgrade -1 Extension Migration Versioning ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -484,57 +326,36 @@ Framework integrations can be configured via ``extension_config``. Litestar Plugin Configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: python - - 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, - "correlation_header": "x-correlation-id", - "correlation_headers": ["x-custom-trace"], - "auto_trace_headers": True, # Detect Traceparent, X-Cloud-Trace-Context, etc. - } - } - ) +.. literalinclude:: /examples/usage/usage_configuration_23.py + :language: python + :caption: `litestar plugin configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Telemetry Snapshot ~~~~~~~~~~~~~~~~~~ Call ``SQLSpec.telemetry_snapshot()`` to inspect lifecycle counters, serializer metrics, and recent storage jobs: -.. code-block:: python - - snapshot = spec.telemetry_snapshot() - print(snapshot["storage_bridge.bytes_written"]) - for job in snapshot.get("storage_bridge.recent_jobs", []): - print(job["destination"], job.get("correlation_id")) +.. literalinclude:: /examples/usage/usage_configuration_30.py + :language: python + :caption: `telemetry snapshot` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Environment-Based Configuration ------------------------------- Use environment variables for configuration: -.. code-block:: python - - import os - 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"), - } - ) +.. literalinclude:: /examples/usage/usage_configuration_24.py + :language: python + :caption: `environment-based configuration` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Configuration Best Practices ----------------------------- @@ -543,59 +364,57 @@ Configuration Best Practices Always use pooling in production: -.. code-block:: python - - config = AsyncpgConfig( - pool_config={ - "dsn": "postgresql://localhost/db", - "min_size": 10, - "max_size": 20, - } - ) +.. literalinclude:: /examples/usage/usage_configuration_25.py + :language: python + :caption: `connection pooling` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 **2. Enable Caching** Enable caching to avoid recompiling SQL statements: -.. code-block:: python - - statement_config = StatementConfig( - dialect="postgres", - enable_caching=True - ) +.. literalinclude:: /examples/usage/usage_configuration_26.py + :language: python + :caption: `enable caching` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 **3. Tune Pool Sizes** Size pools based on your workload: -.. code-block:: python - - # CPU-bound workload - pool_config = {"min_size": 5, "max_size": 10} - - # I/O-bound workload - pool_config = {"min_size": 20, "max_size": 50} +.. literalinclude:: /examples/usage/usage_configuration_27.py + :language: python + :caption: `tune pool sizes` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 **4. Disable Validation in Production** For trusted, performance-critical queries: -.. code-block:: python +.. literalinclude:: /examples/usage/usage_configuration_28.py + :language: python + :caption: `no validation` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 - statement_config = StatementConfig( - dialect="postgres", - enable_validation=False, # Skip security checks - ) **5. Clean Up Resources** Always close pools on shutdown: -.. code-block:: python - - # Synchronous cleanup (automatic with atexit) - # Asynchronous cleanup (manual) - await spec.close_all_pools() +.. literalinclude:: /examples/usage/usage_configuration_29.py + :language: python + :caption: `cleanup resources` + :start-after: # start-example + :end-before: # end-example + :dedent: 2 Next Steps ---------- diff --git a/docs/usage/index.rst b/docs/usage/index.rst index 2f31063f..3ad03838 100644 --- a/docs/usage/index.rst +++ b/docs/usage/index.rst @@ -54,24 +54,30 @@ Quick Reference .. literalinclude:: /examples/usage/usage_index_1.py :language: python :caption: ``basic query execution`` - :lines: 1-23 + :start-after: # start-example + :end-before: # end-example :dedent: 4 + :no-upgrade: **Using the Query Builder** .. literalinclude:: /examples/usage/usage_index_2.py :language: python :caption: ``using the query builder`` - :lines: 1-32 + :start-after: # start-example + :end-before: # end-example :dedent: 4 + :no-upgrade: **Loading from SQL Files** .. literalinclude:: /examples/usage/usage_index_3.py :language: python :caption: ``loading from sql files`` - :lines: 1-39 + :start-after: # start-example + :end-before: # end-example :dedent: 4 + :no-upgrade: Next Steps ---------- diff --git a/pyproject.toml b/pyproject.toml index b6e7631f..a568b67c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -287,7 +287,6 @@ exclude_lines = [ addopts = ["-q", "-ra"] asyncio_default_fixture_loop_scope = "function" asyncio_mode = "auto" -python_files = ["test_*.py", "quickstart_*.py", "usage_*.py"] filterwarnings = [ "ignore::DeprecationWarning:pkg_resources.*", "ignore:pkg_resources is deprecated as an API:DeprecationWarning", @@ -331,6 +330,7 @@ markers = [ "pymysql: marks tests using pymysql", "psqlpy: marks tests using psqlpy", ] +python_files = ["test_*.py", "quickstart_*.py", "usage_*.py"] testpaths = ["tests", "docs/examples/quickstart", "docs/examples/usage"] [tool.mypy] @@ -499,7 +499,7 @@ known-first-party = ["sqlspec", "tests"] split-on-trailing-comma = false [tool.ruff.lint.per-file-ignores] -"docs/**/*.*" = ["S", "B", "DTZ", "A", "TC", "ERA", "D", "RET", "PLW0127"] +"docs/**/*.*" = ["S", "B", "DTZ", "A", "TC", "ERA", "D", "RET", "PLW0127", "PLR2004"] "docs/examples/**" = ["T201"] "sqlspec/builder/mixins/**/*.*" = ["SLF001"] "sqlspec/extensions/adk/converters.py" = ["S403"]