Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit288f39c

Browse files
nejchmax-wittig
authored andcommitted
feat(graphql): add async client
1 parent8046387 commit288f39c

File tree

9 files changed

+259
-36
lines changed

9 files changed

+259
-36
lines changed

‎README.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ python-gitlab
2525
..image::https://img.shields.io/github/license/python-gitlab/python-gitlab
2626
:target:https://github.com/python-gitlab/python-gitlab/blob/main/COPYING
2727

28-
``python-gitlab`` is a Python package providing access to the GitLabserver API.
28+
``python-gitlab`` is a Python package providing access to the GitLabAPIs.
2929

30-
It supports the v4 API of GitLab, and provides a CLI tool (``gitlab``).
30+
It includes a client for GitLab's v4 REST API, synchronous and asynchronous GraphQL API
31+
clients, as well as a CLI tool (``gitlab``) wrapping REST API endpoints.
3132

3233
.. _features:
3334

@@ -39,6 +40,7 @@ Features
3940
* write Pythonic code to manage your GitLab resources.
4041
* pass arbitrary parameters to the GitLab API. Simply follow GitLab's docs
4142
on what parameters are available.
43+
* use a synchronous or asynchronous client when using the GraphQL API.
4244
* access arbitrary endpoints as soon as they are available on GitLab, by using
4345
lower-level API methods.
4446
* use persistent requests sessions for authentication, proxy and certificate handling.

‎docs/api-usage-graphql.rst

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
Using the GraphQL API (beta)
33
############################
44

5-
python-gitlab provides basic support for executing GraphQL queries and mutations.
5+
python-gitlab provides basic support for executing GraphQL queries and mutations,
6+
providing both a synchronous and asynchronous client.
67

78
..danger::
89

@@ -13,10 +14,11 @@ python-gitlab provides basic support for executing GraphQL queries and mutations
1314
It is currently unstable and its implementation may change. You can expect a more
1415
mature client in one of the upcoming versions.
1516

16-
The ``gitlab.GraphQL``class
17-
==================================
17+
The ``gitlab.GraphQL``and ``gitlab.AsyncGraphQL`` classes
18+
==========================================================
1819

19-
As with the REST client, you connect to a GitLab instance by creating a ``gitlab.GraphQL`` object:
20+
As with the REST client, you connect to a GitLab instance by creating a ``gitlab.GraphQL``
21+
(for synchronous code) or ``gitlab.AsyncGraphQL`` instance (for asynchronous code):
2022

2123
..code-block::python
2224
@@ -34,6 +36,12 @@ As with the REST client, you connect to a GitLab instance by creating a ``gitlab
3436
# personal access token or OAuth2 token authentication (self-hosted GitLab instance)
3537
gq= gitlab.GraphQL('https://gitlab.example.com',token='glpat-JVNSESs8EwWRx5yDxM5q')
3638
39+
# or the async equivalents
40+
async_gq= gitlab.AsyncGraphQL()
41+
async_gq= gitlab.AsyncGraphQL('https://gitlab.example.com')
42+
async_gq= gitlab.AsyncGraphQL(token='glpat-JVNSESs8EwWRx5yDxM5q')
43+
async_gq= gitlab.AsyncGraphQL('https://gitlab.example.com',token='glpat-JVNSESs8EwWRx5yDxM5q')
44+
3745
Sending queries
3846
===============
3947

