0
# DSL Query Builder
1
2
Domain-specific language for programmatic GraphQL query construction without string templates. Provides type-safe query building with schema introspection, variable handling, fragment support, and automatic field name conversion.
3
4
## Capabilities
5
6
### Core DSL Functions
7
8
Primary functions for creating and converting DSL operations into executable GraphQL requests.
9
10
```python { .api }
11
def dsl_gql(*operations, **operations_with_name) -> GraphQLRequest:
12
"""
13
Convert DSL operations into executable GraphQL requests.
14
15
Args:
16
*operations: DSLExecutable instances (DSLQuery, DSLMutation, DSLSubscription, DSLFragment)
17
**operations_with_name: Same as above but with operation names as keys
18
19
Returns:
20
GraphQLRequest that can be executed by gql clients
21
22
Example:
23
dsl_gql(query) or dsl_gql(GetUser=query, CreateUser=mutation)
24
"""
25
```
26
27
### Schema Integration
28
29
Classes for integrating with GraphQL schemas to provide type-safe query building.
30
31
```python { .api }
32
class DSLSchema:
33
def __init__(self, schema: GraphQLSchema):
34
"""
35
Root class for DSL operations providing access to schema types.
36
37
Args:
38
schema: GraphQL schema object for type introspection
39
40
Usage:
41
ds = DSLSchema(client.schema)
42
ds.Query.user # Returns DSLType for User queries
43
ds.User.name # Returns DSLField for User.name field
44
"""
45
46
# Dynamic attributes: Any schema type name becomes a DSLType instance
47
48
class DSLType:
49
"""
50
Represents GraphQL object/interface types in DSL.
51
52
Accessed via DSLSchema attributes (ds.Query, ds.User, etc.)
53
Dynamic attributes: Field names become DSLField instances
54
Supports snake_case to camelCase conversion for field names
55
"""
56
57
# Dynamic attributes: Field names become DSLField instances
58
59
class DSLField:
60
"""
61
Represents GraphQL fields with arguments and sub-selections.
62
63
Accessed via DSLType attributes or method calls.
64
"""
65
66
def args(self, **kwargs) -> DSLField:
67
"""
68
Set field arguments.
69
70
Args:
71
**kwargs: Field arguments as keyword arguments
72
73
Returns:
74
DSLField with arguments set
75
76
Example:
77
ds.Query.user.args(id="123")
78
"""
79
80
def select(self, *fields, **fields_with_alias) -> DSLField:
81
"""
82
Select sub-fields for this field.
83
84
Args:
85
*fields: DSLField instances to select
86
**fields_with_alias: DSLField instances with custom aliases
87
88
Returns:
89
DSLField with sub-field selections
90
91
Example:
92
ds.Query.user.select(ds.User.name, ds.User.email)
93
ds.Query.user.select(user_name=ds.User.name)
94
"""
95
96
def alias(self, alias: str) -> DSLField:
97
"""
98
Set field alias.
99
100
Args:
101
alias: Custom alias for this field
102
103
Returns:
104
DSLField with alias set
105
106
Example:
107
ds.User.name.alias("userName")
108
"""
109
110
def __call__(self, **kwargs) -> DSLField:
111
"""
112
Shorthand for args() method.
113
114
Args:
115
**kwargs: Field arguments
116
117
Returns:
118
DSLField with arguments set
119
120
Example:
121
ds.Query.user(id="123") # Same as ds.Query.user.args(id="123")
122
"""
123
124
class DSLMetaField:
125
def __init__(self, name: str):
126
"""
127
Represents GraphQL meta-fields.
128
129
Args:
130
name: Meta-field name ("__typename", "__schema", "__type")
131
132
Example:
133
DSLMetaField("__typename")
134
"""
135
```
136
137
### Operation Classes
138
139
Classes for building different types of GraphQL operations.
140
141
```python { .api }
142
class DSLQuery:
143
def __init__(self, *fields, **fields_with_alias):
144
"""
145
Represents GraphQL Query operations.
146
147
Args:
148
*fields: DSLField instances to include in query
149
**fields_with_alias: DSLField instances with custom aliases
150
151
Example:
152
DSLQuery(ds.Query.user.select(ds.User.name, ds.User.email))
153
"""
154
155
class DSLMutation:
156
def __init__(self, *fields, **fields_with_alias):
157
"""
158
Represents GraphQL Mutation operations.
159
160
Args:
161
*fields: DSLField instances to include in mutation
162
**fields_with_alias: DSLField instances with custom aliases
163
164
Example:
165
DSLMutation(ds.Mutation.createUser.args(input=user_input).select(ds.User.id))
166
"""
167
168
class DSLSubscription:
169
def __init__(self, *fields, **fields_with_alias):
170
"""
171
Represents GraphQL Subscription operations.
172
173
Args:
174
*fields: DSLField instances to include in subscription
175
**fields_with_alias: DSLField instances with custom aliases
176
177
Example:
178
DSLSubscription(ds.Subscription.messageAdded.select(ds.Message.content))
179
"""
180
```
181
182
### Fragment Support
183
184
Classes for creating reusable GraphQL fragments.
185
186
```python { .api }
187
class DSLFragment:
188
def __init__(self, name: str):
189
"""
190
Represents named GraphQL fragments.
191
192
Args:
193
name: Fragment name
194
195
Example:
196
user_info = DSLFragment("UserInfo")
197
"""
198
199
def on(self, type_condition: DSLType) -> DSLFragment:
200
"""
201
Set fragment type condition.
202
203
Args:
204
type_condition: DSLType representing the fragment type
205
206
Returns:
207
DSLFragment with type condition set
208
209
Example:
210
user_info.on(ds.User)
211
"""
212
213
def select(self, *fields, **fields_with_alias) -> DSLFragment:
214
"""
215
Select fields for this fragment.
216
217
Args:
218
*fields: DSLField instances to include
219
**fields_with_alias: DSLField instances with aliases
220
221
Returns:
222
DSLFragment with field selections
223
224
Example:
225
user_info.select(ds.User.name, ds.User.email)
226
"""
227
228
class DSLInlineFragment:
229
def __init__(self, *fields, **fields_with_alias):
230
"""
231
Represents inline GraphQL fragments.
232
233
Args:
234
*fields: DSLField instances to include
235
**fields_with_alias: DSLField instances with aliases
236
237
Example:
238
DSLInlineFragment(ds.User.name).on(ds.User)
239
"""
240
241
def on(self, type_condition: DSLType) -> DSLInlineFragment:
242
"""
243
Set type condition for inline fragment.
244
245
Args:
246
type_condition: DSLType for fragment condition
247
248
Returns:
249
DSLInlineFragment with type condition
250
251
Example:
252
DSLInlineFragment(ds.User.name).on(ds.User)
253
"""
254
255
def select(self, *fields, **fields_with_alias) -> DSLInlineFragment:
256
"""
257
Select additional fields for inline fragment.
258
259
Args:
260
*fields: DSLField instances to add
261
**fields_with_alias: DSLField instances with aliases
262
263
Returns:
264
DSLInlineFragment with additional selections
265
"""
266
```
267
268
### Variable System
269
270
Classes for handling GraphQL variables in DSL operations.
271
272
```python { .api }
273
class DSLVariableDefinitions:
274
def __init__(self):
275
"""
276
Container for operation variables.
277
278
Dynamic attributes: Variable names become DSLVariable instances
279
280
Example:
281
vars = DSLVariableDefinitions()
282
vars.user_id # Returns DSLVariable("user_id")
283
"""
284
285
# Dynamic attributes: Variable names become DSLVariable instances
286
287
class DSLVariable:
288
"""
289
Represents GraphQL variables in operations.
290
291
Accessed via DSLVariableDefinitions attributes.
292
"""
293
294
def set_type(self, type_: GraphQLInputType) -> DSLVariable:
295
"""
296
Set variable type.
297
298
Args:
299
type_: GraphQL input type for this variable
300
301
Returns:
302
DSLVariable with type set
303
304
Example:
305
vars.user_id.set_type(GraphQLNonNull(GraphQLID))
306
"""
307
308
def default(self, default_value: Any) -> DSLVariable:
309
"""
310
Set default value for variable.
311
312
Args:
313
default_value: Default value if not provided
314
315
Returns:
316
DSLVariable with default value set
317
318
Example:
319
vars.limit.default(10)
320
"""
321
```
322
323
## Usage Examples
324
325
### Basic DSL Query Building
326
327
```python
328
from gql import Client
329
from gql.dsl import DSLSchema, DSLQuery, dsl_gql
330
from gql.transport.requests import RequestsHTTPTransport
331
332
# Setup client and DSL schema
333
transport = RequestsHTTPTransport(url="https://api.example.com/graphql")
334
client = Client(transport=transport, fetch_schema_from_transport=True)
335
336
# Create DSL schema from client schema
337
ds = DSLSchema(client.schema)
338
339
# Build query using DSL
340
query = DSLQuery(
341
ds.Query.user(id="123").select(
342
ds.User.name,
343
ds.User.email,
344
ds.User.posts.select(
345
ds.Post.title,
346
ds.Post.content,
347
ds.Post.publishedAt
348
)
349
)
350
)
351
352
# Convert to executable request and execute
353
request = dsl_gql(query)
354
result = client.execute(request)
355
356
print(result["user"]["name"])
357
```
358
359
### Field Arguments and Aliases
360
361
```python
362
from gql.dsl import DSLSchema, DSLQuery, dsl_gql
363
364
# Build query with arguments and aliases
365
query = DSLQuery(
366
# Field with arguments
367
ds.Query.users(
368
limit=10,
369
offset=0,
370
orderBy="CREATED_AT"
371
).select(
372
ds.User.id,
373
ds.User.name,
374
# Field with alias
375
user_email=ds.User.email,
376
# Nested field with arguments
377
ds.User.posts(published=True).select(
378
ds.Post.title,
379
published_date=ds.Post.publishedAt
380
)
381
)
382
)
383
384
request = dsl_gql(query)
385
result = client.execute(request)
386
387
# Access aliased fields
388
for user in result["users"]:
389
print(f"{user['name']} - {user['user_email']}")
390
```
391
392
### Variables and Dynamic Queries
393
394
```python
395
from gql.dsl import DSLSchema, DSLQuery, DSLVariableDefinitions, dsl_gql
396
397
# Setup variables
398
vars = DSLVariableDefinitions()
399
400
# Build query with variables
401
query = DSLQuery(
402
ds.Query.user(id=vars.user_id).select(
403
ds.User.name,
404
ds.User.email,
405
ds.User.posts(limit=vars.post_limit).select(
406
ds.Post.title,
407
ds.Post.content
408
)
409
)
410
)
411
412
# Execute with different variable values
413
request = dsl_gql(query)
414
415
result1 = client.execute(request, variable_values={
416
"user_id": "123",
417
"post_limit": 5
418
})
419
420
result2 = client.execute(request, variable_values={
421
"user_id": "456",
422
"post_limit": 10
423
})
424
```
425
426
### Fragment Usage
427
428
```python
429
from gql.dsl import DSLSchema, DSLQuery, DSLFragment, dsl_gql
430
431
# Define reusable fragment
432
user_info = DSLFragment("UserInfo").on(ds.User).select(
433
ds.User.id,
434
ds.User.name,
435
ds.User.email,
436
ds.User.createdAt
437
)
438
439
# Use fragment in multiple places
440
query = DSLQuery(
441
# Use fragment in different contexts
442
current_user=ds.Query.currentUser.select(user_info),
443
user_by_id=ds.Query.user(id="123").select(user_info),
444
445
# Use fragment in nested selections
446
ds.Query.posts.select(
447
ds.Post.title,
448
ds.Post.author.select(user_info)
449
)
450
)
451
452
# Include fragment in request
453
request = dsl_gql(query, user_info)
454
result = client.execute(request)
455
```
456
457
### Inline Fragments for Union Types
458
459
```python
460
from gql.dsl import DSLSchema, DSLQuery, DSLInlineFragment, dsl_gql
461
462
# Handle union types with inline fragments
463
query = DSLQuery(
464
ds.Query.search(query="python").select(
465
# Common fields available on all union members
466
ds.SearchResult.id,
467
468
# Type-specific fields using inline fragments
469
DSLInlineFragment().on(ds.User).select(
470
ds.User.name,
471
ds.User.email
472
),
473
474
DSLInlineFragment().on(ds.Post).select(
475
ds.Post.title,
476
ds.Post.content
477
),
478
479
DSLInlineFragment().on(ds.Repository).select(
480
ds.Repository.name,
481
ds.Repository.description,
482
ds.Repository.starCount
483
)
484
)
485
)
486
487
request = dsl_gql(query)
488
result = client.execute(request)
489
490
# Results will include type-specific fields based on actual types
491
for item in result["search"]:
492
if "name" in item and "email" in item:
493
print(f"User: {item['name']} ({item['email']})")
494
elif "title" in item:
495
print(f"Post: {item['title']}")
496
elif "starCount" in item:
497
print(f"Repository: {item['name']} ({item['starCount']} stars)")
498
```
499
500
### Mutations with Input Objects
501
502
```python
503
from gql.dsl import DSLSchema, DSLMutation, dsl_gql
504
505
# Build mutation with input object
506
user_input = {
507
"name": "John Doe",
508
"email": "john@example.com",
509
"age": 30
510
}
511
512
mutation = DSLMutation(
513
ds.Mutation.createUser(input=user_input).select(
514
ds.User.id,
515
ds.User.name,
516
ds.User.email,
517
ds.User.createdAt
518
)
519
)
520
521
request = dsl_gql(mutation)
522
result = client.execute(request)
523
524
created_user = result["createUser"]
525
print(f"Created user {created_user['name']} with ID {created_user['id']}")
526
```
527
528
### Subscriptions with DSL
529
530
```python
531
import asyncio
532
from gql.dsl import DSLSchema, DSLSubscription, dsl_gql
533
534
async def handle_subscription():
535
# Build subscription
536
subscription = DSLSubscription(
537
ds.Subscription.messageAdded(channelId="general").select(
538
ds.Message.id,
539
ds.Message.content,
540
ds.Message.user.select(
541
ds.User.name,
542
ds.User.avatar
543
),
544
ds.Message.timestamp
545
)
546
)
547
548
request = dsl_gql(subscription)
549
550
async with client.connect_async() as session:
551
async for result in session.subscribe(request):
552
message = result["messageAdded"]
553
user = message["user"]
554
print(f"[{message['timestamp']}] {user['name']}: {message['content']}")
555
556
asyncio.run(handle_subscription())
557
```
558
559
### Meta-fields and Introspection
560
561
```python
562
from gql.dsl import DSLSchema, DSLQuery, DSLMetaField, dsl_gql
563
564
# Query with typename meta-field
565
query = DSLQuery(
566
ds.Query.search(query="graphql").select(
567
DSLMetaField("__typename"), # Include __typename for each result
568
ds.SearchResult.id,
569
570
# Conditional selection based on type
571
DSLInlineFragment().on(ds.User).select(
572
ds.User.name
573
),
574
575
DSLInlineFragment().on(ds.Post).select(
576
ds.Post.title
577
)
578
)
579
)
580
581
request = dsl_gql(query)
582
result = client.execute(request)
583
584
# Use __typename to handle different types
585
for item in result["search"]:
586
if item["__typename"] == "User":
587
print(f"User: {item['name']}")
588
elif item["__typename"] == "Post":
589
print(f"Post: {item['title']}")
590
```
591
592
### Complex Nested Queries
593
594
```python
595
from gql.dsl import DSLSchema, DSLQuery, DSLFragment, dsl_gql
596
597
# Define fragments for reusability
598
post_summary = DSLFragment("PostSummary").on(ds.Post).select(
599
ds.Post.id,
600
ds.Post.title,
601
ds.Post.publishedAt,
602
ds.Post.commentCount,
603
ds.Post.likeCount
604
)
605
606
user_profile = DSLFragment("UserProfile").on(ds.User).select(
607
ds.User.id,
608
ds.User.name,
609
ds.User.email,
610
ds.User.bio,
611
ds.User.followersCount,
612
ds.User.followingCount
613
)
614
615
# Build complex nested query
616
query = DSLQuery(
617
# Current user with profile info
618
ds.Query.currentUser.select(
619
user_profile,
620
621
# Recent posts with summary
622
ds.User.posts(limit=5, orderBy="PUBLISHED_AT_DESC").select(
623
post_summary,
624
625
# Comments on each post
626
ds.Post.comments(limit=3).select(
627
ds.Comment.id,
628
ds.Comment.content,
629
ds.Comment.author.select(user_profile)
630
)
631
),
632
633
# Following users
634
ds.User.following(limit=10).select(user_profile)
635
)
636
)
637
638
request = dsl_gql(query, post_summary, user_profile)
639
result = client.execute(request)
640
641
# Access nested data
642
current_user = result["currentUser"]
643
print(f"User: {current_user['name']}")
644
print(f"Recent posts: {len(current_user['posts'])}")
645
646
for post in current_user["posts"]:
647
print(f" - {post['title']} ({post['commentCount']} comments)")
648
for comment in post["comments"]:
649
print(f" * {comment['author']['name']}: {comment['content']}")
650
```
651
652
### Snake Case to Camel Case Conversion
653
654
```python
655
from gql.dsl import DSLSchema, DSLQuery, dsl_gql
656
657
# DSL automatically converts snake_case to camelCase
658
query = DSLQuery(
659
ds.Query.current_user.select( # Becomes currentUser
660
ds.User.first_name, # Becomes firstName
661
ds.User.last_name, # Becomes lastName
662
ds.User.created_at, # Becomes createdAt
663
ds.User.updated_at # Becomes updatedAt
664
)
665
)
666
667
# Generated GraphQL uses camelCase field names
668
request = dsl_gql(query)
669
print(request.payload["query"])
670
# Output includes: currentUser { firstName lastName createdAt updatedAt }
671
672
result = client.execute(request)
673
user = result["currentUser"]
674
print(f"{user['firstName']} {user['lastName']}")
675
```