Skip to content
mplemayPublic

About

Typesafe raw SQL for Python, with a PostgreSQL driver and query checker.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

179 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rowx

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.

Quickstart

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())
  • Pool is safe to create at module level and acquires a connection per call. Use rowx.postgres.connect(dsn) for a single connection, and pool.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. one returns T, optional returns T | None, all returns list[T], and execute returns a QueryResult with rows_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/app

rowx 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.

Distributions

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-postgres

The checker uses --database-url or DATABASE_URL and does not import the application.

Documentation

  • 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.

About

Typesafe raw SQL for Python, with a PostgreSQL driver and query checker.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages