SQLx for Python 3.14+. Typesafe raw SQL, with no ORM: you write SQL as a PEP 750
t-string and say what a row looks like. rowx check prepares each checked query against a live Postgres 18 database.
query_unchecked SQL is not described.
Install the checker and PostgreSQL backend with uv add "rowx[postgres]".
Free-threaded CPython 3.14t cannot install the abi3 wheels, so pip builds rowx-postgres from the source distribution.
import asyncio
from rowx import query
from rowx.postgres import Pool
type UserRow = tuple[int, str, str | None] # id, name, email
async def main() -> None:
async with Pool(DSN, max_size=10) as pool:
name = "alice"
user = await query[UserRow](t"SELECT id, name, email FROM users WHERE name = {name}").one(pool)
assert user[1] == "alice"
names = await query[str](t"SELECT name FROM users ORDER BY name").all(pool)
assert names == ["alice", "bob", "carol"]
ids = [user[0]]
updated = await query(t"UPDATE users SET email = {None} WHERE id = ANY({ids})").execute(pool)
assert updated.rows_affected == 1
asyncio.run(main())Poolis safe to create at module level and acquires a connection per call. Userowx.postgres.connect(dsn)for a single connection, andpool.transaction()for transactions.- Interpolations (
{name}) are always bound as server-side parameters. They are never pasted into the SQL text. query[T]states the row type.onereturnsT,optionalreturnsT | None,allreturnslist[T], andexecutereturns aQueryResultwithrows_affected.- Type checkers (pyright, mypy, ty) infer all of this with no plugin.
Rows can also be a dataclass, NamedTuple, TypedDict or pydantic model, matched to the columns by name:
import asyncio
from dataclasses import dataclass
from rowx import query
from rowx.postgres import connect
@dataclass(slots=True)
class User:
id: int
name: str
email: str | None
async def main() -> None:
async with await connect(DSN) as conn:
user = await query[User](t"SELECT id, name, email FROM users WHERE name = {'alice'}").one(conn)
assert user.name == "alice"
asyncio.run(main())Check the queries against your database:
rowx check src --database-url postgresql://app@localhost/approwx check exits 0 when it reports no diagnostics, including when no queries were found, 1 when it reports one or
more diagnostics, and 2 on a bad invocation or config, an unreachable database, a describe failure that is not an SQL
error, a drift or scratch failure, or an internal error.
No server? uv add "rowx[embedded]" adds an in-process Postgres 18: use rowx.embedded.Database() in code, or an
embedded:// database URL for rowx check, rowx migrate and the pytest plugin, with no Docker. It is for
development, checking and tests, and it is not for production. See Embedded Postgres.
A type mismatch is reported like this:
error[type-mismatch]: column 2 "name": column type is `int` but the annotation is `str`
--> src/app.py:14:22
|
14 | query[tuple[int, str]](t"SELECT id, name FROM users")
| ^^^ element 2
The rules are in the checker guide. The checker reference is the contract for the exit codes.
rowx is the optional CLI. With rowx[postgres], import the query API from rowx and the driver from
rowx.postgres. from rowx import query and from rowx_core import query are the same object. Pool imported
from rowx is the executor protocol, not a connection pool. rowx.core re-exports only the rowx_core names that
rowx and rowx.errors do not already export.
rowx-core (rowx_core) has no runtime dependencies. rowx-postgres depends only on matching rowx-core.
rowx.postgres re-exports that driver and needs the postgres extra.
A standalone application installs rowx-core and rowx-postgres and does not get the rowx command. That pair
imports the driver as rowx_postgres:
uv add rowx-core rowx-postgresThe checker uses --database-url or DATABASE_URL and does not import the application.
- User guide: row types (tuples, scalars and named classes), pools, transactions, migrations, writing queries, overrides, the checker, CI and poolers.
- Why rowx: where SQL lives, and how a live database check compares with other libraries.
- Changelog: what changed in each release.
- Packages: the four distributions and the canonical imports.
- Embedded Postgres:
rowx[embedded],Database,embedded://URLs, the one-session rule, checking, migrating and testing without a server, and its limits (reference). - Postgres type mapping: what each Postgres type decodes to and which Python types can be bound.
- Known limits: where nullability inference is less precise than the database.
- Reference: the full specification.
- use-rowx skill: agent guidance for queries, the package split, and
rowx check.