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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions docs/examples/usage/usage_sql_files_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from pathlib import Path

from sqlspec import SQLFileLoader

__all__ = ("create_loader", "test_loader_loads_queries")


def create_loader(tmp_path: Path) -> tuple[SQLFileLoader, list[str]]:
sql_dir = tmp_path / "sql"
sql_dir.mkdir()

sql_file_1 = sql_dir / "queries" / "users.sql"
sql_file_1.parent.mkdir()
sql_file_1.write_text("""
-- name: get_user_by_id)
SELECT * FROM users WHERE id = :user_id;
-- name: list_active_users
SELECT * FROM users WHERE active = 1;
-- name: create_user
INSERT INTO users (name, email) VALUES (:name, :email);
""")
# start-example
from sqlspec.loader import SQLFileLoader

# Create loader
loader = SQLFileLoader()

# Load SQL files
loader.load_sql(sql_file_1)

# Or load from a directory
loader.load_sql(sql_dir)

# List available queries
queries = loader.list_queries()
print(queries) # ['get_user_by_id', 'list_active_users', 'create_user', ...]
# end-example
return loader, queries


def test_loader_loads_queries(tmp_path: Path) -> None:

loader, queries = create_loader(tmp_path)
# Dummy asserts for doc example
assert hasattr(loader, "load_sql")
assert hasattr(loader, "list_queries")
assert isinstance(queries, list)
29 changes: 29 additions & 0 deletions docs/examples/usage/usage_sql_files_10.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pathlib import Path

__all__ = ("test_integration_with_sqlspec",)


def test_integration_with_sqlspec(tmp_path: Path) -> None:
tmp_sql_dir = tmp_path / "sql"
tmp_sql_dir.mkdir()
sql_file = tmp_sql_dir / "queries.sql"
sql_file.write_text("""
-- name: get_user_by_id
SELECT * FROM users WHERE id = :user_id;
""")
# start-example
from sqlspec import SQLSpec
from sqlspec.loader import SQLFileLoader

# Create loader
loader = SQLFileLoader()
loader.load_sql(tmp_path / "sql/")

# Create SQLSpec with loader
spec = SQLSpec(loader=loader)

# Access loader via SQLSpec
user_query = spec._sql_loader.get_sql("get_user_by_id")
# end-example
# Dummy asserts for doc example
assert user_query is not None
38 changes: 38 additions & 0 deletions docs/examples/usage/usage_sql_files_11.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from pathlib import Path

from docs.examples.usage.usage_sql_files_1 import create_loader
from sqlspec import SQLSpec
from sqlspec.adapters.sqlite import SqliteConfig

__all__ = ("test_type_safe_query_execution",)


def test_type_safe_query_execution(tmp_path: Path) -> None:
loader, _queries = create_loader(tmp_path)
# start-example

from pydantic import BaseModel

class User(BaseModel):
id: int
username: str
email: str

# Load and execute with type safety
query = loader.get_sql("get_user_by_id")

spec = SQLSpec(loader=loader)
config = SqliteConfig(pool_config={"database": ":memory:"})

with spec.provide_session(config) as session:
session.execute("""CREATE TABLE users ( id INTEGER PRIMARY KEY, username TEXT, email TEXT)""")
session.execute(
""" INSERT INTO users (id, username, email) VALUES (1, 'alice', 'alice@example.com'), (2, 'bob', 'bob@example.com');"""
)
user: User = session.select_one(query, user_id=1, schema_type=User)
# end-example
# Dummy asserts for doc example
assert user.id == 1
assert user.username == "alice"
assert user.email == "alice@example.com"
assert query is not None
54 changes: 54 additions & 0 deletions docs/examples/usage/usage_sql_files_12.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from pathlib import Path

__all__ = ("test_user_management_example",)


def test_user_management_example(tmp_path: Path) -> None:
user_sql_path = tmp_path / "sql"
user_sql_path.mkdir(parents=True, exist_ok=True)
user_sql_file = user_sql_path / "users.sql"
user_sql_file.write_text(
"""-- name: create_user
INSERT INTO users (username, email, password_hash) VALUES (:username, :email, :password_hash) RETURNING id, username, email;
-- name: get_user
SELECT id, username, email FROM users WHERE id = :user_id;
-- name: list_users
SELECT id, username, email FROM users WHERE (:status IS NULL OR active = :status) LIMIT :limit OFFSET :offset;
"""
)
# start-example
# Python code
from sqlspec import SQLSpec
from sqlspec.adapters.sqlite import SqliteConfig
from sqlspec.loader import SQLFileLoader

