0
# Field Definitions and Resolvers
1
2
Field definition system with custom resolvers, descriptions, permissions, and advanced configuration options for GraphQL fields. This system allows fine-grained control over how GraphQL fields are resolved and secured.
3
4
## Capabilities
5
6
### Field Decorator
7
8
Defines GraphQL fields with custom resolvers and configuration options.
9
10
```python { .api }
11
def field(
12
resolver: Callable = None,
13
*,
14
name: str = None,
15
description: str = None,
16
deprecation_reason: str = None,
17
permission_classes: List[Type[BasePermission]] = None,
18
extensions: List[FieldExtension] = None,
19
default: Any = dataclasses.NOTHING,
20
default_factory: Callable = dataclasses.NOTHING
21
) -> Any:
22
"""
23
Decorator to define GraphQL fields with custom configuration.
24
25
Args:
26
resolver: Custom resolver function for the field
27
name: Custom field name (defaults to function/attribute name)
28
description: Field description for GraphQL schema
29
deprecation_reason: Deprecation message if field is deprecated
30
permission_classes: List of permission classes for field-level authorization
31
extensions: List of field extensions to apply
32
default: Default value for the field
33
default_factory: Factory function for default value
34
35
Returns:
36
Configured GraphQL field
37
"""
38
```
39
40
**Usage Examples:**
41
42
```python
43
@strawberry.type
44
class User:
45
id: strawberry.ID
46
name: str
47
email: str = strawberry.field(description="User's email address")
48
49
@strawberry.field(description="User's full display name")
50
def display_name(self) -> str:
51
return f"{self.name} <{self.email}>"
52
53
@strawberry.field(
54
description="User's posts",
55
permission_classes=[IsAuthenticated]
56
)
57
def posts(self, info: strawberry.Info) -> List[Post]:
58
return get_user_posts(self.id, info.context.user)
59
60
@strawberry.field(deprecation_reason="Use display_name instead")
61
def full_name(self) -> str:
62
return self.display_name()
63
64
# Field with resolver arguments
65
@strawberry.field
66
def posts_by_tag(self, tag: str, limit: int = 10) -> List[Post]:
67
return get_posts_by_tag(self.id, tag, limit)
68
```
69
70
### Mutation Decorator
71
72
Defines GraphQL mutation fields for data modification operations.
73
74
```python { .api }
75
def mutation(
76
resolver: Callable = None,
77
*,
78
name: str = None,
79
description: str = None,
80
permission_classes: List[Type[BasePermission]] = None,
81
extensions: List[FieldExtension] = None
82
) -> Any:
83
"""
84
Decorator to define GraphQL mutation fields.
85
86
Args:
87
resolver: Resolver function for the mutation
88
name: Custom mutation name
89
description: Mutation description
90
permission_classes: Permission classes for authorization
91
extensions: Field extensions to apply
92
93
Returns:
94
Configured GraphQL mutation field
95
"""
96
```
97
98
**Usage Example:**
99
100
```python
101
@strawberry.type
102
class Mutation:
103
@strawberry.mutation(
104
description="Create a new user account",
105
permission_classes=[IsAdmin]
106
)
107
def create_user(
108
self,
109
name: str,
110
email: str,
111
age: int = 18
112
) -> User:
113
# Validation and user creation logic
114
if not email or "@" not in email:
115
raise ValueError("Invalid email address")
116
117
return User(
118
id=generate_id(),
119
name=name,
120
email=email,
121
age=age
122
)
123
124
@strawberry.mutation
125
def update_user(
126
self,
127
id: strawberry.ID,
128
input: UpdateUserInput
129
) -> User:
130
user = get_user(id)
131
if input.name is not strawberry.UNSET:
132
user.name = input.name
133
if input.email is not strawberry.UNSET:
134
user.email = input.email
135
save_user(user)
136
return user
137
```
138
139
### Subscription Decorator
140
141
Defines GraphQL subscription fields for real-time data streaming.
142
143
```python { .api }
144
def subscription(
145
resolver: Callable = None,
146
*,
147
name: str = None,
148
description: str = None,
149
permission_classes: List[Type[BasePermission]] = None,
150
extensions: List[FieldExtension] = None
151
) -> Any:
152
"""
153
Decorator to define GraphQL subscription fields.
154
155
Args:
156
resolver: Async generator function for the subscription
157
name: Custom subscription name
158
description: Subscription description
159
permission_classes: Permission classes for authorization
160
extensions: Field extensions to apply
161
162
Returns:
163
Configured GraphQL subscription field
164
"""
165
```
166
167
**Usage Example:**
168
169
```python
170
@strawberry.type
171
class Subscription:
172
@strawberry.subscription(description="Real-time user updates")
173
async def user_updates(self, user_id: strawberry.ID) -> AsyncIterator[User]:
174
# Subscribe to user updates from message broker/database
175
async for update in subscribe_to_user_updates(user_id):
176
yield User(**update)
177
178
@strawberry.subscription
179
async def message_stream(
180
self,
181
room_id: strawberry.ID,
182
info: strawberry.Info
183
) -> AsyncIterator[Message]:
184
# Check permissions
185
if not can_access_room(info.context.user, room_id):
186
raise PermissionError("Access denied")
187
188
async for message in message_broker.subscribe(f"room:{room_id}"):
189
yield Message(**message)
190
```
191
192
### Argument Definition
193
194
Defines GraphQL field arguments with validation and default values.
195
196
```python { .api }
197
def argument(
198
name: str = None,
199
description: str = None,
200
default: Any = dataclasses.NOTHING,
201
default_factory: Callable = dataclasses.NOTHING
202
) -> Any:
203
"""
204
Define GraphQL field arguments with metadata.
205
206
Args:
207
name: Custom argument name (defaults to parameter name)
208
description: Argument description
209
default: Default value for the argument
210
default_factory: Factory function for default value
211
212
Returns:
213
Configured GraphQL argument
214
"""
215
```
216
217
**Usage Example:**
218
219
```python
220
@strawberry.type
221
class Query:
222
@strawberry.field
223
def users(
224
self,
225
limit: int = strawberry.argument(
226
default=10,
227
description="Maximum number of users to return"
228
),
229
offset: int = strawberry.argument(
230
default=0,
231
description="Number of users to skip"
232
),
233
filter_name: str = strawberry.argument(
234
default=None,
235
description="Filter users by name (case-insensitive)"
236
)
237
) -> List[User]:
238
return get_users(limit=limit, offset=offset, name_filter=filter_name)
239
```
240
241
## Permission System
242
243
### Base Permission Class
244
245
```python { .api }
246
class BasePermission:
247
"""Base class for field-level permissions."""
248
249
message: str = "Permission denied"
250
251
def has_permission(
252
self,
253
source: Any,
254
info: Info,
255
**kwargs
256
) -> bool:
257
"""
258
Synchronous permission check.
259
260
Args:
261
source: Parent object being resolved
262
info: GraphQL execution info
263
**kwargs: Field arguments
264
265
Returns:
266
True if permission granted, False otherwise
267
"""
268
269
async def has_permission_async(
270
self,
271
source: Any,
272
info: Info,
273
**kwargs
274
) -> bool:
275
"""
276
Asynchronous permission check.
277
278
Args:
279
source: Parent object being resolved
280
info: GraphQL execution info
281
**kwargs: Field arguments
282
283
Returns:
284
True if permission granted, False otherwise
285
"""
286
```
287
288
**Usage Example:**
289
290
```python
291
class IsAuthenticated(strawberry.BasePermission):
292
message = "You must be authenticated to access this field"
293
294
def has_permission(self, source: Any, info: strawberry.Info, **kwargs) -> bool:
295
return info.context.user is not None
296
297
class IsOwner(strawberry.BasePermission):
298
message = "You can only access your own data"
299
300
def has_permission(self, source: Any, info: strawberry.Info, **kwargs) -> bool:
301
return info.context.user and source.user_id == info.context.user.id
302
303
class IsAdmin(strawberry.BasePermission):
304
message = "Admin access required"
305
306
async def has_permission_async(self, source: Any, info: strawberry.Info, **kwargs) -> bool:
307
user = info.context.user
308
if not user:
309
return False
310
311
# Async database check
312
return await is_user_admin(user.id)
313
314
@strawberry.type
315
class User:
316
id: strawberry.ID
317
name: str
318
319
@strawberry.field(permission_classes=[IsAuthenticated, IsOwner])
320
def email(self) -> str:
321
return self._email
322
323
@strawberry.field(permission_classes=[IsAdmin])
324
def admin_notes(self) -> str:
325
return self._admin_notes
326
```
327
328
## Resolver Context
329
330
### Info Object
331
332
The Info object provides context and metadata to resolvers.
333
334
```python { .api }
335
class Info:
336
"""GraphQL execution info passed to resolvers."""
337
338
context: Any # Request context (user, database connections, etc.)
339
field_name: str # Name of the current field being resolved
340
field_nodes: List[FieldNode] # AST nodes for the field
341
is_subscription: bool # Whether this is a subscription field
342
operation_name: str # Name of the GraphQL operation
343
path: List[Union[str, int]] # Path to this field in the result
344
return_type: Type # Expected return type of the field
345
root_value: Any # Root value passed to the schema
346
schema: Schema # GraphQL schema instance
347
variable_values: Dict[str, Any] # Variables passed to the operation
348
```
349
350
**Usage Example:**
351
352
```python
353
@strawberry.type
354
class Query:
355
@strawberry.field
356
def current_user(self, info: strawberry.Info) -> Optional[User]:
357
# Access request context
358
return info.context.user
359
360
@strawberry.field
361
def field_info(self, info: strawberry.Info) -> str:
362
return f"Resolving field '{info.field_name}' at path {info.path}"
363
364
@strawberry.field
365
def debug_info(self, info: strawberry.Info) -> Dict[str, Any]:
366
return {
367
"operation_name": info.operation_name,
368
"variables": info.variable_values,
369
"is_subscription": info.is_subscription
370
}
371
```
372
373
### Parent Object Access
374
375
```python { .api }
376
Parent: TypeVar # Type hint for parent object in resolvers
377
```
378
379
**Usage Example:**
380
381
```python
382
@strawberry.type
383
class Post:
384
id: strawberry.ID
385
title: str
386
author_id: strawberry.ID
387
388
@strawberry.field
389
def author(self) -> User:
390
# 'self' is the parent Post object
391
return get_user(self.author_id)
392
393
@strawberry.type
394
class User:
395
id: strawberry.ID
396
name: str
397
398
@strawberry.field
399
def posts(self, parent: strawberry.Parent[User]) -> List[Post]:
400
# Explicit parent type annotation
401
return get_posts_by_author(parent.id)
402
```
403
404
## Advanced Field Configuration
405
406
### Field Extensions
407
408
Field extensions allow custom logic to be applied to individual fields.
409
410
```python { .api }
411
class FieldExtension:
412
"""Base class for field-level extensions."""
413
414
def apply(self, field: StrawberryField) -> StrawberryField:
415
"""Apply extension logic to a field."""
416
```
417
418
### Default Values and Factories
419
420
```python { .api }
421
@strawberry.type
422
class User:
423
id: strawberry.ID
424
name: str
425
created_at: datetime = strawberry.field(
426
default_factory=datetime.utcnow,
427
description="Account creation timestamp"
428
)
429
is_active: bool = strawberry.field(
430
default=True,
431
description="Whether the user account is active"
432
)
433
preferences: Dict[str, Any] = strawberry.field(
434
default_factory=dict,
435
description="User preferences and settings"
436
)
437
```
438
439
### Complex Resolver Examples
440
441
```python
442
@strawberry.type
443
class Query:
444
@strawberry.field
445
async def search_users(
446
self,
447
query: str,
448
limit: int = 20,
449
info: strawberry.Info
450
) -> List[User]:
451
"""Async resolver with database operations."""
452
async with info.context.database.transaction():
453
results = await search_users_in_database(
454
query=query,
455
limit=limit,
456
user_context=info.context.user
457
)
458
return [User(**user_data) for user_data in results]
459
460
@strawberry.field
461
def paginated_posts(
462
self,
463
after: str = None,
464
first: int = 10
465
) -> PostConnection:
466
"""Cursor-based pagination resolver."""
467
posts = get_posts_after_cursor(after, first + 1) # Get one extra
468
469
edges = [
470
PostEdge(node=post, cursor=encode_cursor(post.id))
471
for post in posts[:first]
472
]
473
474
return PostConnection(
475
edges=edges,
476
page_info=PageInfo(
477
has_next_page=len(posts) > first,
478
end_cursor=edges[-1].cursor if edges else None
479
)
480
)
481
```