| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d44ddd2 commit d6b1b0a
14 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,52 @@ | |||
| 1 | + ############################ | ||
| 2 | + Using the GraphQL API (beta) | ||
| 3 | + ############################ | ||
| 4 | + | ||
| 5 | + python-gitlab provides basic support for executing GraphQL queries and mutations. | ||
| 6 | + | ||
| 7 | + .. danger:: | ||
| 8 | + | ||
| 9 | + The GraphQL client is experimental and only provides basic support. | ||
| 10 | + It does not currently support pagination, obey rate limits, | ||
| 11 | + or attempt complex retries. You can use it to build simple queries and mutations. | ||
| 12 | + | ||
| 13 | + It is currently unstable and its implementation may change. You can expect a more | ||
| 14 | + mature client in one of the upcoming versions. | ||
| 15 | + | ||
| 16 | + The ``gitlab.GraphQL`` class | ||
| 17 | + ================================== | ||
| 18 | + | ||
| 19 | + As with the REST client, you connect to a GitLab instance by creating a ``gitlab.GraphQL`` object: | ||
| 20 | + | ||
| 21 | + .. code-block:: python | ||
| 22 | + | ||
| 23 | + import gitlab | ||
| 24 | + | ||
| 25 | + # anonymous read-only access for public resources (GitLab.com) | ||
| 26 | + gq = gitlab.GraphQL() | ||
| 27 | + | ||
| 28 | + # anonymous read-only access for public resources (self-hosted GitLab instance) | ||
| 29 | + gq = gitlab.GraphQL('https://gitlab.example.com') | ||
| 30 | + | ||
| 31 | + # personal access token or OAuth2 token authentication (GitLab.com) | ||
| 32 | + gq = gitlab.GraphQL(token='glpat-JVNSESs8EwWRx5yDxM5q') | ||
| 33 | + | ||
| 34 | + # personal access token or OAuth2 token authentication (self-hosted GitLab instance) | ||
| 35 | + gq = gitlab.GraphQL('https://gitlab.example.com', token='glpat-JVNSESs8EwWRx5yDxM5q') | ||
| 36 | + | ||
| 37 | + Sending queries | ||
| 38 | + =============== | ||
| 39 | + | ||
| 40 | + Get the result of a query: | ||
| 41 | + | ||
| 42 | + .. code-block:: python | ||
| 43 | + | ||
| 44 | + query = """{ | ||
| 45 | + query { | ||
| 46 | + currentUser { | ||
| 47 | + name | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | + """ | ||
| 51 | + | ||
| 52 | + result = gq.execute(query) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,8 +1,8 @@ | |||
| 1 | - ############################ | ||
| 2 | - Getting started with the API | ||
| 3 | - ############################ | ||
| 1 | + ################## | ||
| 2 | + Using the REST API | ||
| 3 | + ################## | ||
| 4 | 4 | ||
| 5 | - python-gitlab only supports GitLab API v4. | ||
| 5 | + python-gitlab currently only supports v4 of the GitLab REST API. | ||
| 6 | 6 | ||
| 7 | 7 | ``gitlab.Gitlab`` class | |
| 8 | 8 | ======================= | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,6 @@ | |||
| 1 | - ############################ | ||
| 2 | - Getting started with the CLI | ||
| 3 | - ############################ | ||
| 1 | + ############# | ||
| 2 | + Using the CLI | ||
| 3 | + ############# | ||
| 4 | 4 | ||
| 5 | 5 | ``python-gitlab`` provides a :command:`gitlab` command-line tool to interact | |
| 6 | 6 | with GitLab servers. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,6 +7,7 @@ | |||
| 7 | 7 | cli-usage | |
| 8 | 8 | api-usage | |
| 9 | 9 | api-usage-advanced | |
| 10 | + api-usage-graphql | ||
| 10 | 11 | cli-examples | |
| 11 | 12 | api-objects | |
| 12 | 13 | api/gitlab | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -27,7 +27,7 @@ | |||
| 27 | 27 | __title__, | |
| 28 | 28 | __version__, | |
| 29 | 29 | ) | |
| 30 | - from gitlab.client import Gitlab, GitlabList # noqa: F401 | ||
| 30 | + from gitlab.client import Gitlab, GitlabList, GraphQL # noqa: F401 | ||
| 31 | 31 | from gitlab.exceptions import * # noqa: F401,F403 | |
| 32 | 32 | ||
| 33 | 33 | warnings.filterwarnings("default", category=DeprecationWarning, module="^gitlab") | |
@@ -42,5 +42,6 @@ | |||
| 42 | 42 | "__version__", | |
| 43 | 43 | "Gitlab", | |
| 44 | 44 | "GitlabList", | |
| 45 | + "GraphQL", | ||
| 45 | 46 | ] | |
| 46 | 47 | __all__.extend(gitlab.exceptions.__all__) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,24 @@ | |||
| 1 | + from typing import Any | ||
| 2 | + | ||
| 3 | + import httpx | ||
| 4 | + from gql.transport.httpx import HTTPXTransport | ||
| 5 | + | ||
| 6 | + | ||
| 7 | + class GitlabTransport(HTTPXTransport): | ||
| 8 | + """A gql httpx transport that reuses an existing httpx.Client. | ||
| 9 | + By default, gql's transports do not have a keep-alive session | ||
| 10 | + and do not enable providing your own session that's kept open. | ||
| 11 | + This transport lets us provide and close our session on our own | ||
| 12 | + and provide additional auth. | ||
| 13 | + For details, see https://github.com/graphql-python/gql/issues/91. | ||
| 14 | + """ | ||
| 15 | + | ||
| 16 | + def __init__(self, *args: Any, client: httpx.Client, **kwargs: Any): | ||
| 17 | + super().__init__(*args, **kwargs) | ||
| 18 | + self.client = client | ||
| 19 | + | ||
| 20 | + def connect(self) -> None: | ||
| 21 | + pass | ||
| 22 | + | ||
| 23 | + def close(self) -> None: | ||
| 24 | + pass | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,6 +25,18 @@ | |||
| 25 | 25 | import gitlab.exceptions | |
| 26 | 26 | from gitlab import _backends, utils | |
| 27 | 27 | ||
| 28 | + try: | ||
| 29 | + import gql | ||
| 30 | + import graphql | ||
| 31 | + import httpx | ||
| 32 | + | ||
| 33 | + from ._backends.graphql import GitlabTransport | ||
| 34 | + | ||
| 35 | + _GQL_INSTALLED = True | ||
| 36 | + except ImportError: # pragma: no cover | ||
| 37 | + _GQL_INSTALLED = False | ||
| 38 | + | ||
| 39 | + | ||
| 28 | 40 | REDIRECT_MSG = ( | |
| 29 | 41 | "python-gitlab detected a {status_code} ({reason!r}) redirection. You must update " | |
| 30 | 42 | "your GitLab URL to the correct URL to avoid issues. The redirection was from: " | |
@@ -89,7 +101,7 @@ def __init__( | |||
| 89 | 101 | self._api_version = str(api_version) | |
| 90 | 102 | self._server_version: Optional[str] = None | |
| 91 | 103 | self._server_revision: Optional[str] = None | |
| 92 | - self._base_url = self._get_base_url(url) | ||
| 104 | + self._base_url = utils.get_base_url(url) | ||
| 93 | 105 | self._url = f"{self._base_url}/api/v{api_version}" | |
| 94 | 106 | #: Timeout to use for requests to gitlab server | |
| 95 | 107 | self.timeout = timeout | |
@@ -557,18 +569,6 @@ def _get_session_opts(self) -> Dict[str, Any]: | |||
| 557 | 569 | "verify": self.ssl_verify, | |
| 558 | 570 | } | |
| 559 | 571 | ||
| 560 | - @staticmethod | ||
| 561 | - def _get_base_url(url: Optional[str] = None) -> str: | ||
| 562 | - """Return the base URL with the trailing slash stripped. | ||
| 563 | - If the URL is a Falsy value, return the default URL. | ||
| 564 | - Returns: | ||
| 565 | - The base URL | ||
| 566 | - """ | ||
| 567 | - if not url: | ||
| 568 | - return gitlab.const.DEFAULT_URL | ||
| 569 | - | ||
| 570 | - return url.rstrip("/") | ||
| 571 | - | ||
| 572 | 572 | def _build_url(self, path: str) -> str: | |
| 573 | 573 | """Returns the full url from path. | |
| 574 | 574 | ||
@@ -1296,3 +1296,62 @@ def next(self) -> Dict[str, Any]: | |||
| 1296 | 1296 | return self.next() | |
| 1297 | 1297 | ||
| 1298 | 1298 | raise StopIteration | |
| 1299 | + | ||
| 1300 | + | ||
| 1301 | + class GraphQL: | ||
| 1302 | + def __init__( | ||
| 1303 | + self, | ||
| 1304 | + url: Optional[str] = None, | ||
| 1305 | + *, | ||
| 1306 | + token: Optional[str] = None, | ||
| 1307 | + ssl_verify: Union[bool, str] = True, | ||
| 1308 | + client: Optional[httpx.Client] = None, | ||
| 1309 | + timeout: Optional[float] = None, | ||
| 1310 | + user_agent: str = gitlab.const.USER_AGENT, | ||
| 1311 | + fetch_schema_from_transport: bool = False, | ||
| 1312 | + ) -> None: | ||
| 1313 | + if not _GQL_INSTALLED: | ||
| 1314 | + raise ImportError( | ||
| 1315 | + "The GraphQL client could not be initialized because " | ||
| 1316 | + "the gql dependencies are not installed. " | ||
| 1317 | + "Install them with 'pip install python-gitlab[graphql]'" | ||
| 1318 | + ) | ||
| 1319 | + self._base_url = utils.get_base_url(url) | ||
| 1320 | + self._timeout = timeout | ||
| 1321 | + self._token = token | ||
| 1322 | + self._url = f"{self._base_url}/api/graphql" | ||
| 1323 | + self._user_agent = user_agent | ||
| 1324 | + self._ssl_verify = ssl_verify | ||
| 1325 | + | ||
| 1326 | + opts = self._get_client_opts() | ||
| 1327 | + self._http_client = client or httpx.Client(**opts) | ||
| 1328 | + self._transport = GitlabTransport(self._url, client=self._http_client) | ||
| 1329 | + self._client = gql.Client( | ||
| 1330 | + transport=self._transport, | ||
| 1331 | + fetch_schema_from_transport=fetch_schema_from_transport, | ||
| 1332 | + ) | ||
| 1333 | + self._gql = gql.gql | ||
| 1334 | + | ||
| 1335 | + def __enter__(self) -> "GraphQL": | ||
| 1336 | + return self | ||
| 1337 | + | ||
| 1338 | + def __exit__(self, *args: Any) -> None: | ||
| 1339 | + self._http_client.close() | ||
| 1340 | + | ||
| 1341 | + def _get_client_opts(self) -> Dict[str, Any]: | ||
| 1342 | + headers = {"User-Agent": self._user_agent} | ||
| 1343 | + | ||
| 1344 | + if self._token: | ||
| 1345 | + headers["Authorization"] = f"Bearer {self._token}" | ||
| 1346 | + | ||
| 1347 | + return { | ||
| 1348 | + "headers": headers, | ||
| 1349 | + "timeout": self._timeout, | ||
| 1350 | + "verify": self._ssl_verify, | ||
| 1351 | + } | ||
| 1352 | + | ||
| 1353 | + def execute( | ||
| 1354 | + self, request: Union[str, graphql.Source], *args: Any, **kwargs: Any | ||
| 1355 | + ) -> Any: | ||
| 1356 | + parsed_document = self._gql(request) | ||
| 1357 | + return self._client.execute(parsed_document, *args, **kwargs) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,14 +9,26 @@ | |||
| 9 | 9 | ||
| 10 | 10 | import requests | |
| 11 | 11 | ||
| 12 | - from gitlab import types | ||
| 12 | + from gitlab import const, types | ||
| 13 | 13 | ||
| 14 | 14 | ||
| 15 | 15 | class _StdoutStream: | |
| 16 | 16 | def __call__(self, chunk: Any) -> None: | |
| 17 | 17 | print(chunk) | |
| 18 | 18 | ||
| 19 | 19 | ||
| 20 | + def get_base_url(url: Optional[str] = None) -> str: | ||
| 21 | + """Return the base URL with the trailing slash stripped. | ||
| 22 | + If the URL is a Falsy value, return the default URL. | ||
| 23 | + Returns: | ||
| 24 | + The base URL | ||
| 25 | + """ | ||
| 26 | + if not url: | ||
| 27 | + return const.DEFAULT_URL | ||
| 28 | + | ||
| 29 | + return url.rstrip("/") | ||
| 30 | + | ||
| 31 | + | ||
| 20 | 32 | def get_content_type(content_type: Optional[str]) -> str: | |
| 21 | 33 | message = email.message.Message() | |
| 22 | 34 | if content_type is not None: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -43,6 +43,7 @@ dynamic = ["version"] | |||
| 43 | 43 | [project.optional-dependencies] | |
| 44 | 44 | autocompletion = ["argcomplete>=1.10.0,<3"] | |
| 45 | 45 | yaml = ["PyYaml>=6.0.1"] | |
| 46 | + graphql = ["gql[httpx]>=3.5.0,<4"] | ||
| 46 | 47 | ||
| 47 | 48 | [project.scripts] | |
| 48 | 49 | gitlab = "gitlab.cli:main" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,2 +1,4 @@ | |||
| 1 | + gql==3.5.0 | ||
| 2 | + httpx==0.27.0 | ||
| 1 | 3 | requests==2.32.3 | |
| 2 | 4 | requests-toolbelt==1.0.0 | |
| Back | FazBrowse Home | New Git URL |
0 commit comments