GraphqlBackend

Backend for integrating clearskies models with GraphQL APIs.

  1. Overview
  2. graphql_client
  3. graphql_client_name
  4. root_field
  5. api_case
  6. model_case
  7. is_collection
  8. max_relationship_depth
  9. relationship_limit
  10. use_connection_for_relationships
  11. can_create
  12. can_update
  13. can_delete
  14. can_query

Overview

Dynamically constructs GraphQL queries by introspecting the clearskies Model. Supports CRUD operations, pagination, filtering, and relationships. For pagination, GraphQL servers can often be broadly divided into two classes: those that use cursor-style pagination and those that use offset style pagination. These tend to use slightly different styles for returning records and requesting pagination data. This base class assumes cursor-based pagination. If your server uses offsets, just use the clearskies.backends.GraphqlOffsetBackend class instead. Finally, this assumes your model is using snake_case column names and that the server uses camelCase column names. As a result, it automatically changes the casing. You can adjust this behavior with the model_case and api_case configuration settings.

Basic Usage

Consider the following example application:

#!/usr/bin/env python3

import clearskies


class Product(clearskies.Model):
    id_column_name = "id"
    backend = clearskies.backends.GraphqlBackend(
        graphql_client=clearskies.clients.GraphqlClient(
            endpoint="https://example.net/gql",
            authentication=clearskies.authentication.Public(),
        )
    )

    id = clearskies.columns.String()
    category = clearskies.columns.Select(["Toy", "Auto", "Pet", "Hair"])
    name = clearskies.columns.String()
    description = clearskies.columns.String()
    price = clearskies.columns.Float()
    created_at = clearskies.columns.Created()


cli = clearskies.contexts.Cli(
    clearskies.endpoints.List(
        model_class=Product,
        readable_column_names=["id", "category", "name", "description", "price", "created_at"],
        sortable_column_names=["name", "price"],
        default_sort_column_name="name",
    ),
    classes=[Product],
)
cli()

It would construct the following graphql query:

query GetRecords($first: Int, $sortBy: String, $sortDirection: String) {
    products(first: $first, sortBy: $sortBy, sortDirection: $sortDirection) {
        nodes {
            category createdAt description id name price
        }
        pageInfo {
            endCursor
            hasNextPage
        }
    }
}

and variables:

{"first": 50, "sortBy": "name", "sortDirection": "ASC"}

using the same model, if you were to filter on some columns like so:

for product in products.where("category=Toy"):
    pass

it would produce the following query:

query GetRecords($filter_name_0: String) {
    products(name: $filter_name_0) {
        nodes {
            category createdAt description id name price
        }
        pageInfo {
            endCursor
            hasNextPage
        }
    }
}

and variables:

{"filter_name_0": "Bob"}

Relationships

If your record has relationships, the GQL backend will automatically add these to the query. Consider the following application where we fetch our products, which belong to a category and also have a price history:

#!/usr/bin/env python3

import clearskies

shared_backend = clearskies.backends.GraphqlBackend(
    graphql_client=clearskies.clients.GraphqlClient(
        endpoint="https://example.net/gql",
        authentication=clearskies.authentication.Public(),
    )
)


class Category(clearskies.Model):
    id_column_name = "id"
    backend = shared_backend

    id = clearskies.columns.String()
    name = clearskies.columns.String()


class PriceHistory(clearskies.Model):
    id_column_name = "id"
    backend = shared_backend

    id = clearskies.columns.String()
    product_id = clearskies.columns.String()
    price = clearskies.columns.Float()
    created_at = clearskies.columns.Datetime()


class Product(clearskies.Model):
    id_column_name = "id"
    backend = shared_backend

    id = clearskies.columns.String()
    category_id = clearskies.columns.BelongsToId(Category, readable_parent_columns=["id", "name"])
    category = clearskies.columns.BelongsToModel("category_id")
    name = clearskies.columns.String()
    description = clearskies.columns.String()
    price = clearskies.columns.Float()
    created_at = clearskies.columns.Datetime()

    histories = clearskies.columns.HasMany(PriceHistory)


