Overview

Keyword-only parameters#

Every parameter on a generated function is keyword-only — every call site after the connection argument requires a bare *,:

await get_user(conn, id=1)   # correct
await get_user(conn, 1)      # TypeError

Example SQL#

-- @name GetUser
-- @returns :one
SELECT * FROM users WHERE id = $1;
 
-- @name ListUsers
-- @returns :many
SELECT * FROM users ORDER BY id;
 
-- @name CreateUser
-- @returns :exec
INSERT INTO users (name, email) VALUES ($1, $2);

Generated code shape#

Row types default to a frozen, slotted dataclass:

@dataclass(frozen=True, slots=True)
class User:
    """A row from the users table."""
    id: int
    name: str
    email: str

Each generated row and function carries a one-line docstring. Set row_type = "pydantic" or row_type = "msgspec" in scythe.toml to generate a Pydantic BaseModel or msgspec Struct instead — see Configuration.

Driver differences#

psycopg3 uses %(name)s named placeholders internally and a two-step fetch:

cur = await conn.execute("SELECT * FROM users WHERE id = %(id)s", {"id": id})
row = await cur.fetchone()

asyncpg uses $N positional placeholders in the underlying SQL while keeping the generated function's parameters keyword-only, and enables direct row access by column name from the driver's own result type.

Type mappings#

SQL Type Python Type
SERIAL/INTEGER int
TEXT/VARCHAR str
BOOLEAN bool
UUID uuid.UUID
NUMERIC decimal.Decimal
TIMESTAMPTZ datetime.datetime
JSON/JSONB dict[str, Any]
Nullable column T | None

Enums#

A PostgreSQL CREATE TYPE ... AS ENUM generates a Python enum.Enum subclass that also inherits from str, so generated values compare equal to their SQL string representation directly.

  • Backend Overview — how the Python backend fits into scythe's broader manifest-driven pipeline.
  • Simple CRUD Example — Python output alongside Rust, Go, and TypeScript for the same schema.

Updated

Was this page helpful?