strawberry icon indicating copy to clipboard operation
strawberry copied to clipboard

First-class Pydantic v2+ support

Open patrick91 opened this issue 5 months ago • 7 comments

Summary

Add first-class support for Pydantic v2+ models in Strawberry GraphQL.

This PR introduces a new strawberry.pydantic module that allows you to directly decorate Pydantic BaseModel classes to create GraphQL types, inputs, and interfaces without requiring separate wrapper classes.

Basic Usage

import strawberry
from pydantic import BaseModel

@strawberry.pydantic.type
class User(BaseModel):
    name: str
    age: int

@strawberry.pydantic.input
class CreateUserInput(BaseModel):
    name: str
    age: int

@strawberry.pydantic.interface
class Node(BaseModel):
    id: str

Features Implemented

Core Features

  • @strawberry.pydantic.type - Convert Pydantic models to GraphQL types
  • @strawberry.pydantic.input - Convert Pydantic models to GraphQL input types
  • @strawberry.pydantic.interface - Convert Pydantic models to GraphQL interfaces
  • ✅ Automatic field extraction from Pydantic models
  • ✅ Pydantic field descriptions preserved in GraphQL schema
  • ✅ Pydantic field aliases used as GraphQL field names
  • ✅ Support for strawberry.Private to exclude fields from schema
  • ✅ Support for strawberry.field() with Annotated for directives, permissions, deprecation
  • ✅ Generic Pydantic model support
  • ✅ Nested Pydantic types
  • strawberry.pydantic.Error type for validation error handling
  • ✅ Computed fields via include_computed=True

Pydantic v2 Features (130 Tests Passing)

  • Functional Validators - BeforeValidator, AfterValidator, WrapValidator with Annotated types
  • @model_validator Support - Cross-field validation with mode='before'|'after'|'wrap'
  • Validation Context - Strawberry Info automatically passed to Pydantic validators
  • model_config Settings - strict=True, extra='forbid', from_attributes=True all respected
  • Strict Mode Per-Field - Field(strict=True) for individual field validation
  • Separate Aliases - validation_alias and serialization_alias supported via by_name=True
  • Discriminated Unions - Literal type support for type discriminators
  • TypeAdapter - Use TypeAdapter in resolvers for scalar/list validation
  • RootModel - Use RootModel for validated list/dict wrappers in resolvers

Migration from Experimental

# Before (experimental)
@strawberry.experimental.pydantic.type(model=UserModel, all_fields=True)
class User:
    pass

# After (first-class)
@strawberry.pydantic.type
class User(BaseModel):
    name: str
    age: int

Validation Context Example

Pydantic validators can access GraphQL context for permission-based validation:

from pydantic import field_validator, ValidationInfo

@strawberry.pydantic.input
class CreatePostInput(BaseModel):
    title: str

    @field_validator('title')
    @classmethod
    def check_permissions(cls, v: str, info: ValidationInfo) -> str:
        strawberry_info = info.context.get('info') if info.context else None
        if strawberry_info:
            user = strawberry_info.context.get('user')
            if user and not user.can_create_posts:
                raise ValueError('User cannot create posts')
        return v

Discriminated Union Example

from typing import Literal

@strawberry.pydantic.type
class Cat(BaseModel):
    pet_type: Literal["cat"]
    meow_volume: int

@strawberry.pydantic.type
class Dog(BaseModel):
    pet_type: Literal["dog"]
    bark_volume: int

@strawberry.type
class Query:
    @strawberry.field
    def pet(self) -> Cat | Dog:
        return Cat(pet_type="cat", meow_volume=10)

Test Results

  • 130 Pydantic-specific tests - All passing ✅
  • Full test suite (4675+ tests) - All passing ✅

🤖 Generated with Claude Code

patrick91 avatar Aug 02 '25 09:08 patrick91

Reviewer's Guide

This PR introduces first-class Pydantic support by adding a dedicated strawberry/pydantic module with type/input/interface decorators, overhauls the Pydantic integration documentation and migration guide, cleans up experimental code and unused flags, and restructures and extends the test suite to cover all new features.

