A library for creating GraphQL APIs using dataclasses and type annotations with extensive framework integration support.
Experimental features including Pydantic integration and preview functionality that may change in future versions. These features are under active development and may have breaking changes in minor releases.
⚠️ Warning: Experimental features are not covered by semantic versioning and may change or be removed in future releases. Use with caution in production environments.
Integration with Pydantic models for automatic GraphQL type generation and validation.
# Pydantic type decorators
def pydantic.type(
model: Type[BaseModel],
*,
name: str = None,
description: str = None,
all_fields: bool = False
) -> Any:
"""
Convert Pydantic model to GraphQL object type.
Args:
model: Pydantic model class
name: Custom GraphQL type name
description: Type description
all_fields: Include all model fields (including private)
Returns:
GraphQL object type based on Pydantic model
"""
def pydantic.input(
model: Type[BaseModel],
*,
name: str = None,
description: str = None,
all_fields: bool = False
) -> Any:
"""
Convert Pydantic model to GraphQL input type.
Args:
model: Pydantic model class
name: Custom GraphQL input type name
description: Input type description
all_fields: Include all model fields
Returns:
GraphQL input type based on Pydantic model
"""
def pydantic.interface(
model: Type[BaseModel],
*,
name: str = None,
description: str = None
) -> Any:
"""
Convert Pydantic model to GraphQL interface type.
Args:
model: Pydantic model class
name: Custom GraphQL interface name
description: Interface description
Returns:
GraphQL interface type based on Pydantic model
"""Usage Examples:
from pydantic import BaseModel, Field, validator
import strawberry.experimental.pydantic
# Define Pydantic models
class UserModel(BaseModel):
id: int
name: str = Field(description="User's full name")
email: str = Field(description="User's email address")
age: int = Field(ge=0, le=150, description="User's age")
is_active: bool = True
@validator('email')
def validate_email(cls, v):
# Pydantic validation logic
if '@' not in v:
raise ValueError('Invalid email format')
return v.lower()
class CreateUserModel(BaseModel):
name: str = Field(min_length=1, max_length=100)
email: str
age: int = Field(ge=0, le=150)
# Convert to GraphQL types
@strawberry.experimental.pydantic.type(UserModel)
class User:
pass # Fields are automatically generated from Pydantic model
@strawberry.experimental.pydantic.input(CreateUserModel)
class CreateUserInput:
pass # Input fields generated from Pydantic model
# Use in GraphQL schema
@strawberry.type
class Query:
@strawberry.field
def user(self, id: int) -> User:
# Pydantic validation happens automatically
user_data = get_user_from_database(id)
return User.from_pydantic(UserModel(**user_data))
@strawberry.type
class Mutation:
@strawberry.mutation
def create_user(self, input: CreateUserInput) -> User:
# Input is automatically validated by Pydantic
user_model = input.to_pydantic() # Convert to Pydantic model
# Pydantic validation runs here
if not user_model.email:
raise ValueError("Email is required")
# Create user
user_data = create_user_in_database(user_model.dict())
return User.from_pydantic(UserModel(**user_data))
schema = strawberry.Schema(query=Query, mutation=Mutation)from typing import Optional, List
from pydantic import BaseModel, Field
from datetime import datetime
import strawberry.experimental.pydantic
# Complex Pydantic model with relationships
class AddressModel(BaseModel):
street: str
city: str
state: str
zip_code: str = Field(alias="zipCode")
class UserModel(BaseModel):
id: int
name: str
email: str
addresses: List[AddressModel] = []
created_at: datetime = Field(alias="createdAt")
metadata: dict = Field(default_factory=dict)
class Config:
# Pydantic configuration
allow_population_by_field_name = True
json_encoders = {
datetime: lambda v: v.isoformat()
}
# Convert complex model
@strawberry.experimental.pydantic.type(UserModel, all_fields=True)
class User:
# Additional GraphQL-specific fields
@strawberry.field
def full_address(self) -> Optional[str]:
if not self.addresses:
return None
addr = self.addresses[0]
return f"{addr.street}, {addr.city}, {addr.state} {addr.zip_code}"
# Partial model conversion (only specific fields)
@strawberry.experimental.pydantic.type(UserModel)
class PublicUser:
# Only expose safe fields
id: strawberry.auto
name: strawberry.auto
created_at: strawberry.autoError type conversion from Pydantic validation errors.
def pydantic.error_type(
model: Type[BaseModel],
*,
name: str = None
) -> Any:
"""
Create GraphQL error type from Pydantic model validation errors.
Args:
model: Pydantic model class
name: Custom error type name
Returns:
GraphQL type for validation errors
"""Usage Example:
from pydantic import BaseModel, ValidationError
import strawberry.experimental.pydantic
class UserValidationModel(BaseModel):
name: str = Field(min_length=2, max_length=50)
email: str = Field(regex=r'^[^@]+@[^@]+\.[^@]+$')
age: int = Field(ge=13, le=120)
@strawberry.experimental.pydantic.error_type(UserValidationModel)
class UserValidationError:
pass
@strawberry.type
class CreateUserResult:
user: Optional[User]
errors: Optional[List[UserValidationError]]
@strawberry.type
class Mutation:
@strawberry.mutation
def create_user_with_validation(
self,
name: str,
email: str,
age: int
) -> CreateUserResult:
try:
# Validate with Pydantic
validated_data = UserValidationModel(
name=name,
email=email,
age=age
)
# Create user
user = create_user(validated_data.dict())
return CreateUserResult(user=user, errors=None)
except ValidationError as e:
# Convert Pydantic errors to GraphQL errors
errors = [
UserValidationError.from_pydantic_error(error)
for error in e.errors()
]
return CreateUserResult(user=None, errors=errors)Exception handling for Pydantic integration.
class UnregisteredTypeException(Exception):
"""Exception raised when trying to use unregistered Pydantic type."""
def __init__(self, type_name: str):
self.type_name = type_name
super().__init__(f"Pydantic type '{type_name}' is not registered")Usage Example:
try:
# This might raise UnregisteredTypeException
@strawberry.experimental.pydantic.type(SomeUnregisteredModel)
class SomeType:
pass
except strawberry.experimental.pydantic.UnregisteredTypeException as e:
print(f"Type registration failed: {e.type_name}")
# Handle the error appropriatelyfrom pydantic import BaseModel
import strawberry.experimental.pydantic
# Base Pydantic model
class BaseEntity(BaseModel):
id: int
created_at: datetime
updated_at: datetime
# Derived models
class UserModel(BaseEntity):
name: str
email: str
class PostModel(BaseEntity):
title: str
content: str
author_id: int
# Convert to GraphQL with inheritance
@strawberry.experimental.pydantic.interface(BaseEntity)
class Entity:
pass
@strawberry.experimental.pydantic.type(UserModel)
class User(Entity):
pass
@strawberry.experimental.pydantic.type(PostModel)
class Post(Entity):
passfrom pydantic import BaseModel, Field
import strawberry.experimental.pydantic
class UserModel(BaseModel):
user_id: int = Field(alias="id")
full_name: str = Field(alias="name")
email_address: str = Field(alias="email")
# Custom field mapping
@strawberry.experimental.pydantic.type(
UserModel,
name="User",
description="User account with custom field mapping"
)
class User:
# Override specific fields
@strawberry.field(name="displayName")
def full_name(self) -> str:
return self.full_name.title()
# Add computed fields
@strawberry.field
def username(self) -> Optional[str]:
return self.email_address.split('@')[0] if self.email_address else Nonefrom pydantic import BaseModel
from strawberry.dataloader import DataLoader
import strawberry.experimental.pydantic
class UserModel(BaseModel):
id: int
name: str
email: str
department_id: int
class DepartmentModel(BaseModel):
id: int
name: str
description: str
@strawberry.experimental.pydantic.type(UserModel)
class User:
@strawberry.field
async def department(self, info: strawberry.Info) -> "Department":
# Use DataLoader with Pydantic models
dept_data = await info.context.department_loader.load(self.department_id)
return Department.from_pydantic(DepartmentModel(**dept_data))
@strawberry.experimental.pydantic.type(DepartmentModel)
class Department:
pass
# DataLoader for departments
async def load_departments(dept_ids: List[int]) -> List[DepartmentModel]:
departments_data = await database.fetch_departments_by_ids(dept_ids)
return [DepartmentModel(**dept) for dept in departments_data]
department_loader = DataLoader(load_departments)from pydantic import BaseModel
import strawberry.experimental.pydantic
from typing import AsyncIterator
class NotificationModel(BaseModel):
id: int
user_id: int
message: str
type: str
created_at: datetime
@strawberry.experimental.pydantic.type(NotificationModel)
class Notification:
pass
@strawberry.type
class Subscription:
@strawberry.subscription
async def user_notifications(
self,
user_id: int
) -> AsyncIterator[Notification]:
# Subscribe to user notifications
async for notification_data in notification_stream(user_id):
# Pydantic validation on streaming data
validated_notification = NotificationModel(**notification_data)
yield Notification.from_pydantic(validated_notification)import strawberry
from strawberry.experimental import enable_pydantic_integration
# Enable experimental features
enable_pydantic_integration()
# Or configure specific experimental features
strawberry.experimental.configure(
pydantic_integration=True,
auto_camel_case_pydantic=True,
strict_pydantic_validation=True
)# Gradual migration from regular Strawberry to Pydantic integration
@strawberry.type
class User:
id: strawberry.ID
name: str
email: str
# Gradually add Pydantic validation
@strawberry.field
def validated_profile(self) -> "UserProfile":
# Use Pydantic for new fields
profile_data = get_user_profile(self.id)
return UserProfile.from_pydantic(UserProfileModel(**profile_data))
# New features use Pydantic from the start
class UserProfileModel(BaseModel):
bio: str = Field(max_length=500)
website: Optional[str] = Field(regex=r'^https?://')
social_links: List[str] = []
@strawberry.experimental.pydantic.type(UserProfileModel)
class UserProfile:
pass# Use feature detection
import strawberry.experimental.pydantic
if hasattr(strawberry.experimental.pydantic, 'type'):
# Use Pydantic integration
@strawberry.experimental.pydantic.type(UserModel)
class User:
pass
else:
# Fallback to regular Strawberry types
@strawberry.type
class User:
id: int
name: str
email: str
# Version pinning for experimental features
# In requirements.txt:
# strawberry-graphql==0.281.0 # Pin exact version for stabilityimport pytest
from pydantic import ValidationError
import strawberry.experimental.pydantic
def test_pydantic_integration():
"""Test Pydantic model integration."""
class TestModel(BaseModel):
name: str = Field(min_length=1)
age: int = Field(ge=0)
@strawberry.experimental.pydantic.type(TestModel)
class TestType:
pass
# Test successful conversion
valid_data = TestModel(name="Alice", age=30)
graphql_obj = TestType.from_pydantic(valid_data)
assert graphql_obj.name == "Alice"
assert graphql_obj.age == 30
# Test validation error handling
with pytest.raises(ValidationError):
TestModel(name="", age=-5) # Invalid data
def test_experimental_feature_availability():
"""Test that experimental features are available."""
# Check if feature is available
assert hasattr(strawberry.experimental.pydantic, 'type')
assert hasattr(strawberry.experimental.pydantic, 'input')
assert hasattr(strawberry.experimental.pydantic, 'interface')Install with Tessl CLI
npx tessl i tessl/pypi-strawberry-graphql