-
-
Notifications
You must be signed in to change notification settings - Fork 585
WIP #3965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
WIP #3965
Conversation
Reviewer's GuideThis 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
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3965 +/- ##
==========================================
- Coverage 94.40% 93.97% -0.44%
==========================================
Files 528 549 +21
Lines 34371 36335 +1964
Branches 1803 1882 +79
==========================================
+ Hits 32449 34144 +1695
- Misses 1630 1880 +250
- Partials 292 311 +19 🚀 New features to boost your workflow:
|
CodSpeed Performance ReportMerging #3965 will not alter performanceComparing Summary
|
/pre-release |
Pre-release👋 Pre-release 0.279.0.dev.1754159379 [70130b4] has been released on PyPi! 🚀 poetry add strawberry-graphql==0.279.0.dev.1754159379 |
/pre-release |
/pre-release |
tests/pydantic/test_error.py
Outdated
try: | ||
# Validate the input using Pydantic | ||
validated = CreateUserModel(name=input.name, age=input.age) | ||
# Simulate successful creation | ||
return CreateUserSuccess( | ||
user_id=1, message=f"User {validated.name} created successfully" | ||
) | ||
except pydantic.ValidationError as e: | ||
return Error.from_validation_error(e) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try: | |
# Validate the input using Pydantic | |
validated = CreateUserModel(name=input.name, age=input.age) | |
# Simulate successful creation | |
return CreateUserSuccess( | |
user_id=1, message=f"User {validated.name} created successfully" | |
) | |
except pydantic.ValidationError as e: | |
return Error.from_validation_error(e) | |
# Validate the input using Pydantic | |
validated = CreateUserModel(name=input.name, age=input.age) | |
return CreateUserSuccess( | |
user_id=1, message=f"User {validated.name} created successfully" | |
) | |
for more information, see https://pre-commit.ci
"""Represents a single validation error detail.""" | ||
|
||
type: str | ||
loc: list[str] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this might return something_else
instead of somethingElse
|
||
|
||
@strawberry.pydantic.type | ||
class User(BaseModel): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class User(BaseModel): | |
class User(Node): |
name: str = Field(alias="fullName") | ||
age: int = Field(alias="yearsOld") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name: str = Field(alias="fullName") | |
age: int = Field(alias="yearsOld") | |
name: Annotated[str, Field(alias="fullName")] | |
age: Annotated[int, Field(alias="yearsOld")] |
potentially also allow strawberry.pydantic.field
?
### Optional Fields | ||
|
||
Pydantic optional fields are properly handled: | ||
|
||
```python | ||
from typing import Optional | ||
|
||
|
||
@strawberry.pydantic.type | ||
class User(BaseModel): | ||
name: str | ||
email: Optional[str] = None | ||
age: Optional[int] = None | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
### Optional Fields | |
Pydantic optional fields are properly handled: | |
```python | |
from typing import Optional | |
@strawberry.pydantic.type | |
class User(BaseModel): | |
name: str | |
email: Optional[str] = None | |
age: Optional[int] = None | |
``` |
if user.password: | ||
return user | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a silly example, let's change it :D
docs/integrations/pydantic.md
Outdated
### Nested Types | ||
|
||
Pydantic models can contain other Pydantic models: | ||
|
||
```python | ||
@strawberry.pydantic.type | ||
class Address(BaseModel): | ||
street: str | ||
city: str | ||
zipcode: str | ||
|
||
|
||
@strawberry.pydantic.type | ||
class User(BaseModel): | ||
name: str | ||
address: Address | ||
``` | ||
|
||
### Lists and Collections | ||
|
||
Lists of Pydantic models work seamlessly: | ||
|
||
```python | ||
from typing import List | ||
|
||
|
||
@strawberry.pydantic.type | ||
class User(BaseModel): | ||
name: str | ||
age: int | ||
|
||
|
||
@strawberry.type | ||
class Query: | ||
@strawberry.field | ||
def get_users(self) -> List[User]: | ||
return [User(name="John", age=30), User(name="Jane", age=25)] | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
### Nested Types | |
Pydantic models can contain other Pydantic models: | |
```python | |
@strawberry.pydantic.type | |
class Address(BaseModel): | |
street: str | |
city: str | |
zipcode: str | |
@strawberry.pydantic.type | |
class User(BaseModel): | |
name: str | |
address: Address | |
``` | |
### Lists and Collections | |
Lists of Pydantic models work seamlessly: | |
```python | |
from typing import List | |
@strawberry.pydantic.type | |
class User(BaseModel): | |
name: str | |
age: int | |
@strawberry.type | |
class Query: | |
@strawberry.field | |
def get_users(self) -> List[User]: | |
return [User(name="John", age=30), User(name="Jane", age=25)] | |
``` |
name: str | ||
age: int | ||
|
||
@validator("age") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is Pydantic v1, we should use field_validator
Let's also maybe use number: Annotated[int, AfterValidator(is_even)]
## Conversion Methods | ||
|
||
Decorated models automatically get conversion methods: | ||
|
||
```python | ||
@strawberry.pydantic.type | ||
class User(BaseModel): | ||
name: str | ||
age: int | ||
|
||
|
||
# Create from existing Pydantic instance | ||
pydantic_user = User(name="John", age=30) | ||
strawberry_user = User.from_pydantic(pydantic_user) | ||
|
||
# Convert back to Pydantic | ||
converted_back = strawberry_user.to_pydantic() | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
## Conversion Methods | |
Decorated models automatically get conversion methods: | |
```python | |
@strawberry.pydantic.type | |
class User(BaseModel): | |
name: str | |
age: int | |
# Create from existing Pydantic instance | |
pydantic_user = User(name="John", age=30) | |
strawberry_user = User.from_pydantic(pydantic_user) | |
# Convert back to Pydantic | |
converted_back = strawberry_user.to_pydantic() | |
``` |
we don't even need this
converted_back = strawberry_user.to_pydantic() | ||
``` | ||
|
||
## Migration from Experimental |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove this
from .error import Error | ||
from .object_type import input as input_decorator | ||
from .object_type import interface | ||
from .object_type import type as type_decorator | ||
|
||
# Re-export with proper names | ||
input = input_decorator | ||
type = type_decorator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this good
"""Represents a single validation error detail.""" | ||
|
||
type: str | ||
loc: list[str] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loc: list[str] | |
location: list[str] |
|
||
type: str | ||
loc: list[str] | ||
msg: str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
""" | ||
return Error( | ||
errors=[ | ||
ErrorDetail( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ErrorDetail( | |
ErrorDetail.from_error |
def _get_interfaces(cls: builtins.type[Any]) -> list[StrawberryObjectDefinition]: | ||
"""Extract interfaces from a class's inheritance hierarchy.""" | ||
interfaces: list[StrawberryObjectDefinition] = [] | ||
|
||
for base in cls.__mro__[1:]: # Exclude current class | ||
if hasattr(base, "__strawberry_definition__"): | ||
type_definition = base.__strawberry_definition__ | ||
if type_definition.is_interface: | ||
interfaces.append(type_definition) | ||
|
||
return interfaces |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reuse the one from strawberry/types/object_type.py
tests/pydantic/test_basic.py
Outdated
age: int | ||
password: Optional[str] | ||
|
||
definition: StrawberryObjectDefinition = User.__strawberry_definition__ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's use the util to get the definition
Summary by Sourcery
Implement first-class Pydantic support in Strawberry GraphQL by introducing dedicated decorators and utilities in a new pydantic module, fully replacing the experimental integration, enhancing schema generation and runtime behavior, enriching documentation, and adding an extensive test suite.
New Features:
Enhancements:
Documentation:
Tests:
Chores: