Skip to main content

Python Libraries

Comprehensive guide to GraphQL libraries and frameworks for Python, including setup, usage, and best practices.

Graphene

Overview

Graphene is the most popular GraphQL library for Python, providing Django and SQLAlchemy integrations.

Installation

# Core library
pip install graphene

# Django integration
pip install graphene-django

# SQLAlchemy integration
pip install graphene-sqlalchemy

# Flask integration
pip install flask-graphql

Basic Schema

import graphene
from graphene import String, Int, List, Field, ObjectType, Schema

class User(ObjectType):
id = Int()
name = String()
email = String()

def resolve_name(self, info):
return f"User {self.id}"

class Query(ObjectType):
user = Field(User, id=Int(required=True))
users = List(User)

def resolve_user(self, info, id):
return User(id=id, name=f"User {id}", email=f"user{id}@example.com")

def resolve_users(self, info):
return [
User(id=1, name="Alice", email="alice@example.com"),
User(id=2, name="Bob", email="bob@example.com")
]

class Mutation(ObjectType):
create_user = Field(User, name=String(required=True), email=String(required=True))

def resolve_create_user(self, info, name, email):
# Create user logic here
return User(id=999, name=name, email=email)

schema = Schema(query=Query, mutation=Mutation)

Django Integration

# models.py
from django.db import models

class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
created_at = models.DateTimeField(auto_now_add=True)

class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)

# schema.py
import graphene
from graphene_django import DjangoObjectType
from .models import User, Post

class UserType(DjangoObjectType):
class Meta:
model = User
fields = ("id", "name", "email", "created_at")

class PostType(DjangoObjectType):
class Meta:
model = Post
fields = ("id", "title", "content", "author", "created_at")

class Query(graphene.ObjectType):
users = graphene.List(UserType)
user = graphene.Field(UserType, id=graphene.ID(required=True))
posts = graphene.List(PostType)
post = graphene.Field(PostType, id=graphene.ID(required=True))

def resolve_users(self, info):
return User.objects.all()

def resolve_user(self, info, id):
return User.objects.get(pk=id)

def resolve_posts(self, info):
return Post.objects.select_related('author').all()

def resolve_post(self, info, id):
return Post.objects.select_related('author').get(pk=id)

class UserInput(graphene.InputObjectType):
name = graphene.String(required=True)
email = graphene.String(required=True)

class PostInput(graphene.InputObjectType):
title = graphene.String(required=True)
content = graphene.String(required=True)
author_id = graphene.ID(required=True)

class CreateUser(graphene.Mutation):
class Arguments:
input = UserInput(required=True)

user = graphene.Field(UserType)

def mutate(self, info, input):
user = User.objects.create(
name=input.name,
email=input.email
)
return CreateUser(user=user)

class CreatePost(graphene.Mutation):
class Arguments:
input = PostInput(required=True)

post = graphene.Field(PostType)

def mutate(self, info, input):
author = User.objects.get(pk=input.author_id)
post = Post.objects.create(
title=input.title,
content=input.content,
author=author
)
return CreatePost(post=post)

class Mutation(graphene.ObjectType):
create_user = CreateUser.Field()
create_post = CreatePost.Field()

schema = graphene.Schema(query=Query, mutation=Mutation)

# urls.py
from django.urls import path
from graphene_django.views import GraphQLView
from .schema import schema

urlpatterns = [
path('graphql/', GraphQLView.as_view(graphiql=True, schema=schema)),
]

Advanced Graphene Features

import graphene
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from graphene_django.converter import convert_django_field

# Custom scalar types
class DateTime(graphene.Scalar):
@staticmethod
def serialize(dt):
return dt.isoformat()

@staticmethod
def parse_literal(node):
return datetime.fromisoformat(node.value)

@staticmethod
def parse_value(value):
return datetime.fromisoformat(value)

# Relay-style connections
class UserNode(DjangoObjectType):
class Meta:
model = User
interfaces = (relay.Node,)
filter_fields = ['name', 'email']