File-Level Changes

Change Details Files
Add first-class Pydantic integration decorators
  • Create strawberry/pydantic/object_type.py with processing logic and decorators
  • Implement _get_pydantic_fields and conversion utilities in strawberry/pydantic/fields.py
  • Export new pydantic module in strawberry/pydantic/init.py
  • Update strawberry/init.py to include pydantic in the public API
strawberry/pydantic/object_type.py
strawberry/pydantic/fields.py
strawberry/pydantic/__init__.py
strawberry/__init__.py
Overhaul Pydantic documentation and remove experimental section
  • Expand docs/integrations/pydantic.md with installation, usage examples, decorator reference, advanced scenarios, and migration guide
  • Remove outdated experimental examples and flags
docs/integrations/pydantic.md
Restructure and extend test suite for Pydantic integration
  • Move experimental tests into a new tests/pydantic directory and split them into focused modules
  • Add comprehensive tests covering basic types, execution (queries/mutations/async), special features, nested types, and validation error handling
tests/pydantic/test_basic.py
tests/pydantic/test_execution.py
tests/pydantic/test_special_features.py
tests/pydantic/test_queries_mutations.py
tests/pydantic/test_nested_types.py
Cleanup unused experimental code and flags
  • Remove leftover experimental flags and references from strawberry.experimental.pydantic
  • Delete deprecated _strawberry_input_type usage
  • Tidy up compat module imports
strawberry/experimental/pydantic/_compat.py
Update CLAUDE.md and PLAN.md with new integration status
  • Detail completed implementation steps and migration path in PLAN.md
  • Provide guidance and commands for the repository in CLAUDE.md
PLAN.md
CLAUDE.md

Possibly linked issues

  • #1: The PR implements first-class Pydantic integration, including new decorators and updated documentation, directly addressing Pydantic V2 support and migration.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an issue from a review comment by replying to it. You can also reply to a review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull request title to generate a title at any time. You can also comment @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in the pull request body to generate a PR summary at any time exactly where you want it. You can also comment @sourcery-ai summary on the pull request to (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the pull request to resolve all Sourcery comments. Useful if you've already addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull request to dismiss all existing Sourcery reviews. Especially useful if you want to start fresh with a new review - don't forget to comment @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

  • Contact our support team for questions or feedback.
  • Visit our documentation for detailed guides and information.
  • Keep in touch with the Sourcery team by following us on X/Twitter, LinkedIn or GitHub.

sourcery-ai[bot] avatar Aug 02 '25 09:08 sourcery-ai[bot]

Codecov Report

:x: Patch coverage is 56.78818% with 1375 lines in your changes missing coverage. Please review. :white_check_mark: Project coverage is 91.27%. Comparing base (c03892c) to head (7067816).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3965      +/-   ##
==========================================
- Coverage   94.41%   91.27%   -3.14%     
==========================================
  Files         536      563      +27     
  Lines       35036    38186    +3150     
  Branches     1842     1913      +71     
==========================================
+ Hits        33079    34856    +1777     
- Misses       1659     3023    +1364     
- Partials      298      307       +9     
:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

codecov[bot] avatar Aug 02 '25 09:08 codecov[bot]

CodSpeed Performance Report

Merging #3965 will not alter performance

Comparing feature/pydantic-first-class (7067816) with main (c03892c)

Summary

✅ 28 untouched

codspeed-hq[bot] avatar Aug 02 '25 10:08 codspeed-hq[bot]

/pre-release

patrick91 avatar Aug 02 '25 12:08 patrick91

Pre-release

:wave:

Pre-release 0.279.0.dev.1754159379 [70130b4618633d5086793cc6e0b903c56ff1dc14] has been released on PyPi! :rocket: You can try it by doing:

poetry add strawberry-graphql==0.279.0.dev.1754159379

botberry avatar Aug 02 '25 12:08 botberry

/pre-release

patrick91 avatar Aug 02 '25 17:08 patrick91

/pre-release

patrick91 avatar Aug 02 '25 18:08 patrick91