0
# Experimental Features
1
2
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.
3
4
⚠️ **Warning**: Experimental features are not covered by semantic versioning and may change or be removed in future releases. Use with caution in production environments.
5
6
## Capabilities
7
8
### Pydantic Integration
9
10
Integration with Pydantic models for automatic GraphQL type generation and validation.
11
12
```python { .api }
13
# Pydantic type decorators
14
def pydantic.type(
15
model: Type[BaseModel],
16
*,
17
name: str = None,
18
description: str = None,
19
all_fields: bool = False
20
) -> Any:
21
"""
22
Convert Pydantic model to GraphQL object type.
23
24
Args:
25
model: Pydantic model class
26
name: Custom GraphQL type name
27
description: Type description
28
all_fields: Include all model fields (including private)
29
30
Returns:
31
GraphQL object type based on Pydantic model
32
"""
33
34
def pydantic.input(
35
model: Type[BaseModel],
36
*,
37
name: str = None,
38
description: str = None,
39
all_fields: bool = False
40
) -> Any:
41
"""
42
Convert Pydantic model to GraphQL input type.
43
44
Args:
45
model: Pydantic model class
46
name: Custom GraphQL input type name
47
description: Input type description
48
all_fields: Include all model fields
49
50
Returns:
51
GraphQL input type based on Pydantic model
52
"""
53
54
def pydantic.interface(
55
model: Type[BaseModel],
56
*,
57
name: str = None,
58
description: str = None
59
) -> Any:
60
"""
61
Convert Pydantic model to GraphQL interface type.
62
63
Args:
64
model: Pydantic model class
65
name: Custom GraphQL interface name
66
description: Interface description
67
68
Returns:
69
GraphQL interface type based on Pydantic model
70
"""
71
```
72
73
**Usage Examples:**
74
75
```python
76
from pydantic import BaseModel, Field, validator
77
import strawberry.experimental.pydantic
78
79
# Define Pydantic models
80
class UserModel(BaseModel):
81
id: int
82
name: str = Field(description="User's full name")
83
email: str = Field(description="User's email address")
84
age: int = Field(ge=0, le=150, description="User's age")
85
is_active: bool = True
86
87
@validator('email')
88
def validate_email(cls, v):
89
# Pydantic validation logic
90
if '@' not in v:
91
raise ValueError('Invalid email format')
92
return v.lower()
93
94
class CreateUserModel(BaseModel):
95
name: str = Field(min_length=1, max_length=100)
96
email: str
97
age: int = Field(ge=0, le=150)
98
99
# Convert to GraphQL types
100
@strawberry.experimental.pydantic.type(UserModel)
101
class User:
102
pass # Fields are automatically generated from Pydantic model
103
104
@strawberry.experimental.pydantic.input(CreateUserModel)
105
class CreateUserInput:
106
pass # Input fields generated from Pydantic model
107
108
# Use in GraphQL schema
109
@strawberry.type
110
class Query:
111
@strawberry.field
112
def user(self, id: int) -> User:
113
# Pydantic validation happens automatically
114
user_data = get_user_from_database(id)
115
return User.from_pydantic(UserModel(**user_data))
116
117
@strawberry.type
118
class Mutation:
119
@strawberry.mutation
120
def create_user(self, input: CreateUserInput) -> User:
121
# Input is automatically validated by Pydantic
122
user_model = input.to_pydantic() # Convert to Pydantic model
123
124
# Pydantic validation runs here
125
if not user_model.email:
126
raise ValueError("Email is required")
127
128
# Create user
129
user_data = create_user_in_database(user_model.dict())
130
return User.from_pydantic(UserModel(**user_data))
131
132
schema = strawberry.Schema(query=Query, mutation=Mutation)
133
```
134
135
### Advanced Pydantic Integration
136
137
```python
138
from typing import Optional, List
139
from pydantic import BaseModel, Field
140
from datetime import datetime
141
import strawberry.experimental.pydantic
142
143
# Complex Pydantic model with relationships
144
class AddressModel(BaseModel):
145
street: str
146
city: str
147
state: str
148
zip_code: str = Field(alias="zipCode")
149
150
class UserModel(BaseModel):
151
id: int
152
name: str
153
email: str
154
addresses: List[AddressModel] = []
155
created_at: datetime = Field(alias="createdAt")
156
metadata: dict = Field(default_factory=dict)
157
158
class Config:
159
# Pydantic configuration
160
allow_population_by_field_name = True
161
json_encoders = {
162
datetime: lambda v: v.isoformat()
163
}
164
165
# Convert complex model
166
@strawberry.experimental.pydantic.type(UserModel, all_fields=True)
167
class User:
168
# Additional GraphQL-specific fields
169
@strawberry.field
170
def full_address(self) -> Optional[str]:
171
if not self.addresses:
172
return None
173
addr = self.addresses[0]
174
return f"{addr.street}, {addr.city}, {addr.state} {addr.zip_code}"
175
176
# Partial model conversion (only specific fields)
177
@strawberry.experimental.pydantic.type(UserModel)
178
class PublicUser:
179
# Only expose safe fields
180
id: strawberry.auto
181
name: strawberry.auto
182
created_at: strawberry.auto
183
```
184
185
### Pydantic Error Handling
186
187
Error type conversion from Pydantic validation errors.
188
189
```python { .api }
190
def pydantic.error_type(
191
model: Type[BaseModel],
192
*,
193
name: str = None
194
) -> Any:
195
"""
196
Create GraphQL error type from Pydantic model validation errors.
197
198
Args:
199
model: Pydantic model class
200
name: Custom error type name
201
202
Returns:
203
GraphQL type for validation errors
204
"""
205
```
206
207
**Usage Example:**
208
209
```python
210
from pydantic import BaseModel, ValidationError
211
import strawberry.experimental.pydantic
212
213
class UserValidationModel(BaseModel):
214
name: str = Field(min_length=2, max_length=50)
215
email: str = Field(regex=r'^[^@]+@[^@]+\.[^@]+$')
216
age: int = Field(ge=13, le=120)
217
218
@strawberry.experimental.pydantic.error_type(UserValidationModel)
219
class UserValidationError:
220
pass
221
222
@strawberry.type
223
class CreateUserResult:
224
user: Optional[User]
225
errors: Optional[List[UserValidationError]]
226
227
@strawberry.type
228
class Mutation:
229
@strawberry.mutation
230
def create_user_with_validation(
231
self,
232
name: str,
233
email: str,
234
age: int
235
) -> CreateUserResult:
236
try:
237
# Validate with Pydantic
238
validated_data = UserValidationModel(
239
name=name,
240
email=email,
241
age=age
242
)
243
244
# Create user
245
user = create_user(validated_data.dict())
246
return CreateUserResult(user=user, errors=None)
247
248
except ValidationError as e:
249
# Convert Pydantic errors to GraphQL errors
250
errors = [
251
UserValidationError.from_pydantic_error(error)
252
for error in e.errors()
253
]
254
return CreateUserResult(user=None, errors=errors)
255
```
256
257
### Pydantic Exceptions
258
259
Exception handling for Pydantic integration.
260
261
```python { .api }
262
class UnregisteredTypeException(Exception):
263
"""Exception raised when trying to use unregistered Pydantic type."""
264
265
def __init__(self, type_name: str):
266
self.type_name = type_name
267
super().__init__(f"Pydantic type '{type_name}' is not registered")
268
```
269
270
**Usage Example:**
271
272
```python
273
try:
274
# This might raise UnregisteredTypeException
275
@strawberry.experimental.pydantic.type(SomeUnregisteredModel)
276
class SomeType:
277
pass
278
except strawberry.experimental.pydantic.UnregisteredTypeException as e:
279
print(f"Type registration failed: {e.type_name}")
280
# Handle the error appropriately
281
```
282
283
## Advanced Experimental Patterns
284
285
### Pydantic Model Inheritance
286
287
```python
288
from pydantic import BaseModel
289
import strawberry.experimental.pydantic
290
291
# Base Pydantic model
292
class BaseEntity(BaseModel):
293
id: int
294
created_at: datetime
295
updated_at: datetime
296
297
# Derived models
298
class UserModel(BaseEntity):
299
name: str
300
email: str
301
302
class PostModel(BaseEntity):
303
title: str
304
content: str
305
author_id: int
306
307
# Convert to GraphQL with inheritance
308
@strawberry.experimental.pydantic.interface(BaseEntity)
309
class Entity:
310
pass
311
312
@strawberry.experimental.pydantic.type(UserModel)
313
class User(Entity):
314
pass
315
316
@strawberry.experimental.pydantic.type(PostModel)
317
class Post(Entity):
318
pass
319
```
320
321
### Custom Field Mapping
322
323
```python
324
from pydantic import BaseModel, Field
325
import strawberry.experimental.pydantic
326
327
class UserModel(BaseModel):
328
user_id: int = Field(alias="id")
329
full_name: str = Field(alias="name")
330
email_address: str = Field(alias="email")
331
332
# Custom field mapping
333
@strawberry.experimental.pydantic.type(
334
UserModel,
335
name="User",
336
description="User account with custom field mapping"
337
)
338
class User:
339
# Override specific fields
340
@strawberry.field(name="displayName")
341
def full_name(self) -> str:
342
return self.full_name.title()
343
344
# Add computed fields
345
@strawberry.field
346
def username(self) -> Optional[str]:
347
return self.email_address.split('@')[0] if self.email_address else None
348
```
349
350
### Pydantic with DataLoader
351
352
```python
353
from pydantic import BaseModel
354
from strawberry.dataloader import DataLoader
355
import strawberry.experimental.pydantic
356
357
class UserModel(BaseModel):
358
id: int
359
name: str
360
email: str
361
department_id: int
362
363
class DepartmentModel(BaseModel):
364
id: int
365
name: str
366
description: str
367
368
@strawberry.experimental.pydantic.type(UserModel)
369
class User:
370
@strawberry.field
371
async def department(self, info: strawberry.Info) -> "Department":
372
# Use DataLoader with Pydantic models
373
dept_data = await info.context.department_loader.load(self.department_id)
374
return Department.from_pydantic(DepartmentModel(**dept_data))
375
376
@strawberry.experimental.pydantic.type(DepartmentModel)
377
class Department:
378
pass
379
380
# DataLoader for departments
381
async def load_departments(dept_ids: List[int]) -> List[DepartmentModel]:
382
departments_data = await database.fetch_departments_by_ids(dept_ids)
383
return [DepartmentModel(**dept) for dept in departments_data]
384
385
department_loader = DataLoader(load_departments)
386
```
387
388
### Pydantic Subscriptions
389
390
```python
391
from pydantic import BaseModel
392
import strawberry.experimental.pydantic
393
from typing import AsyncIterator
394
395
class NotificationModel(BaseModel):
396
id: int
397
user_id: int
398
message: str
399
type: str
400
created_at: datetime
401
402
@strawberry.experimental.pydantic.type(NotificationModel)
403
class Notification:
404
pass
405
406
@strawberry.type
407
class Subscription:
408
@strawberry.subscription
409
async def user_notifications(
410
self,
411
user_id: int
412
) -> AsyncIterator[Notification]:
413
# Subscribe to user notifications
414
async for notification_data in notification_stream(user_id):
415
# Pydantic validation on streaming data
416
validated_notification = NotificationModel(**notification_data)
417
yield Notification.from_pydantic(validated_notification)
418
```
419
420
## Experimental Configuration
421
422
### Feature Flags
423
424
```python
425
import strawberry
426
from strawberry.experimental import enable_pydantic_integration
427
428
# Enable experimental features
429
enable_pydantic_integration()
430
431
# Or configure specific experimental features
432
strawberry.experimental.configure(
433
pydantic_integration=True,
434
auto_camel_case_pydantic=True,
435
strict_pydantic_validation=True
436
)
437
```
438
439
### Migration Path
440
441
```python
442
# Gradual migration from regular Strawberry to Pydantic integration
443
@strawberry.type
444
class User:
445
id: strawberry.ID
446
name: str
447
email: str
448
449
# Gradually add Pydantic validation
450
@strawberry.field
451
def validated_profile(self) -> "UserProfile":
452
# Use Pydantic for new fields
453
profile_data = get_user_profile(self.id)
454
return UserProfile.from_pydantic(UserProfileModel(**profile_data))
455
456
# New features use Pydantic from the start
457
class UserProfileModel(BaseModel):
458
bio: str = Field(max_length=500)
459
website: Optional[str] = Field(regex=r'^https?://')
460
social_links: List[str] = []
461
462
@strawberry.experimental.pydantic.type(UserProfileModel)
463
class UserProfile:
464
pass
465
```
466
467
## Limitations and Considerations
468
469
### Current Limitations
470
471
1. **Stability**: Experimental features may have breaking changes
472
2. **Performance**: Some features may have performance implications
473
3. **Documentation**: Limited documentation compared to stable features
474
4. **Third-party Integration**: May not work with all third-party tools
475
476
### Best Practices
477
478
```python
479
# Use feature detection
480
import strawberry.experimental.pydantic
481
482
if hasattr(strawberry.experimental.pydantic, 'type'):
483
# Use Pydantic integration
484
@strawberry.experimental.pydantic.type(UserModel)
485
class User:
486
pass
487
else:
488
# Fallback to regular Strawberry types
489
@strawberry.type
490
class User:
491
id: int
492
name: str
493
email: str
494
495
# Version pinning for experimental features
496
# In requirements.txt:
497
# strawberry-graphql==0.281.0 # Pin exact version for stability
498
```
499
500
### Testing Experimental Features
501
502
```python
503
import pytest
504
from pydantic import ValidationError
505
import strawberry.experimental.pydantic
506
507
def test_pydantic_integration():
508
"""Test Pydantic model integration."""
509
510
class TestModel(BaseModel):
511
name: str = Field(min_length=1)
512
age: int = Field(ge=0)
513
514
@strawberry.experimental.pydantic.type(TestModel)
515
class TestType:
516
pass
517
518
# Test successful conversion
519
valid_data = TestModel(name="Alice", age=30)
520
graphql_obj = TestType.from_pydantic(valid_data)
521
assert graphql_obj.name == "Alice"
522
assert graphql_obj.age == 30
523
524
# Test validation error handling
525
with pytest.raises(ValidationError):
526
TestModel(name="", age=-5) # Invalid data
527
528
def test_experimental_feature_availability():
529
"""Test that experimental features are available."""
530
531
# Check if feature is available
532
assert hasattr(strawberry.experimental.pydantic, 'type')
533
assert hasattr(strawberry.experimental.pydantic, 'input')
534
assert hasattr(strawberry.experimental.pydantic, 'interface')
535
```