loader = SQLFileLoader()
loader.load_sql(tmp_path / "sql/users.sql")

spec = SQLSpec()
config = SqliteConfig()
spec.add_config(config)

with spec.provide_session(config) as session:
session.execute(
"""CREATE TABLE users ( id INTEGER PRIMARY KEY, username TEXT, email TEXT, password_hash TEXT, active BOOLEAN DEFAULT 1)"""
)
# Create user
create_query = loader.get_sql("create_user")
result = session.execute(
create_query, username="irma", email="irma@example.com", password_hash="hashed_password"
)
user = result.one()
user_id = user["id"]

# Get user
get_query = loader.get_sql("get_user")
user = session.execute(get_query, user_id=user_id).one()

# List users
list_query = loader.get_sql("list_users")
session.execute(list_query, status=True, limit=10, offset=0).data
# end-example
# Dummy asserts for doc example
assert hasattr(loader, "load_sql")
assert hasattr(spec, "add_config")
64 changes: 64 additions & 0 deletions docs/examples/usage/usage_sql_files_13.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from pathlib import Path

__all__ = ("test_analytics_queries_example",)


def test_analytics_queries_example(tmp_path: Path) -> None:
from docs.examples.usage.usage_sql_files_1 import create_loader
from sqlspec import SQLSpec
from sqlspec.adapters.sqlite import SqliteConfig

loader, _queries = create_loader(tmp_path)
sql_analytics_path = tmp_path / "sql"
sql_analytics_path.mkdir(parents=True, exist_ok=True)
sql_analytics_file = sql_analytics_path / "analytics.sql"
sql_analytics_file.write_text(
"""-- name: daily_sales
SELECT order_date, SUM(total_amount) AS total_sales
FROM orders
WHERE order_date BETWEEN :start_date AND :end_date
GROUP BY order_date;
-- name: top_products
SELECT product_id, SUM(quantity) AS total_sold
FROM order_items
WHERE order_date >= :start_date
GROUP BY product_id
ORDER BY total_sold DESC
LIMIT :limit;
"""
)
# start-example
import datetime

# Load analytics queries
loader.load_sql(tmp_path / "sql/analytics.sql")

# Run daily sales report
sales_query = loader.get_sql("daily_sales")
config = SqliteConfig()
spec = SQLSpec()
spec.add_config(config)
with spec.provide_session(config) as session:
session.execute("""CREATE TABLE orders ( order_id INTEGER PRIMARY KEY, order_date DATE, total_amount REAL);""")
session.execute("""
CREATE TABLE order_items ( order_item_id INTEGER PRIMARY KEY, order_id INTEGER, product_id INTEGER, quantity INTEGER, order_date DATE);""")

# Insert sample data
session.execute("""
INSERT INTO orders (order_id, order_date, total_amount) VALUES
(1, '2025-01-05', 150.00),
(2, '2025-01-15', 200.00),
(3, '2025-01-20', 250.00);
""")
session.execute("""
INSERT INTO order_items (order_item_id, order_id, product_id, quantity, order_date) VALUES
(1, 1, 101, 2, '2025-01-05'),
(2, 2, 102, 3, '2025-01-15'),
(3, 3, 101, 1, '2025-01-20');
""")
session.execute(sales_query, start_date=datetime.date(2025, 1, 1), end_date=datetime.date(2025, 2, 1)).data

# Top products
products_query = loader.get_sql("top_products")
session.execute(products_query, start_date=datetime.date(2025, 1, 1), limit=10).data
# end-example
79 changes: 79 additions & 0 deletions docs/examples/usage/usage_sql_files_14.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from pathlib import Path

from pytest_databases.docker.postgres import PostgresService

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

__all__ = ("test_multi_database_setup_example",)


async def test_multi_database_setup_example(tmp_path: Path, postgres_service: PostgresService) -> None:
user_sql_path_pg = tmp_path / "sql" / "postgres"
user_sql_path_pg.mkdir(parents=True, exist_ok=True)
user_sql_file_pg = user_sql_path_pg / "users.sql"
user_sql_file_pg.write_text(
"""-- name: upsert_user
INSERT INTO users_sf1 (id, username, email) VALUES (:id, :username, :email)
ON CONFLICT (id) DO UPDATE SET username = EXCLUDED.username, email = EXCLUDED.email;
"""
)
user_sql_path_sqlite = tmp_path / "sql" / "sqlite"
user_sql_path_sqlite.mkdir(parents=True, exist_ok=True)
user_sql_file_sqlite = user_sql_path_sqlite / "users.sql"
user_sql_file_sqlite.write_text(
"""-- name: get_user
SELECT id, username, email FROM users_sf1 WHERE id = :user_id;
"""
)
shared_sql_path = tmp_path / "sql" / "shared"
shared_sql_path.mkdir(parents=True, exist_ok=True)
shared_sql_file = shared_sql_path / "common.sql"
shared_sql_file.write_text(
"""-- name: delete_user
DELETE FROM users_sf1 WHERE id = :user_id;
"""
)
params = {"id": 1, "username": "john_doe", "email": "jd@example.com"}

