A library for creating GraphQL APIs using dataclasses and type annotations with extensive framework integration support.
Field definition system with custom resolvers, descriptions, permissions, and advanced configuration options for GraphQL fields. This system allows fine-grained control over how GraphQL fields are resolved and secured.
Defines GraphQL fields with custom resolvers and configuration options.
def field(
resolver: Callable = None,
*,
name: str = None,
description: str = None,
deprecation_reason: str = None,
permission_classes: List[Type[BasePermission]] = None,
extensions: List[FieldExtension] = None,
default: Any = dataclasses.NOTHING,
default_factory: Callable = dataclasses.NOTHING
) -> Any:
"""
Decorator to define GraphQL fields with custom configuration.
Args:
resolver: Custom resolver function for the field
name: Custom field name (defaults to function/attribute name)
description: Field description for GraphQL schema
deprecation_reason: Deprecation message if field is deprecated
permission_classes: List of permission classes for field-level authorization
extensions: List of field extensions to apply
default: Default value for the field
default_factory: Factory function for default value
Returns:
Configured GraphQL field
"""Usage Examples:
@strawberry.type
class User:
id: strawberry.ID
name: str
email: str = strawberry.field(description="User's email address")
@strawberry.field(description="User's full display name")
def display_name(self) -> str:
return f"{self.name} <{self.email}>"
@strawberry.field(
description="User's posts",
permission_classes=[IsAuthenticated]
)
def posts(self, info: strawberry.Info) -> List[Post]:
return get_user_posts(self.id, info.context.user)
@strawberry.field(deprecation_reason="Use display_name instead")
def full_name(self) -> str:
return self.display_name()
# Field with resolver arguments
@strawberry.field
def posts_by_tag(self, tag: str, limit: int = 10) -> List[Post]:
return get_posts_by_tag(self.id, tag, limit)Defines GraphQL mutation fields for data modification operations.
def mutation(
resolver: Callable = None,
*,
name: str = None,
description: str = None,
permission_classes: List[Type[BasePermission]] = None,
extensions: List[FieldExtension] = None
) -> Any:
"""
Decorator to define GraphQL mutation fields.
Args:
resolver: Resolver function for the mutation
name: Custom mutation name
description: Mutation description
permission_classes: Permission classes for authorization
extensions: Field extensions to apply
Returns:
Configured GraphQL mutation field
"""Usage Example:
@strawberry.type
class Mutation:
@strawberry.mutation(
description="Create a new user account",
permission_classes=[IsAdmin]
)
def create_user(
self,
name: str,
email: str,
age: int = 18
) -> User:
# Validation and user creation logic
if not email or "@" not in email:
raise ValueError("Invalid email address")
return User(
id=generate_id(),
name=name,
email=email,
age=age
)
@strawberry.mutation
def update_user(
self,
id: strawberry.ID,
input: UpdateUserInput
) -> User:
user = get_user(id)
if input.name is not strawberry.UNSET:
user.name = input.name
if input.email is not strawberry.UNSET:
user.email = input.email
save_user(user)
return userDefines GraphQL subscription fields for real-time data streaming.
def subscription(
resolver: Callable = None,
*,
name: str = None,
description: str = None,
permission_classes: List[Type[BasePermission]] = None,
extensions: List[FieldExtension] = None
) -> Any:
"""
Decorator to define GraphQL subscription fields.
Args:
resolver: Async generator function for the subscription
name: Custom subscription name
description: Subscription description
permission_classes: Permission classes for authorization
extensions: Field extensions to apply
Returns:
Configured GraphQL subscription field
"""Usage Example:
@strawberry.type
class Subscription:
@strawberry.subscription(description="Real-time user updates")
async def user_updates(self, user_id: strawberry.ID) -> AsyncIterator[User]:
# Subscribe to user updates from message broker/database
async for update in subscribe_to_user_updates(user_id):
yield User(**update)
@strawberry.subscription
async def message_stream(
self,
room_id: strawberry.ID,
info: strawberry.Info
) -> AsyncIterator[Message]:
# Check permissions
if not can_access_room(info.context.user, room_id):
raise PermissionError("Access denied")
async for message in message_broker.subscribe(f"room:{room_id}"):
yield Message(**message)Defines GraphQL field arguments with validation and default values.
def argument(
name: str = None,
description: str = None,
default: Any = dataclasses.NOTHING,
default_factory: Callable = dataclasses.NOTHING
) -> Any:
"""
Define GraphQL field arguments with metadata.
Args:
name: Custom argument name (defaults to parameter name)
description: Argument description
default: Default value for the argument
default_factory: Factory function for default value
Returns:
Configured GraphQL argument
"""Usage Example:
@strawberry.type
class Query:
@strawberry.field
def users(
self,
limit: int = strawberry.argument(
default=10,
description="Maximum number of users to return"
),
offset: int = strawberry.argument(
default=0,
description="Number of users to skip"
),
filter_name: str = strawberry.argument(
default=None,
description="Filter users by name (case-insensitive)"
)
) -> List[User]:
return get_users(limit=limit, offset=offset, name_filter=filter_name)class BasePermission:
"""Base class for field-level permissions."""
message: str = "Permission denied"
def has_permission(
self,
source: Any,
info: Info,
**kwargs
) -> bool:
"""
Synchronous permission check.
Args:
source: Parent object being resolved
info: GraphQL execution info
**kwargs: Field arguments
Returns:
True if permission granted, False otherwise
"""
async def has_permission_async(
self,
source: Any,
info: Info,
**kwargs
) -> bool:
"""
Asynchronous permission check.
Args:
source: Parent object being resolved
info: GraphQL execution info
**kwargs: Field arguments
Returns:
True if permission granted, False otherwise
"""Usage Example:
class IsAuthenticated(strawberry.BasePermission):
message = "You must be authenticated to access this field"
def has_permission(self, source: Any, info: strawberry.Info, **kwargs) -> bool:
return info.context.user is not None
class IsOwner(strawberry.BasePermission):
message = "You can only access your own data"
def has_permission(self, source: Any, info: strawberry.Info, **kwargs) -> bool:
return info.context.user and source.user_id == info.context.user.id
class IsAdmin(strawberry.BasePermission):
message = "Admin access required"
async def has_permission_async(self, source: Any, info: strawberry.Info, **kwargs) -> bool:
user = info.context.user
if not user:
return False
# Async database check
return await is_user_admin(user.id)
@strawberry.type
class User:
id: strawberry.ID
name: str
@strawberry.field(permission_classes=[IsAuthenticated, IsOwner])
def email(self) -> str:
return self._email
@strawberry.field(permission_classes=[IsAdmin])
def admin_notes(self) -> str:
return self._admin_notesThe Info object provides context and metadata to resolvers.
class Info:
"""GraphQL execution info passed to resolvers."""
context: Any # Request context (user, database connections, etc.)
field_name: str # Name of the current field being resolved
field_nodes: List[FieldNode] # AST nodes for the field
is_subscription: bool # Whether this is a subscription field
operation_name: str # Name of the GraphQL operation
path: List[Union[str, int]] # Path to this field in the result
return_type: Type # Expected return type of the field
root_value: Any # Root value passed to the schema
schema: Schema # GraphQL schema instance
variable_values: Dict[str, Any] # Variables passed to the operationUsage Example:
@strawberry.type
class Query:
@strawberry.field
def current_user(self, info: strawberry.Info) -> Optional[User]:
# Access request context
return info.context.user
@strawberry.field
def field_info(self, info: strawberry.Info) -> str:
return f"Resolving field '{info.field_name}' at path {info.path}"
@strawberry.field
def debug_info(self, info: strawberry.Info) -> Dict[str, Any]:
return {
"operation_name": info.operation_name,
"variables": info.variable_values,
"is_subscription": info.is_subscription
}Parent: TypeVar # Type hint for parent object in resolversUsage Example:
@strawberry.type
class Post:
id: strawberry.ID
title: str
author_id: strawberry.ID
@strawberry.field
def author(self) -> User:
# 'self' is the parent Post object
return get_user(self.author_id)
@strawberry.type
class User:
id: strawberry.ID
name: str
@strawberry.field
def posts(self, parent: strawberry.Parent[User]) -> List[Post]:
# Explicit parent type annotation
return get_posts_by_author(parent.id)Field extensions allow custom logic to be applied to individual fields.
class FieldExtension:
"""Base class for field-level extensions."""
def apply(self, field: StrawberryField) -> StrawberryField:
"""Apply extension logic to a field."""@strawberry.type
class User:
id: strawberry.ID
name: str
created_at: datetime = strawberry.field(
default_factory=datetime.utcnow,
description="Account creation timestamp"
)
is_active: bool = strawberry.field(
default=True,
description="Whether the user account is active"
)
preferences: Dict[str, Any] = strawberry.field(
default_factory=dict,
description="User preferences and settings"
)@strawberry.type
class Query:
@strawberry.field
async def search_users(
self,
query: str,
limit: int = 20,
info: strawberry.Info
) -> List[User]:
"""Async resolver with database operations."""
async with info.context.database.transaction():
results = await search_users_in_database(
query=query,
limit=limit,
user_context=info.context.user
)
return [User(**user_data) for user_data in results]
@strawberry.field
def paginated_posts(
self,
after: str = None,
first: int = 10
) -> PostConnection:
"""Cursor-based pagination resolver."""
posts = get_posts_after_cursor(after, first + 1) # Get one extra
edges = [
PostEdge(node=post, cursor=encode_cursor(post.id))
for post in posts[:first]
]
return PostConnection(
edges=edges,
page_info=PageInfo(
has_next_page=len(posts) > first,
end_cursor=edges[-1].cursor if edges else None
)
)Install with Tessl CLI
npx tessl i tessl/pypi-strawberry-graphql