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)