Skip to content

SQLTy

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")

Each 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 check validates 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 a Cursor[User] with the matching row factory; scalar fetches one typed value; row_form returns T at runtime.
  • Schema inference. Read column types from CREATE TABLE statements for PostgreSQL, SQLite and MySQL.
  • Magic codec. Files declaring # coding: sqlty are 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

  1. Install sqlty.
  2. Follow the Quickstart.
  3. Adding sqlty to an existing project? Read Migration and CI and pre-commit.

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:

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