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 sqlc on your
PATH and Python 3.12 or newer (the generated code uses PEP 695 type aliases
and generics, and enum.StrEnum).
Then install the database driver you want to use:
pip install asyncpg1. 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.6.0/sqlc-gen-better-python.wasm
sha256: 16f5affb502f2ec65ca61f6fc5ddd993449c4a4fc281996c3c9a9bc2e35b1474
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())detect_types=sqlite3.PARSE_DECLTYPES above is not needed for this schema,
but it is what makes generated converters work once your tables use dates,
decimals, booleans, or blobs. See
SQLite type conversion.