/ github / MainClass.py
MainClass.py
  1  ############################ Copyrights and license ############################
  2  #                                                                              #
  3  # Copyright 2013 AKFish <akfish@gmail.com>                                     #
  4  # Copyright 2013 Ed Jackson <ed.jackson@gmail.com>                             #
  5  # Copyright 2013 Jonathan J Hunt <hunt@braincorporation.com>                   #
  6  # Copyright 2013 Peter Golm <golm.peter@gmail.com>                             #
  7  # Copyright 2013 Steve Brown <steve@evolvedlight.co.uk>                        #
  8  # Copyright 2013 Vincent Jacques <vincent@vincent-jacques.net>                 #
  9  # Copyright 2014 C. R. Oldham <cro@ncbt.org>                                   #
 10  # Copyright 2014 Thialfihar <thi@thialfihar.org>                               #
 11  # Copyright 2014 Tyler Treat <ttreat31@gmail.com>                              #
 12  # Copyright 2014 Vincent Jacques <vincent@vincent-jacques.net>                 #
 13  # Copyright 2015 Daniel Pocock <daniel@pocock.pro>                             #
 14  # Copyright 2015 Joseph Rawson <joseph.rawson.works@littledebian.org>          #
 15  # Copyright 2015 Uriel Corfa <uriel@corfa.fr>                                  #
 16  # Copyright 2015 edhollandAL <eholland@alertlogic.com>                         #
 17  # Copyright 2016 Jannis Gebauer <ja.geb@me.com>                                #
 18  # Copyright 2016 Peter Buckley <dx-pbuckley@users.noreply.github.com>          #
 19  # Copyright 2017 Colin Hoglund <colinhoglund@users.noreply.github.com>         #
 20  # Copyright 2017 Jannis Gebauer <ja.geb@me.com>                                #
 21  # Copyright 2018 Agor Maxime <maxime.agor23@gmail.com>                         #
 22  # Copyright 2018 Joshua Hoblitt <josh@hoblitt.com>                             #
 23  # Copyright 2018 Maarten Fonville <mfonville@users.noreply.github.com>         #
 24  # Copyright 2018 Mike Miller <github@mikeage.net>                              #
 25  # Copyright 2018 Svend Sorensen <svend@svends.net>                             #
 26  # Copyright 2018 Wan Liuyang <tsfdye@gmail.com>                                #
 27  # Copyright 2018 sfdye <tsfdye@gmail.com>                                      #
 28  # Copyright 2018 itsbruce <it.is.bruce@gmail.com>                              #
 29  # Copyright 2019 Tomas Tomecek <tomas@tomecek.net>                             #
 30  # Copyright 2019 Rigas Papathanasopoulos <rigaspapas@gmail.com>                #
 31  # Copyright 2023 Yugo Hino <henom06@gmail.com>                                 #
 32  #                                                                              #
 33  # This file is part of PyGithub.                                               #
 34  # http://pygithub.readthedocs.io/                                              #
 35  #                                                                              #
 36  # PyGithub is free software: you can redistribute it and/or modify it under    #
 37  # the terms of the GNU Lesser General Public License as published by the Free  #
 38  # Software Foundation, either version 3 of the License, or (at your option)    #
 39  # any later version.                                                           #
 40  #                                                                              #
 41  # PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY  #
 42  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    #
 43  # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more #
 44  # details.                                                                     #
 45  #                                                                              #
 46  # You should have received a copy of the GNU Lesser General Public License     #
 47  # along with PyGithub. If not, see <http://www.gnu.org/licenses/>.             #
 48  #                                                                              #
 49  ################################################################################
 50  from __future__ import annotations
 51  
 52  import pickle
 53  import warnings
 54  from datetime import datetime
 55  from typing import TYPE_CHECKING, Any, BinaryIO, TypeVar
 56  
 57  import urllib3
 58  from urllib3.util import Retry
 59  
 60  import github.ApplicationOAuth
 61  import github.Auth
 62  import github.AuthenticatedUser
 63  import github.Enterprise
 64  import github.Event
 65  import github.Gist
 66  import github.GithubApp
 67  import github.GithubIntegration
 68  import github.GithubRetry
 69  import github.GitignoreTemplate
 70  import github.License
 71  import github.NamedUser
 72  import github.Topic
 73  from github import Consts
 74  from github.GithubIntegration import GithubIntegration
 75  from github.GithubObject import GithubObject, NotSet, Opt, is_defined
 76  from github.GithubRetry import GithubRetry
 77  from github.HookDelivery import HookDelivery, HookDeliverySummary
 78  from github.HookDescription import HookDescription
 79  from github.PaginatedList import PaginatedList
 80  from github.RateLimit import RateLimit
 81  from github.Requester import Requester
 82  
 83  if TYPE_CHECKING:
 84      from github.AppAuthentication import AppAuthentication
 85      from github.ApplicationOAuth import ApplicationOAuth
 86      from github.AuthenticatedUser import AuthenticatedUser
 87      from github.Commit import Commit
 88      from github.ContentFile import ContentFile
 89      from github.Event import Event
 90      from github.Gist import Gist
 91      from github.GithubApp import GithubApp
 92      from github.GitignoreTemplate import GitignoreTemplate
 93      from github.Issue import Issue
 94      from github.License import License
 95      from github.NamedUser import NamedUser
 96      from github.Organization import Organization
 97      from github.Project import Project
 98      from github.ProjectColumn import ProjectColumn
 99      from github.Repository import Repository
