Skip to content
Getting Started

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 asyncpg

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.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"
Always pin the 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 generate

This 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 id column became id_, because id is a Python builtin. See Naming and identifiers.
  • Parameters are keyword-only (*,), so you call get_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())
The 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.

Next steps

  • Work through the Guide - configuration, drivers, model types, writing queries, and every feature, each with real generated output.
  • Look up specifics in the Reference: the full option list, SQL-to-Python type mappings, and per-driver support.