Skip to content

FraiseQL for Python Teams

Python teams get the richest FraiseQL authoring experience. The Python SDK is the only one where every decorator argument is fully supported in the released version — and the only one with REST annotation support (v2.1.0).

The Python SDK is compile-time only. You write your schema in Python using decorators, run fraiseql compile to produce schema.compiled.json, and the Rust runtime executes every query and mutation. No Python runs at request time.

schema.py → fraiseql compile → schema.compiled.json → Rust runtime → GraphQL + REST

This means:

  • No GIL, no async overhead, no Python memory pressure at query time
  • All auth, rate limiting, and RBAC enforced in Rust
  • Python types map directly to GraphQL types — no separate SDL to maintain
import fraiseql
from fraiseql.scalars import ID, DateTime
from uuid import UUID
@fraiseql.type
class Post:
id: ID
title: str
body: str
published_at: DateTime | None
author_id: ID
@fraiseql.query
def posts(
limit: int = 20,
offset: int = 0,
is_published: bool | None = None,
) -> list[Post]:
return fraiseql.config(sql_source="v_post")
@fraiseql.query
def post(id: UUID) -> Post | None:
return fraiseql.config(sql_source="v_post")

sql_source goes inside fraiseql.config() in the function body — it names the PostgreSQL view that backs the query.

@fraiseql.mutation
def create_post(title: str, body: str) -> Post:
return fraiseql.config(
sql_source="fn_create_post",
operation="CREATE",
)
@fraiseql.error
class CreatePostError:
code: str
message: str

Use inject= to pass verified JWT claims to SQL without exposing them as GraphQL arguments:

@fraiseql.query(
inject={"author_id": "jwt:sub"}
)
def my_posts(limit: int = 20) -> list[Post]:
return fraiseql.config(sql_source="v_post")

author_id is not in the GraphQL schema — clients cannot supply or override it. The Rust runtime injects it from the verified JWT on every request.

Expose any query or mutation as a REST endpoint by adding rest_path and rest_method:

@fraiseql.query(
rest_path="/posts",
rest_method="GET",
)
def posts(limit: int = 20, offset: int = 0) -> list[Post]:
return fraiseql.config(sql_source="v_post")
@fraiseql.mutation(
rest_path="/posts",
rest_method="POST",
)
def create_post(title: str, body: str) -> Post:
return fraiseql.config(
sql_source="fn_create_post",
operation="CREATE",
)

FraiseQL compiles these annotations into REST routes alongside GraphQL. The same auth, rate limiting, and RBAC apply to both transports.

For teams that prefer TOML over Python decorators, SpecQL generates FraiseQL Python schemas from a TOML-first specification format.

SpecQL includes 56 semantic scalar types (Email, IBAN, Money, PhoneNumber, etc.) and validation rules that map directly to FraiseQL’s Elo validation language. You write the schema once in TOML — SpecQL generates the Python file.

# schema.toml (SpecQL format)
[types.Post]
fields.id = { type = "UUID", required = true }
fields.title = { type = "ShortText", required = true }
fields.body = { type = "LongText", required = true }
fields.published_at = { type = "Timestamp" }

See the SpecQL GitHub repository for details.

  1. Install FraiseQL

    Terminal window
    curl -fsSL https://install.fraiseql.dev | sh
    fraiseql --version
  2. Clone the minimal starter

    Terminal window
    git clone https://github.com/fraiseql/fraiseql-starter-minimal
    cd fraiseql-starter-minimal
    cp .env.example .env
    docker compose up
  3. Edit schema.py to add your types and queries

  4. Compile

    Terminal window
    fraiseql compile
  5. Run

    Terminal window
    fraiseql run

Decorators Reference

Full reference for @fraiseql.type, @fraiseql.query, @fraiseql.mutation, and all decorator arguments. Decorators

REST Transport

Configure rest_path annotations and the OpenAPI spec. REST Transport

Authentication

JWT verification, OIDC, and the inject= pattern. Authentication

Starter Templates

Clone a working Python project and start from there. Starters