Skip to content

Auto-Inference in FraiseQL

FraiseQL automatically infers several parameters to reduce boilerplate and make your code cleaner. This guide explains what's auto-inferred and when you need to be explicit.

Overview

FraiseQL auto-infers: 1. field_name - GraphQL field name from function name 2. info - GraphQL info parameter from context 3. where, order_by, limit, offset - Added automatically to list queries 4. Mutation success fields - status, message, updated_fields, id


1. field_name Auto-Inference

How It Works

When you call db.find() or db.find_one(), FraiseQL automatically extracts the GraphQL field name from info.field_name:

@fraiseql.query
async def users(info) -> list[User]:
    db = info.context["db"]
    # field_name is automatically "users" (from function name)
    return await db.find("v_user")

GraphQL Response:

{
  "data": {
    "users": [...]  // ← Matches function name
  }
}

When Field Name Matters

The field_name parameter wraps the response in the GraphQL structure:

# Function name: users
# field_name inferred: "users"
# Response: {"data": {"users": [...]}}

# Function name: activeUsers
# field_name inferred: "activeUsers"
# Response: {"data": {"activeUsers": [...]}}

Manual Override (Rarely Needed)

You can explicitly set field_name if needed:

@fraiseql.query
async def get_all_users(info) -> list[User]:
    db = info.context["db"]
    # Explicitly set field_name to "users" instead of "get_all_users"
    return await db.find("v_user", field_name="users")

When to override: - Function name doesn't match desired GraphQL field name - Using helper functions that aren't direct GraphQL resolvers


2. info Parameter Auto-Injection

Current State (v1.9.0)

The info parameter must be explicitly passed:

@fraiseql.query
async def users(info) -> list[User]:
    db = info.context["db"]
    # info parameter is used but must be explicitly passed
    return await db.find("v_user")

Future State (After PR #200)

The info parameter will be auto-injected from the GraphQL context:

@fraiseql.query
async def users() -> list[User]:  # No info parameter needed!
    db = context["db"]
    return await db.find("v_user")

Note: This documentation describes the future state. Check the current version to see if PR #200 is merged.


3. Query Parameters Auto-Wiring

Automatic WHERE, ORDER BY, LIMIT, OFFSET

For list queries, FraiseQL automatically adds these parameters:

@fraiseql.query
async def users(info) -> list[User]:
    db = info.context["db"]
    return await db.find("v_user")

GraphQL automatically supports:

query {
  users(
    where: {isActive: true}
    orderBy: {field: "createdAt", direction: DESC}
    limit: 10
    offset: 20
  ) {
    id
    name
    email
  }
}

How It Works

  1. FraiseQL detects list[T] return type
  2. Automatically adds where, orderBy, limit, offset to GraphQL schema
  3. Passes these parameters to db.find() as **kwargs

Manual Parameter Handling

You can accept and customize these parameters:

@fraiseql.query
async def users(
    info,
    where: dict | None = None,
    limit: int = 100
) -> list[User]:
    db = info.context["db"]

    # Customize or validate parameters
    if limit > 1000:
        limit = 1000  # Cap at 1000

    return await db.find("v_user", where=where, limit=limit)

4. Mutation Success Fields Auto-Injection

@success Decorator

The @success decorator automatically adds standard fields:

from fraiseql.types import ID

@fraiseql.success
class UserCreated:
    user: User  # Your custom field
    # Auto-injected fields (don't add these manually):
    # status: str = "success"
    # message: str | None = None
    # updated_fields: list[str] | None = None
    # id: ID | None = None

GraphQL Response:

{
  "data": {
    "createUser": {
      "status": "success",       // ← Auto-injected
      "message": null,           // ← Auto-injected
      "user": {...},             // ← Your field
      "updatedFields": null,     // ← Auto-injected
      "id": null                 // ← Auto-injected
    }
  }
}

Customizing Auto-Injected Fields

You can override default values:

@fraiseql.success
class UserCreated:
    user: User
    message: str = "User created successfully"  # Custom default
    # Other fields still auto-injected

When to Use Auto-Injection

✅ Use auto-injection for: - Standard CRUD operations - Simple success/error responses - Following GraphQL best practices

❌ Don't use auto-injection when: - You need full control over response structure - Building non-standard mutation responses


5. Comparison with Other Frameworks

Strawberry (Python)

import strawberry

@strawberry.type
class Query:
    @strawberry.field
    async def users(self) -> list[User]:
        # Function name "users" → GraphQL field name
        return await db.query(User).all()

FraiseQL equivalent:

@fraiseql.query
async def users(info) -> list[User]:
    db = info.context["db"]
    # Same auto-inference from function name
    return await db.find("v_user")

GraphQL-Python (Graphene)

import graphene

class Query(graphene.ObjectType):
    users = graphene.List(User)

    def resolve_users(self, info):
        # Field name from attribute "users"
        # Resolver is "resolve_" + field_name
        return User.objects.all()

FraiseQL equivalent:

@fraiseql.query
async def users(info) -> list[User]:
    # Simpler - no separate resolver pattern
    db = info.context["db"]
    return await db.find("v_user")

Ariadne (Python)

from ariadne import QueryType

query = QueryType()

@query.field("users")  # Must specify field name
async def resolve_users(obj, info):
    return await get_users()

FraiseQL equivalent:

@fraiseql.query
async def users(info) -> list[User]:
    # No need to specify field name twice!
    db = info.context["db"]
    return await db.find("v_user")


Auto-Inference Decision Matrix

Feature Auto-Inferred? When to Override
field_name ✅ Yes (from function name) Helper functions, non-standard naming
info parameter ⚠️ Soon (PR #200) Currently must pass explicitly
where/orderBy/limit/offset ✅ Yes (for list queries) Custom validation, business logic
@success fields ✅ Yes (status, message, etc.) Non-standard mutation responses

Best Practices

1. Trust Auto-Inference

✅ DO:

@fraiseql.query
async def users(info) -> list[User]:
    db = info.context["db"]
    return await db.find("v_user")  # Clean, simple

❌ DON'T:

@fraiseql.query
async def users(info) -> list[User]:
    db = info.context["db"]
    # Unnecessary - field_name is auto-inferred
    return await db.find("v_user", field_name="users", info=info)

2. Use Explicit Parameters When Needed

✅ DO:

@fraiseql.query
async def users(info, limit: int = 100) -> list[User]:
    db = info.context["db"]
    # Explicit limit parameter for validation
    if limit > 1000:
        raise ValueError("Limit cannot exceed 1000")
    return await db.find("v_user", limit=limit)

3. Follow Naming Conventions

✅ DO:

from fraiseql.types import ID

@fraiseql.query
async def users(info) -> list[User]:  # Plural for lists
    ...

@fraiseql.query
async def user(info, id: ID) -> User | None:  # Singular for single items
    ...

❌ DON'T:

@fraiseql.query
async def get_user_list(info) -> list[User]:  # Non-standard naming
    return await db.find("v_user", field_name="users")  # Now needs override


Debugging Auto-Inference

Check What Field Name Was Used

@fraiseql.query
async def users(info) -> list[User]:
    db = info.context["db"]
    print(f"Field name: {info.field_name}")  # Debug: See what was inferred
    return await db.find("v_user")

Verify GraphQL Response Structure

Use GraphQL Playground to inspect the response:

query {
  users {
    id
    name
  }
}

Expected response structure:

{
  "data": {
    "users": [...]  // ← Should match function name
  }
}


See Also