@@ -50,3 +58,17 @@ Get the result of a query:
5058
"""
5159
5260
result= gq.execute(query)
61+
62+
Get the result of a query using the async client:
63+
64+
..code-block::python
65+
66+
query="""{
67+
query {
68+
currentUser {
69+
name
70+
}
71+
}
72+
"""
73+
74+
result=await async_gq.execute(query)

‎gitlab/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
__title__,
2828
__version__,
2929
)
30-
fromgitlab.clientimportGitlab,GitlabList,GraphQL# noqa: F401
30+
fromgitlab.clientimportAsyncGraphQL,Gitlab,GitlabList,GraphQL# noqa: F401
3131
fromgitlab.exceptionsimport*# noqa: F401,F403
3232

3333
warnings.filterwarnings("default",category=DeprecationWarning,module="^gitlab")
@@ -42,6 +42,7 @@
4242
"__version__",
4343
"Gitlab",
4444
"GitlabList",
45+
"AsyncGraphQL",
4546
"GraphQL",
4647
]
4748
__all__.extend(gitlab.exceptions.__all__)

‎gitlab/_backends/graphql.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
fromtypingimportAny
22

33
importhttpx
4-
fromgql.transport.httpximportHTTPXTransport
4+
fromgql.transport.httpximportHTTPXAsyncTransport,HTTPXTransport
55

66

77
classGitlabTransport(HTTPXTransport):
@@ -22,3 +22,23 @@ def connect(self) -> None:
2222

2323
defclose(self)->None:
2424
pass
25+
26+
27+
classGitlabAsyncTransport(HTTPXAsyncTransport):
28+
"""An async gql httpx transport that reuses an existing httpx.AsyncClient.
29+
By default, gql's transports do not have a keep-alive session
30+
and do not enable providing your own session that's kept open.
31+
This transport lets us provide and close our session on our own
32+
and provide additional auth.
33+
For details, see https://github.com/graphql-python/gql/issues/91.
34+
"""
35+
36+
def__init__(self,*args:Any,client:httpx.AsyncClient,**kwargs:Any):
37+
super().__init__(*args,**kwargs)
38+
self.client=client
39+
40+
asyncdefconnect(self)->None:
41+
pass
42+
43+
asyncdefclose(self)->None:
44+
pass

‎gitlab/client.py

Lines changed: 120 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
importgraphql
3333
importhttpx
3434

35-
from ._backends.graphqlimportGitlabTransport
35+
from ._backends.graphqlimportGitlabAsyncTransport,GitlabTransport
3636

3737
_GQL_INSTALLED=True
3838
exceptImportError:# pragma: no cover
@@ -1278,14 +1278,13 @@ def next(self) -> Dict[str, Any]:
12781278
raiseStopIteration
12791279

12801280

1281-
classGraphQL:
1281+
class_BaseGraphQL:
12821282
def__init__(
12831283
self,
12841284
url:Optional[str]=None,
12851285
*,
12861286
token:Optional[str]=None,
12871287
ssl_verify:Union[bool,str]=True,
1288-
client:Optional[httpx.Client]=None,
12891288
timeout:Optional[float]=None,
12901289
user_agent:str=gitlab.const.USER_AGENT,
12911290
fetch_schema_from_transport:bool=False,
@@ -1308,9 +1307,50 @@ def __init__(
13081307
self._max_retries=max_retries
13091308
self._obey_rate_limit=obey_rate_limit
13101309
self._retry_transient_errors=retry_transient_errors
1310+
self._client_opts=self._get_client_opts()
1311+
self._fetch_schema_from_transport=fetch_schema_from_transport
1312+
1313+
def_get_client_opts(self)->Dict[str,Any]:
1314+
headers= {"User-Agent":self._user_agent}
1315+
1316+
ifself._token:
1317+
headers["Authorization"]=f"Bearer{self._token}"
1318+
1319+
return {
1320+
"headers":headers,
1321+
"timeout":self._timeout,
1322+
"verify":self._ssl_verify,
1323+
}
1324+
13111325

1312-
opts=self._get_client_opts()
1313-
self._http_client=clientorhttpx.Client(**opts)
1326+
classGraphQL(_BaseGraphQL):
1327+
def__init__(
1328+
self,
1329+
url:Optional[str]=None,
1330+
*,
1331+
token:Optional[str]=None,
1332+
ssl_verify:Union[bool,str]=True,
1333+
client:Optional[httpx.Client]=None,
1334+
timeout:Optional[float]=None,
1335+
user_agent:str=gitlab.const.USER_AGENT,
1336+
fetch_schema_from_transport:bool=False,
1337+
max_retries:int=10,
1338+
obey_rate_limit:bool=True,
1339+
retry_transient_errors:bool=False,
1340+
)->None:
1341+
super().__init__(
1342+
url=url,
1343+
token=token,
1344+
ssl_verify=ssl_verify,
1345+
timeout=timeout,
1346+
user_agent=user_agent,
1347+
fetch_schema_from_transport=fetch_schema_from_transport,
1348+
max_retries=max_retries,
1349+
obey_rate_limit=obey_rate_limit,
1350+
retry_transient_errors=retry_transient_errors,
1351+
)
1352+
1353+
self._http_client=clientorhttpx.Client(**self._client_opts)
13141354
self._transport=GitlabTransport(self._url,client=self._http_client)
13151355
self._client=gql.Client(
13161356
transport=self._transport,
@@ -1324,19 +1364,81 @@ def __enter__(self) -> "GraphQL":
13241364
def__exit__(self,*args:Any)->None:
13251365
self._http_client.close()
13261366

1327-
def_get_client_opts(self)->Dict[str,Any]:
1328-
headers= {"User-Agent":self._user_agent}
1367+
defexecute(
1368+
self,request:Union[str,graphql.Source],*args:Any,**kwargs:Any
1369+
)->Any:
1370+
parsed_document=self._gql(request)
1371+
retry=utils.Retry(
1372+
max_retries=self._max_retries,
1373+
obey_rate_limit=self._obey_rate_limit,
1374+
retry_transient_errors=self._retry_transient_errors,
1375+
)
13291376

1330-
ifself._token:
1331-
headers["Authorization"]=f"Bearer{self._token}"
1377+
whileTrue:
1378+
try:
1379+
result=self._client.execute(parsed_document,*args,**kwargs)
1380+
exceptgql.transport.exceptions.TransportServerErrorase:
1381+
ifretry.handle_retry_on_status(
1382+
status_code=e.code,headers=self._transport.response_headers
1383+
):
1384+
continue
13321385

1333-
return {
1334-
"headers":headers,
1335-
"timeout":self._timeout,
1336-
"verify":self._ssl_verify,
1337-
}
1386+
ife.code==401:
1387+
raisegitlab.exceptions.GitlabAuthenticationError(
1388+
response_code=e.code,
1389+
error_message=str(e),
1390+
)
13381391

1339-
defexecute(
1392+
raisegitlab.exceptions.GitlabHttpError(
1393+
response_code=e.code,
1394+
error_message=str(e),
1395+
)
1396+
1397+
returnresult
1398+
1399+
1400+
classAsyncGraphQL(_BaseGraphQL):
1401+
def__init__(
1402+
self,
1403+
url:Optional[str]=None,
1404+
*,
1405+
token:Optional[str]=None,
1406+
ssl_verify:Union[bool,str]=True,
1407+
client:Optional[httpx.AsyncClient]=None,
1408+
timeout:Optional[float]=None,
1409+
user_agent:str=gitlab.const.USER_AGENT,
1410+
fetch_schema_from_transport:bool=False,
1411+
max_retries:int=10,
1412+
obey_rate_limit:bool=True,
1413+
retry_transient_errors:bool=False,
1414+
)->None:
1415+
super().__init__(
1416+
url=url,
1417+
token=token,
1418+
ssl_verify=ssl_verify,
1419+
timeout=timeout,
1420+
user_agent=user_agent,
1421+
fetch_schema_from_transport=fetch_schema_from_transport,
1422+
max_retries=max_retries,
1423+
obey_rate_limit=obey_rate_limit,
1424+
retry_transient_errors=retry_transient_errors,
1425+
)
1426+
1427+
self._http_client=clientorhttpx.AsyncClient(**self._client_opts)
1428+
self._transport=GitlabAsyncTransport(self._url,client=self._http_client)
1429+
self._client=gql.Client(
1430+
transport=self._transport,
1431+
fetch_schema_from_transport=fetch_schema_from_transport,
1432+
)
1433+
self._gql=gql.gql
1434+
1435+
asyncdef__aenter__(self)->"AsyncGraphQL":
1436+
returnself
1437+
1438+
asyncdef__aexit__(self,*args:Any)->None:
1439+
awaitself._http_client.aclose()
1440+
1441+
asyncdefexecute(
13401442
self,request:Union[str,graphql.Source],*args:Any,**kwargs:Any
13411443
)->Any:
13421444
parsed_document=self._gql(request)
@@ -1348,7 +1450,9 @@ def execute(
13481450

13491451
whileTrue:
13501452
try:
1351-
result=self._client.execute(parsed_document,*args,**kwargs)
1453+
result=awaitself._client.execute_async(
1454+
parsed_document,*args,**kwargs
1455+
)
13521456
exceptgql.transport.exceptions.TransportServerErrorase:
13531457
ifretry.handle_retry_on_status(
13541458
status_code=e.code,headers=self._transport.response_headers

‎pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name ="python-gitlab"
7-
description="A python wrapper for the GitLabAPI"
7+
description="The python wrapper for the GitLabREST and GraphQL APIs."
88
readme ="README.rst"
99
authors = [
1010
{name ="Gauvain Pocentek",email="gauvain@pocentek.net"}

‎requirements-test.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
-r requirements.txt
2+
anyio==4.6.2.post1
23
build==1.2.2.post1
34
coverage==7.6.8
45
pytest-console-scripts==1.4.1
@@ -8,4 +9,5 @@ pytest==8.3.4
89
PyYaml==6.0.2
910
responses==0.25.3
1011
respx==0.21.1
12+
trio==0.27.0
1113
wheel==0.45.1

‎tests/functional/api/test_graphql.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
1-
importlogging
2-
31
importpytest
42

53
importgitlab
64

75

86
@pytest.fixture
97
defgl_gql(gitlab_url:str,gitlab_token:str)->gitlab.GraphQL:
10-
logging.info("Instantiating gitlab.GraphQL instance")
11-
instance=gitlab.GraphQL(gitlab_url,token=gitlab_token)
8+
returngitlab.GraphQL(gitlab_url,token=gitlab_token)
9+
1210

13-
returninstance
11+
@pytest.fixture
12+
defgl_async_gql(gitlab_url:str,gitlab_token:str)->gitlab.AsyncGraphQL:
13+
returngitlab.AsyncGraphQL(gitlab_url,token=gitlab_token)
1414

1515

1616
deftest_query_returns_valid_response(gl_gql:gitlab.GraphQL):
1717
query="query {currentUser {active}}"
1818

1919
response=gl_gql.execute(query)
2020
assertresponse["currentUser"]["active"]isTrue
21+
22+
23+
@pytest.mark.anyio
24+
asyncdeftest_async_query_returns_valid_response(gl_async_gql:gitlab.AsyncGraphQL):
25+
query="query {currentUser {active}}"
26+
27+
response=awaitgl_async_gql.execute(query)
28+
assertresponse["currentUser"]["active"]isTrue

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp