GraphqlBackend
Backend for integrating clearskies models with GraphQL APIs.
- Overview
- graphql_client
- graphql_client_name
- root_field
- api_case
- model_case
- is_collection
- max_relationship_depth
- relationship_limit
- use_connection_for_relationships
- can_create
- can_update
- can_delete
- 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.