Getting Started
From an empty project to your first typed queries. Pick your driver in the tabs below and the whole page follows your choice.
Prerequisites
You need Python 3.12 or newer (the generated code uses PEP 695 type aliases
and generics, and enum.StrEnum) and the sqlc CLI on your PATH - pick
either way to install it:
sqlc-bin ships the unmodified official
sqlc binaries as a Python package - no Go toolchain needed, pinnable like
any other dependency:
uv add --dev sqlc-binThen add the database driver you want to use:
uv add asyncpg asyncpg-stubsasyncpg has no strict typing of its own; asyncpg-stubs makes the generated annotations work under pyright and mypy.
Using pip instead of uv? Swap uv add for pip install in any command above.
1. Configure the plugin
The plugin is a WASM binary that sqlc generate downloads and runs. Create a
sqlc.yaml:
# filename: sqlc.yaml
version: "2"
plugins:
- name: python
wasm:
url: https://github.com/rayakame/sqlc-gen-better-python/releases/download/v0.7.0/sqlc-gen-better-python.wasm
sha256: 64ac923cf5f14ebc05bdeb2d1827f14d89a6f3b7180ac08964ef44ff065af5f9
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "schema.sql"
codegen:
- out: "app/db"
plugin: python
options:
package: "db"
emit_init_file: true
sql_driver: "asyncpg"
model_type: "dataclass"sha256 of the release you use - sqlc refuses to run a plugin
whose hash does not match. Each release lists its hash.model_type: "dataclass" is the default and needs no extra dependency. See
Model types for attrs, msgspec, and pydantic.
2. Describe your schema
-- filename: schema.sql
CREATE TABLE users
(
id bigint PRIMARY KEY NOT NULL,
name text NOT NULL
);3. Write your queries
Each query is a -- name: <Name> :<command> comment followed by SQL. The name
becomes the Python function, and the command decides what it returns - :one
returns a single row or None, :many returns all matching rows.
-- filename: query.sql
-- name: GetUser :one
SELECT * FROM users WHERE id = $1;
-- name: ListUsers :many
SELECT * FROM users ORDER BY name;PostgreSQL uses $1 placeholders, SQLite uses ?. Everything else is the same.
(You write $1 for psycopg too - the plugin rewrites the placeholders to
psycopg’s format at generation time.)
4. Generate
Run sqlc from the directory containing your sqlc.yaml:
sqlc generateThis writes a Python package to out (app/db above) containing models.py,
one query module per query file (query.py), and an __init__.py.
5. What you got
A model per table, and a typed function per query:
# models.py
@dataclasses.dataclass()
class User:
id_: int
name: str
# query.py
async def get_user(conn: ConnectionLike, *, id_: int) -> models.User | None:
row = await conn.fetchrow(GET_USER, id_)
if row is None:
return None
return models.User(id_=row[0], name=row[1])
def list_users(conn: ConnectionLike) -> QueryResults[models.User]: ...Two things to notice:
- The
idcolumn becameid_, becauseidis a Python builtin. See Naming and identifiers. - Parameters are keyword-only (
*,), so you callget_user(conn, id_=1).
6. Use it
:one gives you a model or None. :many returns a QueryResults, which you
can consume all at once or stream row by row:
import asyncio
import asyncpg
from app.db import query
async def main() -> None:
conn = await asyncpg.connect("postgresql://user:pass@localhost/mydb")
user = await query.get_user(conn, id_=1)
if user is not None:
print(user.name)
# every row at once
users = await query.list_users(conn)
# or stream them
async for user in query.list_users(conn):
print(user.name)
asyncio.run(main())