¶
Type-safe raw SQL for Python. sqlty infers the row type of each query from
its SQL, writes it into your source as SQL[T], and checks that it stays
correct.
Beta
sqlty is in beta. APIs and configuration may change before 1.0.
From a raw query to a typed one¶
Mark a query with --sql and cast its columns. Run sqlty rewrite ., and
the file becomes what the rewritten-file lines show:
query = "--sql\nSELECT id::INTEGER, name::TEXT, created_at::TIMESTAMP FROM users"
# rewritten-file: import datetime
# rewritten-file: from sqlty import SQL
# rewritten-file: query = SQL[tuple[int, str, datetime.datetime]]("--sql\nSELECT id::INTEGER, name::TEXT, created_at::TIMESTAMP FROM users")
PlaygroundEach cast becomes a Python type: INTEGER is int, TEXT is str,
TIMESTAMP is datetime.datetime (see the
type mapping). With a
schema file, the casts are optional.
SQL[T] is a str subclass. The query still works anywhere a string does,
and editors and type checkers now know the row type.
Three commands¶
| Command | What it does |
|---|---|
sqlty rewrite . |
Wrap --sql strings in SQL[T](...) with the inferred type |
sqlty check . |
Report drift, row type mismatches, SELECT * and unwrapped queries |
sqlty strip . |
Remove the wrappers, back to plain --sql strings |
rewrite and strip only add or remove the wrapper and its import; the
string itself is never modified. For a file of plain --sql strings,
strip(rewrite(x)) == x. See the Quickstart.
Features¶
- Row types. Use
TypedDict,NamedTuple, dataclasses or Pydantic models instead of tuples;sqlty checkvalidates field count, types and names. Closed TypedDicts (PEP 728) are matched by key, and subclasses are checked against their bases' openness. - psycopg.
execute(conn, SQL[User](...))returns aCursor[User]with the matching row factory;scalarfetches one typed value;row_formreturnsTat runtime. - Schema inference. Read column types from
CREATE TABLEstatements for PostgreSQL, SQLite and MySQL. - Magic codec. Files declaring
# coding: sqltyare rewritten when imported, without a rewrite step. - Untyped queries. Mark queries sqlty cannot type as
SQL[UNTYPED], which type checkers refuse to treat as a concrete row. - Lint rules. Row type, schema, parameter and query checks, each with a configurable severity.
- Rust engine. SQL parsing, type inference and source rewriting in Rust, exposed to Python with PyO3.
Which features to use¶
| Approach | Best for |
|---|---|
| Rewrite and check | Committed code, CI, teams |
| Row types | Named fields, domain objects |
| Schema inference | Projects with CREATE TABLE schemas |
| Magic codec | Scripts and prototypes |
| psycopg helpers | psycopg 3 code that wants typed rows at runtime |
They combine: a schema plus row types plus the psycopg helpers is the most complete setup. Use cases describes the situations sqlty is built for.
Next steps¶
- Install sqlty.
- Follow the Quickstart.
- Adding sqlty to an existing project? Read Migration and CI and pre-commit.
- See the diagnostics as you type with the VS Code extension.
New to the idea? Start with Why SQLTy?. Contributing, testing and architecture are covered in DEVELOPMENT.md.
Example projects¶
Each directory in
examples/ has a
README:
01_quick_start/: rewrite, check and strip on cast queries.02_schema_inference/: types from a schema passed with--schema.03_pyproject_config/: schema, dialect and rules inpyproject.toml.04_check_and_types/: row types validated bysqlty check.05_import_style/: a customimport-style.06_param_types/: parameter types written byparam-types = true.magic_codec/: the magic codec.
Run them from a checkout:
git clone https://github.com/jirikuncar/sqlty.git
cd sqlty && uv sync --all-groups
uv run sqlty check examples/01_quick_start/
uv run sqlty check --schema examples/02_schema_inference/schema.sql examples/02_schema_inference/
uv run python -m sqlty.magic examples/magic_codec/demo.py # print the decoded source