class Query(graphene.ObjectType):
user = relay.Node.Field(UserNode)
users = DjangoFilterConnectionField(UserNode)

# Custom field resolvers
class UserType(DjangoObjectType):
full_name = graphene.String()
post_count = graphene.Int()

class Meta:
model = User
fields = ("id", "name", "email")

def resolve_full_name(self, info):
return f"{self.first_name} {self.last_name}"

def resolve_post_count(self, info):
return self.posts.count()

# Subscription support
import channels_graphql_ws

class Subscription(graphene.ObjectType):
user_created = graphene.Field(UserType)

def resolve_user_created(self, info):
return User.objects.order_by('-created_at').first()

# WebSocket consumer
class GraphqlWsConsumer(channels_graphql_ws.GraphqlWsConsumer):
schema = schema

async def on_connect(self, payload):
await self.accept()

Strawberry

Overview

Strawberry is a modern GraphQL library that uses Python type hints and dataclasses.

Installation

pip install strawberry-graphql

# FastAPI integration
pip install strawberry-graphql[fastapi]

# Django integration
pip install strawberry-graphql[django]

Basic Schema

import strawberry
from typing import List, Optional
from dataclasses import dataclass

@strawberry.type
class User:
id: int
name: str
email: str

@strawberry.type
class Post:
id: int
title: str
content: str
author: User

@strawberry.input
class UserInput:
name: str
email: str

@strawberry.input
class PostInput:
title: str
content: str
author_id: int

@strawberry.type
class Query:
@strawberry.field
def user(self, id: int) -> Optional[User]:
return get_user_by_id(id)

@strawberry.field
def users(self) -> List[User]:
return get_all_users()

@strawberry.field
def posts(self) -> List[Post]:
return get_all_posts()

@strawberry.type
class Mutation:
@strawberry.field
def create_user(self, input: UserInput) -> User:
return create_user(input.name, input.email)

@strawberry.field
def create_post(self, input: PostInput) -> Post:
return create_post(input.title, input.content, input.author_id)

schema = strawberry.Schema(query=Query, mutation=Mutation)

FastAPI Integration

import strawberry
from strawberry.fastapi import GraphQLRouter
from fastapi import FastAPI

# Schema definition (as above)
schema = strawberry.Schema(query=Query, mutation=Mutation)

# FastAPI app
app = FastAPI()

# Add GraphQL endpoint
graphql_app = GraphQLRouter(schema)
app.include_router(graphql_app, prefix="/graphql")

# Alternative: single endpoint
from strawberry.fastapi import GraphQLRouter
app.mount("/graphql", GraphQLRouter(schema))

Advanced Strawberry Features

import strawberry
from strawberry.permission import BasePermission
from strawberry.types import Info
from typing import Any, List
from datetime import datetime

# Custom permissions
class IsAuthenticated(BasePermission):
message = "User is not authenticated"

def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
return info.context.get("user") is not None

class IsOwner(BasePermission):
message = "User is not the owner"

def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
user = info.context.get("user")
return user and user.id == source.author_id

# Custom scalars
@strawberry.scalar(
serialize=lambda v: v.isoformat(),
parse_value=lambda v: datetime.fromisoformat(v)
)
class DateTime:
pass

# Dataloader for N+1 problem
from strawberry.dataloader import DataLoader
from typing import List

async def load_users(keys: List[int]) -> List[User]:
users = await User.objects.filter(id__in=keys)
user_map = {user.id: user for user in users}
return [user_map.get(key) for key in keys]

user_loader = DataLoader(load_fn=load_users)

@strawberry.type
class Post:
id: int
title: str
content: str
author_id: int

@strawberry.field
async def author(self, info: Info) -> User:
return await user_loader.load(self.author_id)

# Subscriptions
@strawberry.type
class Subscription:
@strawberry.subscription
async def user_updates(self) -> User:
while True:
yield await get_latest_user_update()
await asyncio.sleep(1)

