0
# GraphQL Clients
1
2
Advanced GraphQL query execution with both synchronous and asynchronous clients. Provides flexible querying capabilities and efficient data fetching for complex GitLab API interactions using GitLab's GraphQL API.
3
4
## Capabilities
5
6
### Synchronous GraphQL Client
7
8
```python { .api }
9
class GraphQL:
10
"""
11
Synchronous GraphQL client for GitLab API queries.
12
13
Provides GraphQL query execution with retry logic, error handling,
14
and response processing for GitLab's GraphQL API endpoint.
15
"""
16
17
def __init__(
18
self,
19
url: str,
20
auth: tuple[str, str] | None = None,
21
timeout: float | None = None,
22
retry_transient_errors: bool = False,
23
**kwargs
24
) -> None:
25
"""
26
Initialize GraphQL client.
27
28
Parameters:
29
- url: GitLab GraphQL endpoint URL
30
- auth: Authentication tuple (token_type, token)
31
- timeout: Request timeout in seconds
32
- retry_transient_errors: Retry on transient errors
33
"""
34
35
def execute(
36
self,
37
query: str,
38
variables: dict | None = None,
39
**kwargs
40
) -> dict:
41
"""
42
Execute GraphQL query.
43
44
Parameters:
45
- query: GraphQL query string
46
- variables: Query variables dictionary
47
48
Returns:
49
Dictionary with query results
50
51
Raises:
52
GitlabGraphQLError: If query execution fails
53
54
Example:
55
```python
56
query = '''
57
query getProject($path: ID!) {
58
project(fullPath: $path) {
59
id
60
name
61
description
62
webUrl
63
repository {
64
tree {
65
lastCommit {
66
sha
67
message
68
authoredDate
69
}
70
}
71
}
72
issues(state: OPENED) {
73
count
74
edges {
75
node {
76
id
77
title
78
state
79
createdAt
80
}
81
}
82
}
83
}
84
}
85
'''
86
87
variables = {"path": "group/project"}
88
result = client.execute(query, variables)
89
```
90
"""
91
```
92
93
### Asynchronous GraphQL Client
94
95
```python { .api }
96
class AsyncGraphQL:
97
"""
98
Asynchronous GraphQL client for GitLab API queries.
99
100
Provides async GraphQL query execution with the same capabilities
101
as the synchronous client but with non-blocking I/O operations.
102
"""
103
104
def __init__(
105
self,
106
url: str,
107
auth: tuple[str, str] | None = None,
108
timeout: float | None = None,
109
retry_transient_errors: bool = False,
110
**kwargs
111
) -> None:
112
"""
113
Initialize async GraphQL client.
114
115
Parameters:
116
- url: GitLab GraphQL endpoint URL
117
- auth: Authentication tuple (token_type, token)
118
- timeout: Request timeout in seconds
119
- retry_transient_errors: Retry on transient errors
120
"""
121
122
async def execute(
123
self,
124
query: str,
125
variables: dict | None = None,
126
**kwargs
127
) -> dict:
128
"""
129
Execute GraphQL query asynchronously.
130
131
Parameters:
132
- query: GraphQL query string
133
- variables: Query variables dictionary
134
135
Returns:
136
Dictionary with query results
137
138
Raises:
139
GitlabGraphQLError: If query execution fails
140
141
Example:
142
```python
143
import asyncio
144
145
async def main():
146
query = '''
147
query getUserProjects($userId: UserID!) {
148
user(id: $userId) {
149
id
150
name
151
username
152
projectMemberships {
153
edges {
154
node {
155
project {
156
id
157
name
158
visibility
159
lastActivityAt
160
}
161
accessLevel {
162
integerValue
163
}
164
}
165
}
166
}
167
}
168
}
169
'''
170
171
variables = {"userId": "gid://gitlab/User/123"}
172
result = await client.execute(query, variables)
173
174
asyncio.run(main())
175
```
176
"""
177
```
178
179
### Client Creation and Configuration
180
181
```python { .api }
182
# Create GraphQL clients from Gitlab instance
183
@property
184
def graphql(self) -> GraphQL:
185
"""
186
Get synchronous GraphQL client configured for this GitLab instance.
187
188
Returns:
189
Configured GraphQL client
190
191
Example:
192
```python
193
gl = gitlab.Gitlab("https://gitlab.example.com", private_token="token")
194
gql = gl.graphql
195
196
query = "{ currentUser { id name } }"
197
result = gql.execute(query)
198
```
199
"""
200
201
@property
202
def async_graphql(self) -> AsyncGraphQL:
203
"""
204
Get asynchronous GraphQL client configured for this GitLab instance.
205
206
Returns:
207
Configured AsyncGraphQL client
208
209
Example:
210
```python
211
gl = gitlab.Gitlab("https://gitlab.example.com", private_token="token")
212
async_gql = gl.async_graphql
213
214
async def get_user():
215
query = "{ currentUser { id name } }"
216
result = await async_gql.execute(query)
217
return result
218
```
219
"""
220
```
221
222
### GraphQL Schema and Common Queries
223
224
```python { .api }
225
# Common GraphQL query patterns for GitLab
226
227
# Project Information Query
228
PROJECT_QUERY = """
229
query getProject($fullPath: ID!) {
230
project(fullPath: $fullPath) {
231
id
232
name
233
path
234
fullPath
235
description
236
visibility
237
webUrl
238
sshUrlToRepo
239
httpUrlToRepo
240
defaultBranch
241
repository {
242
exists
243
empty
244
rootRef
245
}
246
statistics {
247
commitCount
248
storageSize
249
repositorySize
250
wikiSize
251
lfsObjectsSize
252
jobArtifactsSize
253
packagesSize
254
snippetsSize
255
}
256
lastActivityAt
257
createdAt
258
updatedAt
259
}
260
}
261
"""
262
263
# Issues Query
264
ISSUES_QUERY = """
265
query getProjectIssues($fullPath: ID!, $state: IssuableState, $first: Int) {
266
project(fullPath: $fullPath) {
267
issues(state: $state, first: $first) {
268
count
269
pageInfo {
270
hasNextPage
271
hasPreviousPage
272
startCursor
273
endCursor
274
}
275
edges {
276
node {
277
id
278
iid
279
title
280
description
281
state
282
createdAt
283
updatedAt
284
closedAt
285
author {
286
id
287
name
288
username
289
avatarUrl
290
}
291
assignees {
292
edges {
293
node {
294
id
295
name
296
username
297
}
298
}
299
}
300
labels {
301
edges {
302
node {
303
id
304
title
305
color
306
description
307
}
308
}
309
}
310
milestone {
311
id
312
title
313
state
314
dueDate
315
}
316
webUrl
317
upvotes
318
downvotes
319
userNotesCount
320
confidential
321
discussionLocked
322
dueDate
323
timeEstimate
324
totalTimeSpent
325
humanTimeEstimate
326
humanTotalTimeSpent
327
}
328
}
329
}
330
}
331
}
332
"""
333
334
# Merge Requests Query
335
MERGE_REQUESTS_QUERY = """
336
query getProjectMergeRequests($fullPath: ID!, $state: MergeRequestState, $first: Int) {
337
project(fullPath: $fullPath) {
338
mergeRequests(state: $state, first: $first) {
339
count
340
pageInfo {
341
hasNextPage
342
hasPreviousPage
343
startCursor
344
endCursor
345
}
346
edges {
347
node {
348
id
349
iid
350
title
351
description
352
state
353
createdAt
354
updatedAt
355
mergedAt
356
closedAt
357
sourceBranch
358
targetBranch
359
author {
360
id
361
name
362
username
363
avatarUrl
364
}
365
assignees {
366
edges {
367
node {
368
id
369
name
370
username
371
}
372
}
373
}
374
reviewers {
375
edges {
376
node {
377
id
378
name
379
username
380
}
381
}
382
}
383
labels {
384
edges {
385
node {
386
id
387
title
388
color
389
}
390
}
391
}
392
milestone {
393
id
394
title
395
state
396
}
397
webUrl
398
upvotes
399
downvotes
400
userNotesCount
401
shouldRemoveSourceBranch
402
forceRemoveSourceBranch
403
squash
404
mergeable
405
mergeStatus
406
draft
407
workInProgress
408
conflicts
409
divergedCommitsCount
410
rebaseInProgress
411
defaultMergeCommitMessage
412
mergeCommitSha
413
squashCommitSha
414
diffHeadSha
415
diffBaseSha
416
reference
417
taskCompletionStatus {
418
count
419
completedCount
420
}
421
}
422
}
423
}
424
}
425
}
426
"""
427
428
# Users Query
429
USERS_QUERY = """
430
query getUsers($search: String, $first: Int) {
431
users(search: $search, first: $first) {
432
count
433
pageInfo {
434
hasNextPage
435
hasPreviousPage
436
startCursor
437
endCursor
438
}
439
edges {
440
node {
441
id
442
name
443
username
444
445
avatarUrl
446
webUrl
447
bot
448
state
449
createdAt
450
lastActivityOn
451
location
452
publicEmail
453
skype
454
455
456
websiteUrl
457
organization
458
jobTitle
459
bio
460
workInformation
461
localTime
462
pronouns
463
bot
464
followers {
465
count
466
}
467
following {
468
count
469
}
470
groupMemberships {
471
count
472
}
473
projectMemberships {
474
count
475
}
476
starredProjects {
477
count
478
}
479
status {
480
availability
481
emoji
482
message
483
}
484
}
485
}
486
}
487
}
488
"""
489
490
# Groups Query
491
GROUPS_QUERY = """
492
query getGroups($search: String, $first: Int) {
493
groups(search: $search, first: $first) {
494
count
495
pageInfo {
496
hasNextPage
497
hasPreviousPage
498
startCursor
499
endCursor
500
}
501
edges {
502
node {
503
id
504
name
505
path
506
fullName
507
fullPath
508
description
509
visibility
510
webUrl
511
avatarUrl
512
parent {
513
id
514
name
515
fullPath
516
}
517
projects {
518
count
519
}
520
descendantGroups {
521
count
522
}
523
createdAt
524
requestAccessEnabled
525
requireTwoFactorAuthentication
526
twoFactorGracePeriod
527
autoDevopsEnabled
528
emailsDisabled
529
mentionsDisabled
530
lfsEnabled
531
shareWithGroupLock
532
projectCreationLevel
533
subgroupCreationLevel
534
defaultBranchProtection
535
repositoryStorageLimit
536
}
537
}
538
}
539
}
540
"""
541
```
542
543
### Usage Examples
544
545
```python
546
import gitlab
547
import asyncio
548
549
# Initialize GitLab client
550
gl = gitlab.Gitlab("https://gitlab.example.com", private_token="your-token")
551
552
# Synchronous GraphQL usage
553
def sync_graphql_example():
554
gql = gl.graphql
555
556
# Simple current user query
557
user_query = """
558
{
559
currentUser {
560
id
561
name
562
username
563
564
avatarUrl
565
}
566
}
567
"""
568
569
result = gql.execute(user_query)
570
user = result['data']['currentUser']
571
print(f"Current user: {user['name']} ({user['username']})")
572
573
# Project query with variables
574
project_query = """
575
query getProject($path: ID!) {
576
project(fullPath: $path) {
577
id
578
name
579
description
580
visibility
581
webUrl
582
issues(state: OPENED, first: 5) {
583
count
584
edges {
585
node {
586
id
587
title
588
author {
589
name
590
}
591
createdAt
592
}
593
}
594
}
595
mergeRequests(state: OPENED, first: 5) {
596
count
597
edges {
598
node {
599
id
600
title
601
author {
602
name
603
}
604
sourceBranch
605
targetBranch
606
}
607
}
608
}
609
}
610
}
611
"""
612
613
variables = {"path": "group/project-name"}
614
result = gql.execute(project_query, variables)
615
616
project = result['data']['project']
617
print(f"Project: {project['name']}")
618
print(f"Open issues: {project['issues']['count']}")
619
print(f"Open MRs: {project['mergeRequests']['count']}")
620
621
# Asynchronous GraphQL usage
622
async def async_graphql_example():
623
async_gql = gl.async_graphql
624
625
# Batch multiple queries
626
queries = [
627
{
628
"query": """
629
query getProject($path: ID!) {
630
project(fullPath: $path) {
631
id
632
name
633
statistics {
634
commitCount
635
storageSize
636
}
637
}
638
}
639
""",
640
"variables": {"path": "group/project1"}
641
},
642
{
643
"query": """
644
query getProject($path: ID!) {
645
project(fullPath: $path) {
646
id
647
name
648
statistics {
649
commitCount
650
storageSize
651
}
652
}
653
}
654
""",
655
"variables": {"path": "group/project2"}
656
}
657
]
658
659
# Execute queries concurrently
660
tasks = [
661
async_gql.execute(q["query"], q["variables"])
662
for q in queries
663
]
664
665
results = await asyncio.gather(*tasks)
666
667
for result in results:
668
project = result['data']['project']
669
stats = project['statistics']
670
print(f"{project['name']}: {stats['commitCount']} commits, {stats['storageSize']} bytes")
671
672
# Complex pagination example
673
def paginated_graphql_query():
674
gql = gl.graphql
675
676
query = """
677
query getProjectIssues($path: ID!, $after: String) {
678
project(fullPath: $path) {
679
issues(first: 50, after: $after) {
680
pageInfo {
681
hasNextPage
682
endCursor
683
}
684
edges {
685
node {
686
id
687
title
688
state
689
createdAt
690
author {
691
name
692
}
693
}
694
}
695
}
696
}
697
}
698
"""
699
700
all_issues = []
701
variables = {"path": "group/project"}
702
has_next_page = True
703
704
while has_next_page:
705
result = gql.execute(query, variables)
706
issues_data = result['data']['project']['issues']
707
708
# Collect issues
709
issues = [edge['node'] for edge in issues_data['edges']]
710
all_issues.extend(issues)
711
712
# Check for next page
713
page_info = issues_data['pageInfo']
714
has_next_page = page_info['hasNextPage']
715
716
if has_next_page:
717
variables['after'] = page_info['endCursor']
718
719
print(f"Retrieved {len(all_issues)} issues total")
720
return all_issues
721
722
# Mutation example (create issue)
723
def create_issue_mutation():
724
gql = gl.graphql
725
726
mutation = """
727
mutation createIssue($input: CreateIssueInput!) {
728
createIssue(input: $input) {
729
issue {
730
id
731
iid
732
title
733
description
734
state
735
webUrl
736
}
737
errors
738
}
739
}
740
"""
741
742
variables = {
743
"input": {
744
"projectPath": "group/project",
745
"title": "New issue via GraphQL",
746
"description": "This issue was created using GraphQL mutation",
747
"confidential": False
748
}
749
}
750
751
result = gql.execute(mutation, variables)
752
753
if result['data']['createIssue']['errors']:
754
print(f"Errors: {result['data']['createIssue']['errors']}")
755
else:
756
issue = result['data']['createIssue']['issue']
757
print(f"Created issue #{issue['iid']}: {issue['title']}")
758
print(f"URL: {issue['webUrl']}")
759
760
# Run examples
761
sync_graphql_example()
762
asyncio.run(async_graphql_example())
763
paginated_issues = paginated_graphql_query()
764
create_issue_mutation()
765
```
766
767
### Error Handling
768
769
```python { .api }
770
class GitlabGraphQLError(GitlabError):
771
"""Raised when GraphQL query execution fails."""
772
pass
773
```
774
775
Example error handling:
776
```python
777
try:
778
result = gql.execute(query, variables)
779
780
# Check for GraphQL errors in response
781
if 'errors' in result:
782
for error in result['errors']:
783
print(f"GraphQL Error: {error['message']}")
784
if 'locations' in error:
785
print(f"Location: {error['locations']}")
786
else:
787
# Process successful result
788
data = result['data']
789
790
except gitlab.GitlabGraphQLError as e:
791
print(f"GraphQL execution failed: {e}")
792
except gitlab.GitlabAuthenticationError:
793
print("Authentication failed - check your token")
794
except gitlab.GitlabConnectionError:
795
print("Connection failed - check your URL")
796
```
797
798
### GraphQL vs REST API
799
800
GraphQL provides several advantages over the REST API:
801
802
- **Single Request**: Fetch related data in one query instead of multiple REST calls
803
- **Flexible Data Selection**: Request only the fields you need
804
- **Strong Type System**: Built-in validation and introspection
805
- **Real-time Subscriptions**: Subscribe to data changes (GitLab Premium feature)
806
- **Efficient Pagination**: Cursor-based pagination with connection patterns
807
- **Reduced Over-fetching**: Get exactly the data you need
808
809
Use GraphQL when:
810
- You need data from multiple related resources
811
- You want to minimize network requests
812
- You need complex filtering or nested data
813
- You're building data-heavy applications
814
- You want strongly-typed API interactions
815
816
Use REST API when:
817
- You need CRUD operations on single resources
818
- You're performing administrative actions
819
- You need features not yet available in GraphQL
820
- You're working with legacy systems expecting REST endpoints