cli = clearskies.contexts.Cli(
    lambda products: [product.id for product in products], classes=[Product]
)
cli()

The Graphql backend will execute the following query to the server:

query GetRecords {
    products {
        nodes {
            id
            name
            description
            price
            createdAt
            category {
                id
                name
            }
            histories(first: 10) {
                nodes {
                    createdAt
                    id
                    price
                    productId
                }
                pageInfo {
                    endCursor
                    hasNextPage
                }
            }
        }
        pageInfo {
            endCursor
            hasNextPage
        }
    }
}

graphql_client

Optional

The GraphQL client instance used to execute queries.

An instance of clearskies.clients.GraphqlClient that handles the connection to your GraphQL API. This is required for the backend to function. Example:

import clearskies

class Project(clearskies.Model):
    id_column_name = "id"
    backend = clearskies.backends.GraphqlBackend(
        graphql_client=clearskies.clients.GraphqlClient(
            endpoint="https://api.example.com/graphql",
            authentication=clearskies.authentication.SecretBearer(
                environment_key="API_TOKEN"
            )
        ),
        root_field="projects"
    )
    id = clearskies.columns.String()
    name = clearskies.columns.String()

graphql_client_name

Optional

The name of the GraphQL client in the DI container.

If you don’t provide a graphql_client directly, the backend will look for a client registered in the dependency injection container with this name. Defaults to “graphql_client”.

root_field

Optional

Dynamically build a GraphQL mutation.

Args:
    operation: "create", "update", or "delete"
    model: The clearskies Model
    data: Data to mutate
    id: Record ID (for update/delete)

Returns: (mutation_string, variables_dict)

api_case

Optional

The case convention used by the GraphQL API for field names.

Allowed values: “camelCase”, “snake_case”, “TitleCase” Defaults to “camelCase” which is the GraphQL standard.

model_case

Optional

The case convention used by clearskies model column names.

Allowed values: “snake_case”, “camelCase”, “TitleCase” Defaults to “snake_case” which is the Python/clearskies standard.

is_collection

Optional

Explicitly set whether the resource is a collection or singular.

Values:

  • None: Auto-detect based on field name patterns (default)
  • True: Resource is a collection (returns multiple items with pagination)
  • False: Resource is singular (returns a single object, like “currentUser”)

Auto-detection works for most cases, but you can override it if needed.

max_relationship_depth

Optional

Maximum depth for nested relationship queries.

Controls how deep the backend will traverse relationships when building GraphQL queries. For example, with max_relationship_depth=2:

  • Depth 0: Root model (Group)
  • Depth 1: First level relationships (Group.projects)
  • Depth 2: Second level relationships (Project.namespace)

This prevents infinite recursion in circular relationships. Defaults to 2.

relationship_limit

Optional

Default limit for HasMany and ManyToMany relationship collections.

When fetching related collections (e.g., projects for a group), this sets the maximum number of related records to fetch. Defaults to 10.

Example:

backend = clearskies.backends.GraphqlBackend(
    graphql_client=my_client,
    relationship_limit=50  # Fetch up to 50 related items
)

use_connection_for_relationships

Optional

Whether to use GraphQL connection pattern for relationship collections.

When True, relationship queries use the connection pattern:

projects(first: 10) {
    nodes { id name }
    pageInfo { endCursor hasNextPage }
}

When False, expects direct arrays:

projects { id name }

Defaults to True (Relay-style connections are the GraphQL standard).

can_create

Optional

Whether creating new records is allowed for this backend.

When set to False, any attempt to create a record will raise a ValueError. This can be set as a class attribute or passed to the constructor.

can_update

Optional

Whether updating existing records is allowed for this backend.

When set to False, any attempt to update a record will raise a ValueError. This can be set as a class attribute or passed to the constructor.

can_delete

Optional

Whether deleting records is allowed for this backend.

When set to False, any attempt to delete a record will raise a ValueError. This can be set as a class attribute or passed to the constructor.

can_query

Optional

Whether querying/reading records is allowed for this backend.

When set to False, any attempt to query records (via iteration, count, find, etc.) will raise a ValueError. This can be set as a class attribute or passed to the constructor.