# start-example
# Different SQL files for different databases
loader = SQLFileLoader()
loader.load_sql(tmp_path / "sql/postgres/", tmp_path / "sql/sqlite/", tmp_path / "sql/shared/")

# Queries automatically select correct dialect
pg_query = loader.get_sql("upsert_user") # Uses Postgres ON CONFLICT
sqlite_query = loader.get_sql("get_user") # Uses shared query

from sqlspec import SQLSpec

spec = SQLSpec()
postgres_config = AsyncpgConfig(
pool_config={
"user": postgres_service.user,
"password": postgres_service.password,
"host": postgres_service.host,
"port": postgres_service.port,
"database": postgres_service.database,
}
)
sqlite_config = SqliteConfig()
# Execute on appropriate database
async with spec.provide_session(postgres_config) as pg_session:
await pg_session.execute("""CREATE TABLE users_sf1 ( id INTEGER PRIMARY KEY, username TEXT, email TEXT)""")
await pg_session.execute(
""" INSERT INTO users_sf1 (id, username, email) VALUES (1, 'old_name', 'old@example.com');"""
)

await pg_session.execute(pg_query, **params)

with spec.provide_session(sqlite_config) as sqlite_session:
sqlite_session.execute("""CREATE TABLE users_sf1 ( id INTEGER PRIMARY KEY, username TEXT, email TEXT)""")
sqlite_session.execute(
""" INSERT INTO users_sf1 (id, username, email) VALUES (1, 'john_doe', 'jd@example.com');"""
)
sqlite_session.execute(sqlite_query, user_id=1)
# end-example
# Dummy asserts for doc example
assert hasattr(loader, "load_sql")
20 changes: 20 additions & 0 deletions docs/examples/usage/usage_sql_files_15.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from pathlib import Path

from docs.examples.usage.usage_sql_files_1 import create_loader
from sqlspec.exceptions import SQLFileNotFoundError

__all__ = ("test_query_not_found",)


def test_query_not_found(tmp_path: Path) -> None:
loader, _queries = create_loader(tmp_path)
# start-example
try:
loader.get_sql("nonexistent_query")
except SQLFileNotFoundError:
print("Query not found. Available queries:")
print(loader.list_queries())
# end-example
# Dummy asserts for doc example
assert hasattr(loader, "get_sql")
assert hasattr(loader, "list_queries")
21 changes: 21 additions & 0 deletions docs/examples/usage/usage_sql_files_16.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from pathlib import Path

from docs.examples.usage.usage_sql_files_1 import create_loader

__all__ = ("test_file_load_errors",)


def test_file_load_errors(tmp_path: Path) -> None:
loader, _queries = create_loader(tmp_path)
# start-example
from sqlspec.exceptions import SQLFileNotFoundError, SQLFileParseError

try:
loader.load_sql("sql/queries.sql")
except SQLFileNotFoundError as e:
print(f"File not found: {e}")
except SQLFileParseError as e:
print(f"Failed to parse SQL file: {e}")
# end-example
# Dummy asserts for doc example
assert hasattr(loader, "load_sql")
22 changes: 22 additions & 0 deletions docs/examples/usage/usage_sql_files_17.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from pathlib import Path

from docs.examples.usage.usage_sql_files_1 import create_loader

__all__ = ("test_debugging_loaded_queries",)


def test_debugging_loaded_queries(tmp_path: Path) -> None:
loader, _queries = create_loader(tmp_path)
# start-example
# Print query SQL
query = loader.get_sql("create_user")
print(f"SQL: {query}")
print(f"Parameters: {query.parameters}")

# Inspect file metadata
file_info = loader.get_file_for_query("create_user")
print(f"Loaded from: {file_info.path}")
# end-example
# Dummy asserts for doc example
assert hasattr(loader, "get_sql")
assert query is not None
Loading