CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-graphene

GraphQL Framework for Python providing declarative schema construction and query execution

Pending
Overview
Eval results
Files

relay.mddocs/

Relay Integration

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.

Capabilities

Node Interface and Global IDs

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")
    """

Global ID Type System

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)
    """

Connection System

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
                }
            }
        }
    """

Relay Mutations

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
        # }
    """

Usage Examples

Implementing Node Interface

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()

Custom Global ID Types

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()

Relay Mutations with Input Validation

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()

Advanced Connection Filtering

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

docs

index.md

relay.md

schema-execution.md

type-system.md

tile.json