SQL File Loader¶
SQLSpec includes a SQL file loader that keeps your queries in .sql files and
exposes them through the registry. Load directories or individual files, then
execute named queries with spec.get_sql().
Load SQL Files¶
load SQL files¶from sqlspec import SQLSpec
from sqlspec.adapters.sqlite import SqliteConfig
sql_file = tmp_path / "queries.sql"
sql_file.write_text("-- name: list_teams\nselect id, name from teams order by id;\n")
spec = SQLSpec()
config = spec.add_config(SqliteConfig(connection_config={"database": ":memory:"}))
spec.load_sql_files(sql_file)
with spec.provide_session(config) as session:
session.execute("create table teams (id integer primary key, name text)")
session.execute("insert into teams (name) values ('Litestar'), ('SQLSpec')")
result = session.execute(spec.get_sql("list_teams"))
rows = result.all()
Named Queries¶
named SQL¶from sqlspec import SQLSpec
from sqlspec.adapters.sqlite import SqliteConfig
spec = SQLSpec()
config = spec.add_config(SqliteConfig(connection_config={"database": ":memory:"}))
spec.add_named_sql("find_team", "select id, name from teams where name = :name")
with spec.provide_session(config) as session:
session.execute("create table teams (id integer primary key, name text)")
session.execute("insert into teams (name) values ('Litestar'), ('SQLSpec')")
result = session.execute(spec.get_sql("find_team"), {"name": "SQLSpec"})
row = result.one()
Dynamic WHERE Chaining¶
SQL objects returned by get_sql() support .where() chaining. This lets you
start with a base query and add conditions dynamically without string concatenation.
dynamic where chaining¶from sqlspec import SQLSpec
from sqlspec.adapters.sqlite import SqliteConfig
spec = SQLSpec()
config = spec.add_config(SqliteConfig(connection_config={"database": ":memory:"}))
# Register a base query
spec.add_named_sql("list_users", "select id, name, status from users")
with spec.provide_session(config) as session:
session.execute("create table users (id integer primary key, name text, status text)")
session.execute(
"insert into users (name, status) values ('Alice', 'active'), ('Bob', 'inactive'), ('Charlie', 'active')"
)
# Get the base SQL object and chain .where() calls
base_query = spec.get_sql("list_users")
# Add a WHERE clause dynamically
active_query = base_query.where("status = 'active'")
active_users = session.select(active_query)
print(active_users) # [{"id": 1, ...}, {"id": 3, ...}]
# Use typed where helpers
filtered = base_query.where_eq("name", "Bob")
bob = session.select(filtered)
print(bob) # [{"id": 2, "name": "Bob", "status": "inactive"}]
# Chain multiple where conditions (AND)
specific = base_query.where_eq("status", "active").where_like("name", "A%")
result = session.select(specific)
print(result) # [{"id": 1, "name": "Alice", "status": "active"}]
Available where helpers:
.where(condition)– raw SQL condition string.where_eq(column, value)– equality.where_neq(column, value)– inequality.where_lt(column, value)/.where_lte(column, value)– less than.where_gt(column, value)/.where_gte(column, value)– greater than.where_like(column, pattern)/.where_ilike(column, pattern)– pattern matching.where_in(column, values)/.where_not_in(column, values)– set membership.where_is_null(column)/.where_is_not_null(column)– null checks.where_between(column, low, high)– range
Each call returns a new SQL object (immutable chaining).
How Query Names Work¶
Name queries with
-- name: query_namecomments.SQLSpec normalizes names to snake_case for Python access.
Add
-- dialect: postgreson the first line of a block to bind SQL to a dialect.Directory structures become namespaces when you load directories (
reports/daily.sql->reports.<query>).