100      from github.Topic import Topic
101  
102  TGithubObject = TypeVar("TGithubObject", bound=GithubObject)
103  
104  
105  class Github:
106      """
107      This is the main class you instantiate to access the Github API v3. Optional parameters allow different authentication methods.
108      """
109  
110      __requester: Requester
111  
112      default_retry = GithubRetry()
113  
114      # keep non-deprecated arguments in-sync with Requester
115      # v3: remove login_or_token, password, jwt and app_auth
116      # v3: move auth to the front of arguments
117      # v3: add * before first argument so all arguments must be named,
118      #     allows to reorder / add new arguments / remove deprecated arguments without breaking user code
119      def __init__(
120          self,
121          login_or_token: str | None = None,
122          password: str | None = None,
123          jwt: str | None = None,
124          app_auth: AppAuthentication | None = None,
125          base_url: str = Consts.DEFAULT_BASE_URL,
126          timeout: int = Consts.DEFAULT_TIMEOUT,
127          user_agent: str = Consts.DEFAULT_USER_AGENT,
128          per_page: int = Consts.DEFAULT_PER_PAGE,
129          verify: bool | str = True,
130          retry: int | Retry | None = default_retry,
131          pool_size: int | None = None,
132          seconds_between_requests: float | None = Consts.DEFAULT_SECONDS_BETWEEN_REQUESTS,
133          seconds_between_writes: float | None = Consts.DEFAULT_SECONDS_BETWEEN_WRITES,
134          auth: github.Auth.Auth | None = None,
135      ) -> None:
136          """
137          :param login_or_token: string deprecated, use auth=github.Auth.Login(...) or auth=github.Auth.Token(...) instead
138          :param password: string deprecated, use auth=github.Auth.Login(...) instead
139          :param jwt: string deprecated, use auth=github.Auth.AppAuth(...) or auth=github.Auth.AppAuthToken(...) instead
140          :param app_auth: github.AppAuthentication deprecated, use auth=github.Auth.AppInstallationAuth(...) instead
141          :param base_url: string
142          :param timeout: integer
143          :param user_agent: string
144          :param per_page: int
145          :param verify: boolean or string
146          :param retry: int or urllib3.util.retry.Retry object,
147                        defaults to github.Github.default_retry,
148                        set to None to disable retries
149          :param pool_size: int
150          :param seconds_between_requests: float
151          :param seconds_between_writes: float
152          :param auth: authentication method
153          """
154  
155          assert login_or_token is None or isinstance(login_or_token, str), login_or_token
156          assert password is None or isinstance(password, str), password
157          assert jwt is None or isinstance(jwt, str), jwt
158          assert isinstance(base_url, str), base_url
159          assert isinstance(timeout, int), timeout
160          assert user_agent is None or isinstance(user_agent, str), user_agent
161          assert isinstance(per_page, int), per_page
162          assert isinstance(verify, (bool, str)), verify
163          assert retry is None or isinstance(retry, int) or isinstance(retry, urllib3.util.Retry), retry
164          assert pool_size is None or isinstance(pool_size, int), pool_size
165          assert seconds_between_requests is None or seconds_between_requests >= 0
166          assert seconds_between_writes is None or seconds_between_writes >= 0
167          assert auth is None or isinstance(auth, github.Auth.Auth), auth
168  
169          if password is not None:
170              warnings.warn(
171                  "Arguments login_or_token and password are deprecated, please use "
172                  "auth=github.Auth.Login(...) instead",
173                  category=DeprecationWarning,
174              )
175              auth = github.Auth.Login(login_or_token, password)  # type: ignore
176          elif login_or_token is not None:
177              warnings.warn(
178                  "Argument login_or_token is deprecated, please use " "auth=github.Auth.Token(...) instead",
179                  category=DeprecationWarning,
180              )
181              auth = github.Auth.Token(login_or_token)
182          elif jwt is not None:
183              warnings.warn(
184                  "Argument jwt is deprecated, please use "
185                  "auth=github.Auth.AppAuth(...) or "
186                  "auth=github.Auth.AppAuthToken(...) instead",
187                  category=DeprecationWarning,
188              )
189              auth = github.Auth.AppAuthToken(jwt)
190          elif app_auth is not None:
191              warnings.warn(
192                  "Argument app_auth is deprecated, please use " "auth=github.Auth.AppInstallationAuth(...) instead",
193                  category=DeprecationWarning,
194              )
195              auth = app_auth
196  
197          self.__requester = Requester(
198              auth,
199              base_url,
200              timeout,
201              user_agent,
202              per_page,
203              verify,
204              retry,
205              pool_size,
206              seconds_between_requests,
207              seconds_between_writes,
208          )
209  
210      def close(self) -> None:
211          """
212          Close connections to the server. Alternatively, use the Github object as a context manager:
213  
214          .. code-block:: python
215  
216            with github.Github(...) as gh:
217              # do something
218          """
219          self.__requester.close()
220  
221      def __enter__(self) -> Github:
222          return self
223  
224      def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
225          self.close()
226  
227      @property
228      def FIX_REPO_GET_GIT_REF(self) -> bool:
229          return self.__requester.FIX_REPO_GET_GIT_REF
230  
231      @FIX_REPO_GET_GIT_REF.setter
232      def FIX_REPO_GET_GIT_REF(self, value: bool) -> None:
233          self.__requester.FIX_REPO_GET_GIT_REF = value
234  
235      # v3: Remove this property? Why should it be necessary to read/modify it after construction
236      @property
237      def per_page(self) -> int:
238          return self.__requester.per_page
239  
240      @per_page.setter
241      def per_page(self, value: int) -> None:
242          self.__requester.per_page = value
243  
244      # v3: Provide a unified way to access values of headers of last response
245      # v3: (and add/keep ad hoc properties for specific useful headers like rate limiting, oauth scopes, etc.)
246      # v3: Return an instance of a class: using a tuple did not allow to add a field "resettime"
247      @property
248      def rate_limiting(self) -> tuple[int, int]:
249          """
250          First value is requests remaining, second value is request limit.
251          """
252          remaining, limit = self.__requester.rate_limiting
253          if limit < 0:
254              self.get_rate_limit()
255          return self.__requester.rate_limiting
256  
257      @property
258      def rate_limiting_resettime(self) -> int:
259          """
260          Unix timestamp indicating when rate limiting will reset.
261          """
262          if self.__requester.rate_limiting_resettime == 0:
263              self.get_rate_limit()
264          return self.__requester.rate_limiting_resettime
265  
266      def get_rate_limit(self) -> RateLimit:
267          """
268          Rate limit status for different resources (core/search/graphql).
269  
270          :calls: `GET /rate_limit <https://docs.github.com/en/rest/reference/rate-limit>`_
271          """
272          headers, data = self.__requester.requestJsonAndCheck("GET", "/rate_limit")
273          return RateLimit(self.__requester, headers, data["resources"], True)
274  
275      @property
276      def oauth_scopes(self) -> list[str] | None:
277          """
278          :type: list of string
279          """
280          return self.__requester.oauth_scopes
281  
282      def get_license(self, key: Opt[str] = NotSet) -> License:
283          """
284          :calls: `GET /license/{license} <https://docs.github.com/en/rest/reference/licenses#get-a-license>`_
285          """
286  
287          assert isinstance(key, str), key
288          headers, data = self.__requester.requestJsonAndCheck("GET", f"/licenses/{key}")
289          return github.License.License(self.__requester, headers, data, completed=True)
290  
291      def get_licenses(self) -> PaginatedList[License]:
292          """
293          :calls: `GET /licenses <https://docs.github.com/en/rest/reference/licenses#get-all-commonly-used-licenses>`_
294          """
295  
296          url_parameters: dict[str, Any] = {}
297  
298          return PaginatedList(github.License.License, self.__requester, "/licenses", url_parameters)
299  
300      def get_events(self) -> PaginatedList[Event]:
301          """
302          :calls: `GET /events <https://docs.github.com/en/rest/reference/activity#list-public-events>`_
303          """
304  
305          return PaginatedList(github.Event.Event, self.__requester, "/events", None)
306  
307      def get_user(self, login: Opt[str] = NotSet) -> NamedUser | AuthenticatedUser:
308          """
309          :calls: `GET /users/{user} <https://docs.github.com/en/rest/reference/users>`_ or `GET /user <https://docs.github.com/en/rest/reference/users>`_
310          """
311          assert login is NotSet or isinstance(login, str), login
312          if login is NotSet:
313              return github.AuthenticatedUser.AuthenticatedUser(self.__requester, {}, {"url": "/user"}, completed=False)
314          else:
315              headers, data = self.__requester.requestJsonAndCheck("GET", f"/users/{login}")
316              return github.NamedUser.NamedUser(self.__requester, headers, data, completed=True)
317  
318      def get_user_by_id(self, user_id: int) -> NamedUser:
319          """
320          :calls: `GET /user/{id} <https://docs.github.com/en/rest/reference/users>`_
321          :param user_id: int
322          :rtype: :class:`github.NamedUser.NamedUser`
323          """
324          assert isinstance(user_id, int), user_id
325          headers, data = self.__requester.requestJsonAndCheck("GET", f"/user/{user_id}")
326          return github.NamedUser.NamedUser(self.__requester, headers, data, completed=True)
327  
328      def get_users(self, since: Opt[int] = NotSet) -> PaginatedList[NamedUser]:
329          """
330          :calls: `GET /users <https://docs.github.com/en/rest/reference/users>`_
331          """
332          assert since is NotSet or isinstance(since, int), since
333          url_parameters = dict()
334          if since is not NotSet:
335              url_parameters["since"] = since
336          return PaginatedList(github.NamedUser.NamedUser, self.__requester, "/users", url_parameters)
337  
338      def get_organization(self, login: str) -> Organization:
339          """
340          :calls: `GET /orgs/{org} <https://docs.github.com/en/rest/reference/orgs>`_
341          """
342          assert isinstance(login, str), login
343          headers, data = self.__requester.requestJsonAndCheck("GET", f"/orgs/{login}")
344          return github.Organization.Organization(self.__requester, headers, data, completed=True)
345  
346      def get_organizations(self, since: Opt[int] = NotSet) -> PaginatedList[Organization]:
347          """
348          :calls: `GET /organizations <https://docs.github.com/en/rest/reference/orgs#list-organizations>`_
349          """
350          assert since is NotSet or isinstance(since, int), since
351          url_parameters = dict()
352          if since is not NotSet:
353              url_parameters["since"] = since
354          return PaginatedList(
355              github.Organization.Organization,
356              self.__requester,
357              "/organizations",
358              url_parameters,
359          )
360  
361      def get_enterprise(self, enterprise: str) -> github.Enterprise.Enterprise:
362          """
363          :calls: `GET /enterprises/{enterprise} <https://docs.github.com/en/enterprise-cloud@latest/rest/enterprise-admin>`_
364          :param enterprise: string
365          :rtype: :class:`Enterprise`
366          """
367          assert isinstance(enterprise, str), enterprise
368          # There is no native "/enterprises/{enterprise}" api, so this function is a hub for apis that start with "/enterprise/{enterprise}".
369          return github.Enterprise.Enterprise(self.__requester, enterprise)
370  
371      def get_repo(self, full_name_or_id: int | str, lazy: bool = False) -> Repository:
372          """
373          :calls: `GET /repos/{owner}/{repo} <https://docs.github.com/en/rest/reference/repos>`_ or `GET /repositories/{id} <https://docs.github.com/en/rest/reference/repos>`_
374          """
375          assert isinstance(full_name_or_id, (str, int)), full_name_or_id
376          url_base = "/repositories/" if isinstance(full_name_or_id, int) else "/repos/"
377          url = f"{url_base}{full_name_or_id}"
378          if lazy:
379              return github.Repository.Repository(self.__requester, {}, {"url": url}, completed=False)
380          headers, data = self.__requester.requestJsonAndCheck("GET", url)
381          return github.Repository.Repository(self.__requester, headers, data, completed=True)
382  
383      def get_repos(
384          self,
385          since: Opt[int] = NotSet,
386          visibility: Opt[str] = NotSet,
387      ) -> PaginatedList[Repository]:
388          """
389          :calls: `GET /repositories <https://docs.github.com/en/rest/reference/repos#list-public-repositories>`_
390          :param since: integer
391          :param visibility: string ('all','public')
392          """
393          assert since is NotSet or isinstance(since, int), since
394          url_parameters: dict[str, Any] = {}
395          if since is not NotSet:
396              url_parameters["since"] = since
397          if visibility is not NotSet:
398              assert visibility in ("public", "all"), visibility
399              url_parameters["visibility"] = visibility
400          return PaginatedList(
401              github.Repository.Repository,
402              self.__requester,
403              "/repositories",
404              url_parameters,
405          )
406  
407      def get_project(self, id: int) -> Project:
408          """
409          :calls: `GET /projects/{project_id} <https://docs.github.com/en/rest/reference/projects#get-a-project>`_
410          """
411          headers, data = self.__requester.requestJsonAndCheck(
412              "GET",
413              f"/projects/{id:d}",
414              headers={"Accept": Consts.mediaTypeProjectsPreview},
415          )
416          return github.Project.Project(self.__requester, headers, data, completed=True)
417  
418      def get_project_column(self, id: int) -> ProjectColumn:
419          """
420          :calls: `GET /projects/columns/{column_id} <https://docs.github.com/en/rest/reference/projects#get-a-project-column>`_
421          """
422          headers, data = self.__requester.requestJsonAndCheck(
423              "GET",
424              "/projects/columns/%d" % id,
425              headers={"Accept": Consts.mediaTypeProjectsPreview},
426          )
427          return github.ProjectColumn.ProjectColumn(self.__requester, headers, data, completed=True)
428  
429      def get_gist(self, id: str) -> Gist:
430          """
431          :calls: `GET /gists/{id} <https://docs.github.com/en/rest/reference/gists>`_
432          """
433          assert isinstance(id, str), id
434          headers, data = self.__requester.requestJsonAndCheck("GET", f"/gists/{id}")
435          return github.Gist.Gist(self.__requester, headers, data, completed=True)
436  
437      def get_gists(self, since: Opt[datetime] = NotSet) -> PaginatedList[Gist]:
438          """
439          :calls: `GET /gists/public <https://docs.github.com/en/rest/reference/gists>`_
440          """
441          assert since is NotSet or isinstance(since, datetime), since
442          url_parameters = dict()
443          if is_defined(since):
444              url_parameters["since"] = since.strftime("%Y-%m-%dT%H:%M:%SZ")
445          return PaginatedList(github.Gist.Gist, self.__requester, "/gists/public", url_parameters)
446  
447      def search_repositories(
448          self,
449          query: str,
450          sort: Opt[str] = NotSet,
451          order: Opt[str] = NotSet,
452          **qualifiers: Any,
453      ) -> PaginatedList[Repository]:
454          """
455          :calls: `GET /search/repositories <https://docs.github.com/en/rest/reference/search>`_
456          :param query: string
457          :param sort: string ('stars', 'forks', 'updated')
458          :param order: string ('asc', 'desc')
459          :param qualifiers: keyword dict query qualifiers
460          """
461          assert isinstance(query, str), query
462          url_parameters = dict()
463          if sort is not NotSet:  # pragma no branch (Should be covered)
464              assert sort in ("stars", "forks", "updated"), sort
465              url_parameters["sort"] = sort
466          if order is not NotSet:  # pragma no branch (Should be covered)
467              assert order in ("asc", "desc"), order
468              url_parameters["order"] = order
469  
470          query_chunks = []
471          if query:  # pragma no branch (Should be covered)
472              query_chunks.append(query)
473  
474          for qualifier, value in qualifiers.items():
475              query_chunks.append(f"{qualifier}:{value}")
476  
477          url_parameters["q"] = " ".join(query_chunks)
478          assert url_parameters["q"], "need at least one qualifier"
479  
480          return PaginatedList(
481              github.Repository.Repository,
482              self.__requester,
483              "/search/repositories",
484              url_parameters,
485          )
486  
487      def search_users(
488          self,
489          query: str,
490          sort: Opt[str] = NotSet,
491          order: Opt[str] = NotSet,
492          **qualifiers: Any,
493      ) -> PaginatedList[NamedUser]:
494          """
495          :calls: `GET /search/users <https://docs.github.com/en/rest/reference/search>`_
496          :param query: string
497          :param sort: string ('followers', 'repositories', 'joined')
498          :param order: string ('asc', 'desc')
499          :param qualifiers: keyword dict query qualifiers
500          :rtype: :class:`PaginatedList` of :class:`github.NamedUser.NamedUser`
501          """
502          assert isinstance(query, str), query
503          url_parameters = dict()
504          if sort is not NotSet:
505              assert sort in ("followers", "repositories", "joined"), sort
506              url_parameters["sort"] = sort
507          if order is not NotSet:
508              assert order in ("asc", "desc"), order
509              url_parameters["order"] = order
510  
511          query_chunks = []
512          if query:
513              query_chunks.append(query)
514  
515          for qualifier, value in qualifiers.items():
516              query_chunks.append(f"{qualifier}:{value}")
517  
518          url_parameters["q"] = " ".join(query_chunks)
519          assert url_parameters["q"], "need at least one qualifier"
520  
521          return PaginatedList(
522              github.NamedUser.NamedUser,
523              self.__requester,
524              "/search/users",
525              url_parameters,
526          )
527  
528      def search_issues(
529          self,
530          query: str,
531          sort: Opt[str] = NotSet,
532          order: Opt[str] = NotSet,
533          **qualifiers: Any,
534      ) -> PaginatedList[Issue]:
535          """
536          :calls: `GET /search/issues <https://docs.github.com/en/rest/reference/search>`_
537          :param query: string
538          :param sort: string ('comments', 'created', 'updated')
539          :param order: string ('asc', 'desc')
540          :param qualifiers: keyword dict query qualifiers
541          :rtype: :class:`PaginatedList` of :class:`github.Issue.Issue`
542          """
543          assert isinstance(query, str), query
544          url_parameters = dict()
545          if sort is not NotSet:
546              assert sort in ("comments", "created", "updated"), sort
547              url_parameters["sort"] = sort
548          if order is not NotSet:
549              assert order in ("asc", "desc"), order
550              url_parameters["order"] = order
551  
552          query_chunks = []
553          if query:  # pragma no branch (Should be covered)
554              query_chunks.append(query)
555  
556          for qualifier, value in qualifiers.items():
557              query_chunks.append(f"{qualifier}:{value}")
558  
559          url_parameters["q"] = " ".join(query_chunks)
560          assert url_parameters["q"], "need at least one qualifier"
561  
562          return PaginatedList(github.Issue.Issue, self.__requester, "/search/issues", url_parameters)
563  
564      def search_code(
565          self,
566          query: str,
567          sort: Opt[str] = NotSet,
568          order: Opt[str] = NotSet,
569          highlight: bool = False,
570          **qualifiers: Any,
571      ) -> PaginatedList[ContentFile]:
572          """
573          :calls: `GET /search/code <https://docs.github.com/en/rest/reference/search>`_
574          :param query: string
575          :param sort: string ('indexed')
576          :param order: string ('asc', 'desc')
577          :param highlight: boolean (True, False)
578          :param qualifiers: keyword dict query qualifiers
579          :rtype: :class:`PaginatedList` of :class:`github.ContentFile.ContentFile`
580          """
581          assert isinstance(query, str), query
582          url_parameters = dict()
583          if sort is not NotSet:  # pragma no branch (Should be covered)
584              assert sort in ("indexed",), sort
585              url_parameters["sort"] = sort
586          if order is not NotSet:  # pragma no branch (Should be covered)
587              assert order in ("asc", "desc"), order
588              url_parameters["order"] = order
589  
590          query_chunks = []
591          if query:  # pragma no branch (Should be covered)
592              query_chunks.append(query)
593  
594          for qualifier, value in qualifiers.items():
595              query_chunks.append(f"{qualifier}:{value}")
596  
597          url_parameters["q"] = " ".join(query_chunks)
598          assert url_parameters["q"], "need at least one qualifier"
599  
600          headers = {"Accept": Consts.highLightSearchPreview} if highlight else None
601  
602          return PaginatedList(
603              github.ContentFile.ContentFile,
604              self.__requester,
605              "/search/code",
606              url_parameters,
607              headers=headers,
608          )
609  
610      def search_commits(
611          self,
612          query: str,
613          sort: Opt[str] = NotSet,
614          order: Opt[str] = NotSet,
615          **qualifiers: Any,
616      ) -> PaginatedList[Commit]:
617          """
618          :calls: `GET /search/commits <https://docs.github.com/en/rest/reference/search>`_
619          :param query: string
620          :param sort: string ('author-date', 'committer-date')
621          :param order: string ('asc', 'desc')
622          :param qualifiers: keyword dict query qualifiers
623          :rtype: :class:`PaginatedList` of :class:`github.Commit.Commit`
624          """
625          assert isinstance(query, str), query
626          url_parameters = dict()
627          if sort is not NotSet:
628              assert sort in ("author-date", "committer-date"), sort
629              url_parameters["sort"] = sort
630          if order is not NotSet:
631              assert order in ("asc", "desc"), order
632              url_parameters["order"] = order
633  
634          query_chunks = []
635          if query:
636              query_chunks.append(query)
637  
638          for qualifier, value in qualifiers.items():
639              query_chunks.append(f"{qualifier}:{value}")
640  
641          url_parameters["q"] = " ".join(query_chunks)
642          assert url_parameters["q"], "need at least one qualifier"
643  
644          return PaginatedList(
645              github.Commit.Commit,
646              self.__requester,
647              "/search/commits",
648              url_parameters,
649              headers={"Accept": Consts.mediaTypeCommitSearchPreview},
650          )
651  
652      def search_topics(self, query: str, **qualifiers: Any) -> PaginatedList[Topic]:
653          """
654          :calls: `GET /search/topics <https://docs.github.com/en/rest/reference/search>`_
655          :param query: string
656          :param qualifiers: keyword dict query qualifiers
657          :rtype: :class:`PaginatedList` of :class:`github.Topic.Topic`
658          """
659          assert isinstance(query, str), query
660          url_parameters = dict()
661  
662          query_chunks = []
663          if query:  # pragma no branch (Should be covered)
664              query_chunks.append(query)
665  
666          for qualifier, value in qualifiers.items():
667              query_chunks.append(f"{qualifier}:{value}")
668  
669          url_parameters["q"] = " ".join(query_chunks)
670          assert url_parameters["q"], "need at least one qualifier"
671  
672          return PaginatedList(
673              github.Topic.Topic,
674              self.__requester,
675              "/search/topics",
676              url_parameters,
677              headers={"Accept": Consts.mediaTypeTopicsPreview},
678          )
679  
680      def render_markdown(self, text: str, context: Opt[Repository] = NotSet) -> str:
681          """
682          :calls: `POST /markdown <https://docs.github.com/en/rest/reference/markdown>`_
683          :param text: string
684          :param context: :class:`github.Repository.Repository`
685          :rtype: string
686          """
687          assert isinstance(text, str), text
688          assert context is NotSet or isinstance(context, github.Repository.Repository), context
689          post_parameters = {"text": text}
690          if is_defined(context):
691              post_parameters["mode"] = "gfm"
692              post_parameters["context"] = context._identity
693          status, headers, data = self.__requester.requestJson("POST", "/markdown", input=post_parameters)
694          return data
695  
696      def get_hook(self, name: str) -> HookDescription:
697          """
698          :calls: `GET /hooks/{name} <https://docs.github.com/en/rest/reference/repos#webhooks>`_
699          """
700          assert isinstance(name, str), name
701          headers, attributes = self.__requester.requestJsonAndCheck("GET", f"/hooks/{name}")
702          return HookDescription(self.__requester, headers, attributes, completed=True)
703  
704      def get_hooks(self) -> list[HookDescription]:
705          """
706          :calls: `GET /hooks <https://docs.github.com/en/rest/reference/repos#webhooks>`_
707          :rtype: list of :class:`github.HookDescription.HookDescription`
708          """
709          headers, data = self.__requester.requestJsonAndCheck("GET", "/hooks")
710          return [HookDescription(self.__requester, headers, attributes, completed=True) for attributes in data]
711  
712      def get_hook_delivery(self, hook_id: int, delivery_id: int) -> HookDelivery:
713          """
714          :calls: `GET /hooks/{hook_id}/deliveries/{delivery_id} <https://docs.github.com/en/rest/reference/repos#webhooks>`_
715          :param hook_id: integer
716          :param delivery_id: integer
717          :rtype: :class:`HookDelivery`
718          """
719          assert isinstance(hook_id, int), hook_id
720          assert isinstance(delivery_id, int), delivery_id
721          headers, attributes = self.__requester.requestJsonAndCheck("GET", f"/hooks/{hook_id}/deliveries/{delivery_id}")
722          return HookDelivery(self.__requester, headers, attributes, completed=True)
723  
724      def get_hook_deliveries(self, hook_id: int) -> list[HookDeliverySummary]:
725          """
726          :calls: `GET /hooks/{hook_id}/deliveries <https://docs.github.com/en/rest/reference/repos#webhooks>`_
727          :param hook_id: integer
728          :rtype: list of :class:`HookDeliverySummary`
729          """
730          assert isinstance(hook_id, int), hook_id
731          headers, data = self.__requester.requestJsonAndCheck("GET", f"/hooks/{hook_id}/deliveries")
732          return [HookDeliverySummary(self.__requester, headers, attributes, completed=True) for attributes in data]
733  
734      def get_gitignore_templates(self) -> list[str]:
735          """
736          :calls: `GET /gitignore/templates <https://docs.github.com/en/rest/reference/gitignore>`_
737          """
738          headers, data = self.__requester.requestJsonAndCheck("GET", "/gitignore/templates")
739          return data
740  
741      def get_gitignore_template(self, name: str) -> GitignoreTemplate:
742          """
743          :calls: `GET /gitignore/templates/{name} <https://docs.github.com/en/rest/reference/gitignore>`_
744          """
745          assert isinstance(name, str), name
746          headers, attributes = self.__requester.requestJsonAndCheck("GET", f"/gitignore/templates/{name}")
747          return github.GitignoreTemplate.GitignoreTemplate(self.__requester, headers, attributes, completed=True)
748  
749      def get_emojis(self) -> dict[str, str]:
750          """
751          :calls: `GET /emojis <https://docs.github.com/en/rest/reference/emojis>`_
752          :rtype: dictionary of type => url for emoji`
753          """
754          headers, attributes = self.__requester.requestJsonAndCheck("GET", "/emojis")
755          return attributes
756  
757      def create_from_raw_data(
758          self, klass: type[TGithubObject], raw_data: dict[str, Any], headers: dict[str, str | int] | None = None
759      ) -> TGithubObject:
760          """
761          Creates an object from raw_data previously obtained by :attr:`GithubObject.raw_data`,
762          and optionally headers previously obtained by :attr:`GithubObject.raw_headers`.
763  
764          :param klass: the class of the object to create
765          :param raw_data: dict
766          :param headers: dict
767          :rtype: instance of class ``klass``
768          """
769          if headers is None:
770              headers = {}
771  
772          return klass(self.__requester, headers, raw_data, completed=True)
773  
774      def dump(self, obj: GithubObject, file: BinaryIO, protocol: int = 0) -> None:
775          """
776          Dumps (pickles) a PyGithub object to a file-like object.
777          Some effort is made to not pickle sensitive information like the Github credentials used in the :class:`Github` instance.
778          But NO EFFORT is made to remove sensitive information from the object's attributes.
779  
780          :param obj: the object to pickle
781          :param file: the file-like object to pickle to
782          :param protocol: the `pickling protocol <https://python.readthedocs.io/en/latest/library/pickle.html#data-stream-format>`_
783          """
784          pickle.dump((obj.__class__, obj.raw_data, obj.raw_headers), file, protocol)
785  
786      def load(self, f: BinaryIO) -> Any:
787          """
788          Loads (unpickles) a PyGithub object from a file-like object.
789  
790          :param f: the file-like object to unpickle from
791          :return: the unpickled object
792          """
793          return self.create_from_raw_data(*pickle.load(f))
794  
795      def get_oauth_application(self, client_id: str, client_secret: str) -> ApplicationOAuth:
796          return github.ApplicationOAuth.ApplicationOAuth(
797              self.__requester,
798              headers={},
799              attributes={"client_id": client_id, "client_secret": client_secret},
800              completed=False,
801          )
802  
803      def get_app(self, slug: Opt[str] = NotSet) -> GithubApp:
804          """
805          :calls: `GET /apps/{slug} <https://docs.github.com/en/rest/reference/apps>`_ or `GET /app <https://docs.github.com/en/rest/reference/apps>`_
806          """
807          assert slug is NotSet or isinstance(slug, str), slug
808  
809          if slug is NotSet:
810              # with no slug given, calling /app returns the authenticated app,
811              # including the actual /apps/{slug}
812              warnings.warn(
813                  "Argument slug is mandatory, calling this method without the slug argument is deprecated, please use "
814                  "github.GithubIntegration(auth=github.Auth.AppAuth(...)).get_app() instead",
815                  category=DeprecationWarning,
816              )
817              return GithubIntegration(**self.__requester.kwargs).get_app()
818          else:
819              # with a slug given, we can lazily load the GithubApp
820              return github.GithubApp.GithubApp(self.__requester, {}, {"url": f"/apps/{slug}"}, completed=False)