CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-strawberry-graphql

A library for creating GraphQL APIs using dataclasses and type annotations with extensive framework integration support.

Overview
Eval results
Files

experimental.mddocs/

Experimental Features

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.

Capabilities

Pydantic Integration

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)

Advanced Pydantic Integration

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.auto

Pydantic Error Handling

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

Pydantic Exceptions

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 appropriately

Advanced Experimental Patterns

Pydantic Model Inheritance

from 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):
    pass

Custom Field Mapping

from 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 None

Pydantic with DataLoader

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

Pydantic Subscriptions

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)

Experimental Configuration

Feature Flags

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
)

Migration Path

# 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

Limitations and Considerations

Current Limitations

  1. Stability: Experimental features may have breaking changes
  2. Performance: Some features may have performance implications
  3. Documentation: Limited documentation compared to stable features
  4. Third-party Integration: May not work with all third-party tools

Best Practices

# 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 stability

Testing Experimental Features

import 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

docs

core-types.md

experimental.md

extensions.md

federation.md

fields-resolvers.md

framework-integrations.md

index.md

relay.md

schema-execution.md

utilities.md

tile.json