# Generic types
from typing import TypeVar, Generic

T = TypeVar('T')

@strawberry.type
class Connection(Generic[T]):
edges: List[T]
total_count: int

@strawberry.type
class UserConnection(Connection[User]):
pass

@strawberry.type
class Query:
@strawberry.field
def users(self, limit: int = 10, offset: int = 0) -> UserConnection:
users = get_users(limit, offset)
return UserConnection(
edges=users,
total_count=get_total_user_count()
)

Ariadne

Overview

Ariadne is a schema-first GraphQL library that uses SDL (Schema Definition Language) for defining schemas.

Installation

pip install ariadne

# ASGI integration
pip install ariadne[asgi]

# Flask integration
pip install ariadne[flask]

Basic Schema

from ariadne import QueryType, MutationType, make_executable_schema
from ariadne.asgi import GraphQL

# Schema definition
type_defs = """
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}

type Post {
id: ID!
title: String!
content: String!
author: User!
}

input UserInput {
name: String!
email: String!
}

input PostInput {
title: String!
content: String!
authorId: ID!
}

type Query {
user(id: ID!): User
users: [User!]!
post(id: ID!): Post
posts: [Post!]!
}

type Mutation {
createUser(input: UserInput!): User!
createPost(input: PostInput!): Post!
}
"""

# Resolvers
query = QueryType()
mutation = MutationType()

@query.field("user")
def resolve_user(_, info, id):
return get_user_by_id(id)

@query.field("users")
def resolve_users(_, info):
return get_all_users()

@query.field("post")
def resolve_post(_, info, id):
return get_post_by_id(id)

@query.field("posts")
def resolve_posts(_, info):
return get_all_posts()

@mutation.field("createUser")
def resolve_create_user(_, info, input):
return create_user(input["name"], input["email"])

@mutation.field("createPost")
def resolve_create_post(_, info, input):
return create_post(input["title"], input["content"], input["authorId"])

# Schema
schema = make_executable_schema(type_defs, query, mutation)

# ASGI app
app = GraphQL(schema, debug=True)

Advanced Ariadne Features

from ariadne import (
QueryType, MutationType, SubscriptionType,
ObjectType, UnionType, InterfaceType,
make_executable_schema, load_schema_from_path
)
from ariadne.validation import cost_validator

# Load schema from file
type_defs = load_schema_from_path("schema.graphql")

# Object type resolvers
user_type = ObjectType("User")
post_type = ObjectType("Post")

@user_type.field("posts")
def resolve_user_posts(user, info):
return get_posts_by_user_id(user["id"])

@post_type.field("author")
def resolve_post_author(post, info):
return get_user_by_id(post["author_id"])

# Interface resolvers
node_interface = InterfaceType("Node")

@node_interface.type_resolver
def resolve_node_type(obj, *_):
if "email" in obj:
return "User"
if "title" in obj:
return "Post"
return None

# Union resolvers
search_result_union = UnionType("SearchResult")

@search_result_union.type_resolver
def resolve_search_result_type(obj, *_):
if "email" in obj:
return "User"
if "title" in obj:
return "Post"
return None

# Subscriptions
subscription = SubscriptionType()

@subscription.source("userCreated")
async def user_created_source(obj, info):
while True:
yield await get_latest_user()
await asyncio.sleep(1)

@subscription.field("userCreated")
def user_created_resolver(user, info):
return user

# Custom directives
from ariadne import SchemaDirectiveVisitor

class UpperDirective(SchemaDirectiveVisitor):
def visit_field_definition(self, field, object_type):
original_resolver = field.resolve

def upper_resolver(obj, info, **kwargs):
result = original_resolver(obj, info, **kwargs)
if isinstance(result, str):
return result.upper()
return result

field.resolve = upper_resolver
return field

# Schema with directives
type_defs = """
directive @upper on FIELD_DEFINITION

type User {
id: ID!
name: String! @upper
email: String!
}

type Query {
user(id: ID!): User
}
"""

