A library for creating GraphQL APIs using dataclasses and type annotations with extensive framework integration support.
Apollo Federation support for building distributed GraphQL architectures with multiple services and schema composition. Federation allows you to split your GraphQL schema across multiple services while presenting a unified API to clients.
Federation-enabled schema class with support for Apollo Federation directives.
class Schema:
"""Apollo Federation schema implementation."""
def __init__(
self,
query: Type = None,
mutation: Type = None,
subscription: Type = None,
*,
types: List[Type] = None,
extensions: List[SchemaExtension] = None,
enable_federation_2: bool = False
):
"""
Initialize Apollo Federation schema.
Args:
query: Root query type
mutation: Root mutation type (optional)
subscription: Root subscription type (optional)
types: Additional types to include in schema
extensions: Schema extensions
enable_federation_2: Enable Apollo Federation 2 features
"""Usage Example:
import strawberry
from strawberry.federation import Schema
@strawberry.federation.type(keys=["id"])
class User:
id: strawberry.ID
name: str
email: str
@strawberry.type
class Query:
@strawberry.field
def users(self) -> List[User]:
return get_all_users()
# Create federation schema
schema = Schema(
query=Query,
enable_federation_2=True
)Creates federated GraphQL object types with entity keys and federation directives.
def type(
cls=None,
*,
name: str = None,
description: str = None,
keys: List[str] = None,
extend: bool = False,
resolvable: bool = True
) -> Any:
"""
Decorator to create federated GraphQL object types.
Args:
cls: The class to convert to a federated type
name: Custom name for the GraphQL type
description: Description for the GraphQL type
keys: List of key fields for entity resolution
extend: Whether this extends an existing entity from another service
resolvable: Whether this entity can be resolved by this service
Returns:
Federated GraphQL object type
"""Usage Examples:
# Basic federated entity
@strawberry.federation.type(keys=["id"])
class User:
id: strawberry.ID
name: str
email: str
# Multi-key entity
@strawberry.federation.type(keys=["id", "email"])
class User:
id: strawberry.ID
name: str
email: str
# Extending entity from another service
@strawberry.federation.type(keys=["id"], extend=True)
class User:
id: strawberry.ID = strawberry.federation.field(external=True)
posts: List["Post"]
@classmethod
def resolve_reference(cls, id: strawberry.ID):
# This service doesn't store user data, just references
return cls(id=id)
# Non-resolvable entity (just provides additional fields)
@strawberry.federation.type(keys=["id"], resolvable=False)
class User:
id: strawberry.ID = strawberry.federation.field(external=True)
computed_field: strDefines federated GraphQL fields with federation-specific directives.
def field(
resolver: Callable = None,
*,
name: str = None,
description: str = None,
external: bool = False,
requires: str = None,
provides: str = None,
override_: str = None,
used_overridden: bool = False
) -> Any:
"""
Decorator to define federated GraphQL fields.
Args:
resolver: Custom resolver function for the field
name: Custom field name
description: Field description
external: Whether field is defined in another service
requires: Fields required from other services to resolve this field
provides: Fields this field provides to the entity
override_: Service that this field overrides (Federation 2)
used_overridden: Whether this field uses overridden field (Federation 2)
Returns:
Configured federated GraphQL field
"""Usage Examples:
@strawberry.federation.type(keys=["id"])
class User:
id: strawberry.ID
name: str = strawberry.federation.field(external=True)
email: str = strawberry.federation.field(external=True)
@strawberry.federation.field(requires="name email")
def display_name(self) -> str:
return f"{self.name} <{self.email}>"
@strawberry.federation.field(provides="title")
def latest_post(self) -> "Post":
post = get_latest_post(self.id)
return Post(id=post.id, title=post.title)
@strawberry.federation.type(keys=["id"])
class Post:
id: strawberry.ID
title: str = strawberry.federation.field(external=True)
content: str
# Override field from another service (Federation 2)
@strawberry.federation.field(override_="posts-service")
def view_count(self) -> int:
return get_accurate_view_count(self.id)Creates federated GraphQL interfaces.
def interface(
cls=None,
*,
name: str = None,
description: str = None,
keys: List[str] = None
) -> Any:
"""
Decorator to create federated GraphQL interfaces.
Args:
cls: The class to convert to a federated interface
name: Custom name for the GraphQL interface
description: Description for the GraphQL interface
keys: Key fields for interface entities
Returns:
Federated GraphQL interface type
"""Usage Example:
@strawberry.federation.interface(keys=["id"])
class Node:
id: strawberry.ID
@strawberry.federation.type(keys=["id"])
class User(Node):
id: strawberry.ID
name: str
@strawberry.federation.type(keys=["id"])
class Post(Node):
id: strawberry.ID
title: strAll core decorators are available with federation support:
def input(
cls=None,
*,
name: str = None,
description: str = None
) -> Any:
"""Federated GraphQL input type decorator."""
def enum(
cls=None,
*,
name: str = None,
description: str = None
) -> Any:
"""Federated GraphQL enum type decorator."""
def scalar(
cls=None,
*,
name: str = None,
description: str = None,
serialize: Callable = None,
parse_value: Callable = None
) -> Any:
"""Federated GraphQL scalar type decorator."""
def union(name: str, types: Tuple[Type, ...]) -> Any:
"""Federated GraphQL union type creator."""Federated entities must implement reference resolution for the Apollo Gateway.
# Entities automatically get a resolve_reference class method
@strawberry.federation.type(keys=["id"])
class User:
id: strawberry.ID
name: str
email: str
@classmethod
def resolve_reference(cls, id: strawberry.ID):
"""
Resolve entity reference from Apollo Gateway.
Args:
id: Entity key value
Returns:
Entity instance
"""
user_data = get_user_by_id(id)
return cls(
id=user_data["id"],
name=user_data["name"],
email=user_data["email"]
)For entities with compound keys:
@strawberry.federation.type(keys=["userId", "productId"])
class UserProductPreference:
user_id: strawberry.ID
product_id: strawberry.ID
rating: int
@classmethod
def resolve_reference(cls, user_id: strawberry.ID, product_id: strawberry.ID):
preference = get_user_product_preference(user_id, product_id)
return cls(
user_id=preference["user_id"],
product_id=preference["product_id"],
rating=preference["rating"]
)For entities that can be resolved by different key combinations:
@strawberry.federation.type(keys=["id", "email"])
class User:
id: strawberry.ID
email: str
name: str
@classmethod
def resolve_reference(cls, **kwargs):
if "id" in kwargs:
user_data = get_user_by_id(kwargs["id"])
elif "email" in kwargs:
user_data = get_user_by_email(kwargs["email"])
else:
raise ValueError("No valid key provided")
return cls(
id=user_data["id"],
email=user_data["email"],
name=user_data["name"]
)Users Service:
# users_service.py
@strawberry.federation.type(keys=["id"])
class User:
id: strawberry.ID
name: str
email: str
created_at: datetime
@classmethod
def resolve_reference(cls, id: strawberry.ID):
user = get_user_from_database(id)
return cls(
id=user.id,
name=user.name,
email=user.email,
created_at=user.created_at
)
@strawberry.type
class Query:
@strawberry.field
def user(self, id: strawberry.ID) -> Optional[User]:
return User.resolve_reference(id)
@strawberry.field
def users(self) -> List[User]:
return [User.resolve_reference(u.id) for u in get_all_users()]
schema = strawberry.federation.Schema(query=Query)Posts Service:
# posts_service.py
@strawberry.federation.type(keys=["id"], extend=True)
class User:
id: strawberry.ID = strawberry.federation.field(external=True)
@strawberry.federation.field
def posts(self) -> List["Post"]:
return get_posts_by_user_id(self.id)
@strawberry.federation.type(keys=["id"])
class Post:
id: strawberry.ID
title: str
content: str
user_id: strawberry.ID
published_at: datetime
@strawberry.federation.field
def author(self) -> User:
return User(id=self.user_id)
@classmethod
def resolve_reference(cls, id: strawberry.ID):
post = get_post_from_database(id)
return cls(
id=post.id,
title=post.title,
content=post.content,
user_id=post.user_id,
published_at=post.published_at
)
@strawberry.type
class Query:
@strawberry.field
def post(self, id: strawberry.ID) -> Optional[Post]:
return Post.resolve_reference(id)
schema = strawberry.federation.Schema(query=Query, types=[User])# Enable Federation 2 features
schema = strawberry.federation.Schema(
query=Query,
enable_federation_2=True
)
@strawberry.federation.type(keys=["id"])
class User:
id: strawberry.ID
name: str
# Override field from another service
@strawberry.federation.field(override_="legacy-service")
def email(self) -> str:
return get_updated_email(self.id)
# Shareable fields (can be defined in multiple services)
@strawberry.federation.type(keys=["id"])
class Product:
id: strawberry.ID
@strawberry.federation.field(shareable=True)
def name(self) -> str:
return self._name@strawberry.federation.type(keys=["id"], extend=True)
class User:
id: strawberry.ID = strawberry.federation.field(external=True)
first_name: str = strawberry.federation.field(external=True)
last_name: str = strawberry.federation.field(external=True)
@strawberry.federation.field(requires="firstName lastName")
def full_name(self) -> str:
return f"{self.first_name} {self.last_name}"@strawberry.federation.type(keys=["id"])
class User:
id: strawberry.ID
email: str
@strawberry.federation.field(provides="username")
def profile(self) -> "UserProfile":
profile = get_user_profile(self.id)
return UserProfile(
user_id=self.id,
username=derive_username_from_email(self.email),
bio=profile.bio
)
@strawberry.federation.type(keys=["userId"])
class UserProfile:
user_id: strawberry.ID
username: str = strawberry.federation.field(external=True)
bio: strGenerate federation-compatible SDL:
from strawberry.printer import print_schema
# Print federation schema
federation_sdl = print_schema(schema)
print(federation_sdl)Example Output:
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@requires", "@provides"])
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post @key(fields: "id") {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
user(id: ID!): User
post(id: ID!): Post
}Services register with Apollo Gateway:
// gateway.js
const { ApolloGateway } = require('@apollo/gateway');
const { ApolloServer } = require('apollo-server');
const gateway = new ApolloGateway({
serviceList: [
{ name: 'users', url: 'http://localhost:4001/graphql' },
{ name: 'posts', url: 'http://localhost:4002/graphql' },
{ name: 'reviews', url: 'http://localhost:4003/graphql' }
]
});
const server = new ApolloServer({
gateway,
subscriptions: false
});This enables clients to make unified queries across all services:
query UnifiedQuery {
user(id: "1") {
name
email
posts {
title
content
reviews {
rating
comment
}
}
}
}Install with Tessl CLI
npx tessl i tessl/pypi-strawberry-graphql