|  | 
| 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) | 
0 commit comments