schema = make_executable_schema(
type_defs,
query,
directives={"upper": UpperDirective}
)

# Validation and security
from ariadne.validation import cost_validator, depth_limit_validator

app = GraphQL(
schema,
validation_rules=[
cost_validator(maximum_cost=1000),
depth_limit_validator(max_depth=10)
]
)

GraphQL-Core

Overview

GraphQL-Core is the reference implementation of GraphQL for Python, providing the core functionality.

Installation

pip install graphql-core

Basic Usage

from graphql import (
GraphQLSchema, GraphQLObjectType, GraphQLField,
GraphQLString, GraphQLInt, GraphQLList, GraphQLArgument,
build_schema, graphql_sync
)

# Schema using code
user_type = GraphQLObjectType(
name='User',
fields={
'id': GraphQLField(GraphQLInt),
'name': GraphQLField(GraphQLString),
'email': GraphQLField(GraphQLString),
}
)

query_type = GraphQLObjectType(
name='Query',
fields={
'user': GraphQLField(
user_type,
args={'id': GraphQLArgument(GraphQLInt)},
resolve=lambda obj, info, id: get_user_by_id(id)
),
'users': GraphQLField(
GraphQLList(user_type),
resolve=lambda obj, info: get_all_users()
)
}
)

schema = GraphQLSchema(query=query_type)

# Execute query
result = graphql_sync(schema, '{ users { id name email } }')
print(result.data)

# Schema using SDL
schema_sdl = """
type User {
id: Int!
name: String!
email: String!
}

type Query {
user(id: Int!): User
users: [User!]!
}
"""

schema = build_schema(schema_sdl)

# Add resolvers
def get_user_resolver(obj, info, id):
return get_user_by_id(id)

def get_users_resolver(obj, info):
return get_all_users()

schema.query_type.fields['user'].resolve = get_user_resolver
schema.query_type.fields['users'].resolve = get_users_resolver

Comparison Matrix

FeatureGrapheneStrawberryAriadneGraphQL-Core
ApproachCode-firstCode-firstSchema-firstLow-level
Type SystemClassesType hintsSDLManual
Django IntegrationExcellentGoodManualManual
FastAPI IntegrationManualExcellentGoodManual
Async SupportLimitedFullFullFull
SubscriptionsYesYesYesManual
Learning CurveMediumEasyMediumHard
PerformanceGoodExcellentGoodExcellent
DocumentationExcellentGoodGoodGood
CommunityLargeGrowingMediumSmall

Best Practices

Schema Design

# Good: Descriptive types
@strawberry.type
class User:
id: int
display_name: str
email_address: str
registration_date: datetime

# Bad: Generic types
@strawberry.type
class User:
id: int
name: str
email: str
date: datetime

Error Handling

# Custom error types
@strawberry.type
class ValidationError:
field: str
message: str

@strawberry.type
class UserResult:
success: bool
user: Optional[User] = None
errors: List[ValidationError] = strawberry.field(default_factory=list)

@strawberry.type
class Mutation:
@strawberry.field
def create_user(self, input: UserInput) -> UserResult:
try:
user = create_user(input)
return UserResult(success=True, user=user)
except ValidationException as e:
return UserResult(
success=False,
errors=[ValidationError(field=e.field, message=e.message)]
)

Performance Optimization

# DataLoader for N+1 problem
from strawberry.dataloader import DataLoader

async def load_users(keys: List[int]) -> List[User]:
users = await User.objects.filter(id__in=keys)
user_map = {user.id: user for user in users}
return [user_map.get(key) for key in keys]

# Caching
from strawberry.extensions import QueryDepthLimiter
from strawberry.extensions.tracing import ApolloTracingExtension

schema = strawberry.Schema(
query=Query,
extensions=[
QueryDepthLimiter(max_depth=10),
ApolloTracingExtension,
]
)

This comprehensive guide covers the major Python GraphQL libraries with practical examples and best practices for each approach.