GraphQL Framework for Python providing declarative schema construction and query execution
—
Graphene provides complete GraphQL Relay specification support, enabling standardized patterns for global object identification, cursor-based pagination, and mutations with client IDs. This includes the Node interface for globally unique objects, connection-based pagination for efficient data fetching, and mutation patterns that support optimistic updates.
Implements the Relay Node interface for global object identification across your GraphQL schema.
class Node(Interface):
"""
Relay Node interface implementation with global ID support.
Features:
- Automatic ID field injection
- Global ID encoding/decoding
- Type validation for node objects
- Integration with Relay client libraries
Class Methods:
Field(*args, **kwargs): Creates NodeField for single node queries
get_node_from_global_id(info, global_id, only_type=None): Node retrieval
to_global_id(type_, id): Global ID creation
resolve_global_id(info, global_id): Global ID parsing
Usage:
class User(graphene.ObjectType):
class Meta:
interfaces = (Node,)
name = graphene.String()
email = graphene.String()
@classmethod
def get_node(cls, info, id):
return get_user_by_id(id)
"""
class GlobalID(Field):
"""
Automatic global ID field generation for Node types.
Parameters:
node: Node type this ID represents
parent_type: Parent type for ID resolution
required: Whether field is NonNull
global_id_type: Custom GlobalIDType implementation
Features:
- Automatic ID resolution and encoding
- Configurable ID type implementations
- Integration with Relay pagination
Usage:
class User(graphene.ObjectType):
class Meta:
interfaces = (Node,)
id = GlobalID()
name = graphene.String()
"""
def is_node(objecttype):
"""
Check if ObjectType implements Node interface.
Args:
objecttype: GraphQL ObjectType to check
Returns:
bool: True if type implements Node interface
Usage:
if graphene.is_node(User):
print("User implements Node interface")
"""Flexible global ID implementations supporting different encoding strategies.
class BaseGlobalIDType:
"""
Base class for global ID type implementations.
Attributes:
graphene_type: GraphQL scalar type used (default: ID)
Abstract Methods:
resolve_global_id(global_id): Parse global ID to (type, id) tuple
to_global_id(type_, id): Encode type and ID to global ID string
Usage:
class CustomGlobalIDType(BaseGlobalIDType):
def resolve_global_id(self, global_id):
parts = global_id.split('.')
return (parts[0], parts[1])
def to_global_id(self, type_, id):
return f"{type_}.{id}"
"""
class DefaultGlobalIDType(BaseGlobalIDType):
"""
Base64-encoded "TypeName:id" format (Relay standard).
Features:
- Standard Relay specification compliance
- Base64 encoding for opacity
- Type safety with parsing validation
- Compatible with Relay client libraries
Methods:
Uses graphql-relay's to_global_id/from_global_id functions
Format: base64("TypeName:id")
Example: "VXNlcjox" -> "User:1"
"""
class SimpleGlobalIDType(BaseGlobalIDType):
"""
Pass-through ID type that uses raw ID values.
Warning: User responsible for ensuring global uniqueness
Features:
- No encoding/decoding overhead
- Direct ID passthrough
- Requires globally unique IDs
Use Case: When IDs are already globally unique (UUIDs, etc.)
"""
class UUIDGlobalIDType(BaseGlobalIDType):
"""
UUID-based global IDs with native UUID support.
Features:
- Uses UUID GraphQL type instead of ID
- Inherently global uniqueness
- No type prefix needed
- Automatic UUID validation
Attributes:
graphene_type: UUID (instead of ID)
"""Cursor-based pagination following the Relay Connection specification.
class Connection(ObjectType):
"""
Relay cursor-based pagination container.
Features:
- Automatic Edge class generation
- PageInfo integration for pagination metadata
- Configurable strict typing
- Cursor-based navigation
Meta Options:
node: Required ObjectType that this connection contains
name: Custom connection name (defaults to {Node}Connection)
strict_types: Enforce strict edge/connection typing
Generated Fields:
edges: List of Edge objects containing nodes and cursors
page_info: PageInfo object with pagination metadata
Usage:
class UserConnection(Connection):
class Meta:
node = User
# Automatically generates:
# - UserEdge with node and cursor fields
# - edges field returning [UserEdge]
# - page_info field returning PageInfo
"""
class ConnectionField(Field):
"""
Field that automatically handles Relay-style pagination.
Features:
- Automatic Relay pagination arguments (before, after, first, last)
- Iterable to connection conversion
- Resolver wrapping for pagination
- Integration with Connection types
Auto Arguments:
before: Cursor for backward pagination
after: Cursor for forward pagination
first: Number of items from start
last: Number of items from end
Methods:
resolve_connection(): Converts iterables to connections
connection_resolver(): Wraps resolvers for pagination
Usage:
class Query(graphene.ObjectType):
users = ConnectionField(UserConnection)
def resolve_users(self, info, **args):
# Return iterable - automatically converted to connection
return User.objects.all()
"""
class PageInfo(ObjectType):
"""
Relay pagination metadata following the specification.
Fields:
has_next_page: Boolean indicating if more items exist forward
has_previous_page: Boolean indicating if more items exist backward
start_cursor: Cursor of first item in current page
end_cursor: Cursor of last item in current page
Features:
- Relay specification compliant
- Automatic cursor generation
- Pagination state management
- Client navigation support
Usage:
# Automatically included in Connection types
# Used by Relay clients for pagination UI
query {
users(first: 10) {
edges {
node {
name
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
"""Standardized mutation patterns with client IDs for optimistic updates.
class ClientIDMutation(Mutation):
"""
Relay-style mutations with client mutation ID support.
Features:
- Automatic Input class generation
- client_mutation_id field injection
- Payload naming convention ({MutationName}Payload)
- Support for optimistic updates
Required Method:
mutate_and_get_payload(): Implementation method with client ID
Generated Features:
- Input class with client_mutation_id field
- Payload class with client_mutation_id field
- Automatic argument extraction
Usage:
class CreateUser(ClientIDMutation):
class Input:
name = graphene.String(required=True)
email = graphene.String()
user = graphene.Field(User)
@classmethod
def mutate_and_get_payload(cls, root, info, **input):
# client_mutation_id automatically handled
user = create_user(
name=input['name'],
email=input.get('email')
)
return CreateUser(user=user)
# Generates mutation field that accepts:
# input {
# name: String!
# email: String
# clientMutationId: String
# }
#
# Returns payload:
# {
# user: User
# clientMutationId: String
# }
"""import graphene
from graphene import relay
class User(graphene.ObjectType):
class Meta:
interfaces = (relay.Node,)
name = graphene.String()
email = graphene.String()
posts = relay.ConnectionField('PostConnection')
@classmethod
def get_node(cls, info, id):
"""Required method for Node interface."""
return get_user_by_id(id)
def resolve_posts(self, info, **args):
return get_posts_for_user(self.id)
class Post(graphene.ObjectType):
class Meta:
interfaces = (relay.Node,)
title = graphene.String()
content = graphene.String()
author = graphene.Field(User)
@classmethod
def get_node(cls, info, id):
return get_post_by_id(id)
# Connection classes
class PostConnection(relay.Connection):
class Meta:
node = Post
class UserConnection(relay.Connection):
class Meta:
node = User
# Query with Node field
class Query(graphene.ObjectType):
node = relay.Node.Field()
users = relay.ConnectionField(UserConnection)
posts = relay.ConnectionField(PostConnection)
def resolve_users(self, info, **args):
return User.objects.all()
def resolve_posts(self, info, **args):
return Post.objects.all()import graphene
from graphene import relay
import uuid
class UUIDNode(relay.Node):
"""Node interface using UUID global IDs."""
class Meta:
# Use UUID-based global ID type
global_id_type = relay.UUIDGlobalIDType()
class User(graphene.ObjectType):
class Meta:
interfaces = (UUIDNode,)
# ID field will use UUID type instead of ID
name = graphene.String()
@classmethod
def get_node(cls, info, id):
# id is already a UUID object
return get_user_by_uuid(id)
# Custom global ID type
class PrefixedGlobalIDType(relay.BaseGlobalIDType):
"""Custom global ID with prefix."""
def to_global_id(self, type_, id):
return f"app_{type_}_{id}"
def resolve_global_id(self, global_id):
parts = global_id.split('_')
if len(parts) >= 3 and parts[0] == 'app':
return (parts[1], '_'.join(parts[2:]))
raise ValueError(f"Invalid global ID format: {global_id}")
class CustomNode(relay.Node):
class Meta:
global_id_type = PrefixedGlobalIDType()import graphene
from graphene import relay
class CreateUserInput(graphene.InputObjectType):
name = graphene.String(required=True)
email = graphene.String(required=True)
age = graphene.Int()
class CreateUser(relay.ClientIDMutation):
class Input:
data = graphene.Field(CreateUserInput, required=True)
user = graphene.Field(User)
success = graphene.Boolean()
errors = graphene.List(graphene.String)
@classmethod
def mutate_and_get_payload(cls, root, info, data, client_mutation_id=None):
errors = []
# Validation
if len(data.name) < 2:
errors.append("Name must be at least 2 characters")
if '@' not in data.email:
errors.append("Invalid email format")
if errors:
return CreateUser(success=False, errors=errors)
# Create user
user = create_user(
name=data.name,
email=data.email,
age=data.age
)
return CreateUser(
user=user,
success=True,
errors=[]
)
class UpdateUser(relay.ClientIDMutation):
class Input:
id = graphene.ID(required=True)
name = graphene.String()
email = graphene.String()
user = graphene.Field(User)
@classmethod
def mutate_and_get_payload(cls, root, info, id, **input):
# Resolve node from global ID
node_type, node_id = relay.Node.resolve_global_id(info, id)
if node_type != 'User':
raise Exception('Invalid user ID')
user = get_user_by_id(node_id)
if not user:
raise Exception('User not found')
# Update fields
for field, value in input.items():
if value is not None:
setattr(user, field, value)
save_user(user)
return UpdateUser(user=user)
class Mutation(graphene.ObjectType):
create_user = CreateUser.Field()
update_user = UpdateUser.Field()import graphene
from graphene import relay
class UserConnection(relay.Connection):
class Meta:
node = User
total_count = graphene.Int()
def resolve_total_count(self, info):
return len(self.iterable)
class Query(graphene.ObjectType):
users = relay.ConnectionField(
UserConnection,
# Custom arguments for filtering
name_contains=graphene.String(),
active_only=graphene.Boolean(),
sort_by=graphene.String()
)
def resolve_users(self, info, name_contains=None, active_only=None, sort_by=None, **args):
queryset = User.objects.all()
# Apply filters
if name_contains:
queryset = queryset.filter(name__icontains=name_contains)
if active_only:
queryset = queryset.filter(is_active=True)
# Apply sorting
if sort_by == 'name':
queryset = queryset.order_by('name')
elif sort_by == 'created':
queryset = queryset.order_by('-created_at')
return queryset
# Usage in GraphQL query:
# query {
# users(first: 10, nameContains: "john", activeOnly: true, sortBy: "name") {
# edges {
# node {
# name
# email
# }
# cursor
# }
# pageInfo {
# hasNextPage
# endCursor
# }
# totalCount
# }
# }Install with Tessl CLI
npx tessl i tessl/pypi-graphene