Skip to content

Commit c44ec52

Browse files
minrkEnricoMi
andauthored
Make requester a public attribute (#3056)
There will always be new APIs not yet supported by PyGitHub (e.g. #2718). This adds an escape hatch for users to send requests directly without having to start from scratch or use private APIs while waiting for PyGitHub to add support. Changes: - private `object._requester` `.__requester` is now available via public `.requester` attribute on MainClass, GitHubObject, etc. - add Requester APIs to docs as public methods, indicating status as stable public APIs Fixes #2071 which received only positive feedback, but was closed for inactivity. --------- Co-authored-by: Enrico Minack <[email protected]>
1 parent 1a05b43 commit c44ec52

File tree

10 files changed

+123
-0
lines changed

10 files changed

+123
-0
lines changed

doc/utilities.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,12 @@ Input classes
3939
.. autoclass:: github.InputFileContent.InputFileContent
4040
.. autoclass:: github.InputGitAuthor.InputGitAuthor
4141
.. autoclass:: github.InputGitTreeElement.InputGitTreeElement
42+
43+
Raw Requests
44+
------------
45+
46+
If you need to make requests to APIs not yet supported by PyGithub,
47+
you can use the :class:`.Requester` object directly, available as :attr:`object.requester` on most PyGithub objects.
48+
49+
.. autoclass:: github.Requester.Requester
50+
:members:

github/GithubIntegration.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,16 @@ def get_github_for_installation(
182182
auth = self.auth.get_installation_auth(installation_id, token_permissions, self.__requester)
183183
return github.Github(**self.__requester.withAuth(auth).kwargs)
184184

185+
@property
186+
def requester(self) -> Requester:
187+
"""
188+
Return my Requester object.
189+
190+
For example, to make requests to API endpoints not yet supported by PyGitHub.
191+
192+
"""
193+
return self.__requester
194+
185195
def _get_headers(self) -> dict[str, str]:
186196
"""
187197
Get headers for the requests.

github/GithubObject.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,16 @@ def _storeAndUseAttributes(self, headers: Dict[str, Union[str, int]], attributes
199199
self._rawData = attributes
200200
self._useAttributes(attributes)
201201

202+
@property
203+
def requester(self) -> "Requester":
204+
"""
205+
Return my Requester object.
206+
207+
For example, to make requests to API endpoints not yet supported by PyGitHub.
208+
209+
"""
210+
return self._requester
211+
202212
@property
203213
def raw_data(self) -> Dict[str, Any]:
204214
"""

github/Installation.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ def __repr__(self) -> str:
104104
def get_github_for_installation(self) -> Github:
105105
return github.Github(**self._requester.kwargs)
106106

107+
@property
108+
def requester(self) -> Requester:
109+
"""
110+
Return my Requester object.
111+
112+
For example, to make requests to API endpoints not yet supported by PyGitHub.
113+
114+
"""
115+
return self._requester
116+
107117
@property
108118
def id(self) -> int:
109119
return self._id.value

github/MainClass.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,16 @@ def __enter__(self) -> Github:
273273
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
274274
self.close()
275275

276+
@property
277+
def requester(self) -> Requester:
278+
"""
279+
Return my Requester object.
280+
281+
For example, to make requests to API endpoints not yet supported by PyGitHub.
282+
283+
"""
284+
return self.__requester
285+
276286
@property
277287
def FIX_REPO_GET_GIT_REF(self) -> bool:
278288
return self.__requester.FIX_REPO_GET_GIT_REF

github/Requester.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,15 @@ def requestJsonAndCheck(
547547
headers: Optional[Dict[str, str]] = None,
548548
input: Optional[Any] = None,
549549
) -> Tuple[Dict[str, Any], Any]:
550+
"""
551+
Send a request with JSON body.
552+
553+
:param input: request body, serialized to JSON if specified
554+
555+
:return: ``(headers: dict, JSON Response: Any)``
556+
:raises: :class:`GithubException` for error status codes
557+
558+
"""
550559
return self.__check(*self.requestJson(verb, url, parameters, headers, input, self.__customConnection(url)))
551560

552561
def requestMultipartAndCheck(
@@ -557,6 +566,15 @@ def requestMultipartAndCheck(
557566
headers: Optional[Dict[str, Any]] = None,
558567
input: Optional[Dict[str, str]] = None,
559568
) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]:
569+
"""
570+
Send a request with multi-part-encoded body.
571+
572+
:param input: request body, will be multi-part encoded if specified
573+
574+
:return: ``(headers: dict, JSON Response: Any)``
575+
:raises: :class:`GithubException` for error status codes
576+
577+
"""
560578
return self.__check(*self.requestMultipart(verb, url, parameters, headers, input, self.__customConnection(url)))
561579

562580
def requestBlobAndCheck(
@@ -568,6 +586,15 @@ def requestBlobAndCheck(
568586
input: Optional[str] = None,
569587
cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None,
570588
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
589+
"""
590+
Send a request with a file for the body.
591+
592+
:param input: path to a file to use for the request body
593+
594+
:return: ``(headers: dict, JSON Response: Any)``
595+
:raises: :class:`GithubException` for error status codes
596+
597+
"""
571598
return self.__check(*self.requestBlob(verb, url, parameters, headers, input, self.__customConnection(url)))
572599

573600
def graphql_query(self, query: str, variables: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
@@ -709,6 +736,14 @@ def requestJson(
709736
input: Optional[Any] = None,
710737
cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None,
711738
) -> Tuple[int, Dict[str, Any], str]:
739+
"""
740+
Send a request with JSON input.
741+
742+
:param input: request body, will be serialized as JSON
743+
:returns:``(status, headers, body)``
744+
745+
"""
746+
712747
def encode(input: Any) -> Tuple[str, str]:
713748
return "application/json", json.dumps(input)
714749

@@ -723,6 +758,14 @@ def requestMultipart(
723758
input: Optional[Dict[str, str]] = None,
724759
cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None,
725760
) -> Tuple[int, Dict[str, Any], str]:
761+
"""
762+
Send a request with multi-part encoding.
763+
764+
:param input: request body, will be serialized as multipart form data
765+
:returns:``(status, headers, body)``
766+
767+
"""
768+
726769
def encode(input: Dict[str, Any]) -> Tuple[str, str]:
727770
boundary = "----------------------------3c3ba8b523b2"
728771
eol = "\r\n"
@@ -747,6 +790,13 @@ def requestBlob(
747790
input: Optional[str] = None,
748791
cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None,
749792
) -> Tuple[int, Dict[str, Any], str]:
793+
"""
794+
Send a request with a file as request body.
795+
796+
:param input: path to a local file to use for request body
797+
:returns:``(status, headers, body)``
798+
799+
"""
750800
if headers is None:
751801
headers = {}
752802

@@ -772,6 +822,15 @@ def requestMemoryBlobAndCheck(
772822
file_like: BinaryIO,
773823
cnx: Optional[Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass]] = None,
774824
) -> Tuple[Dict[str, Any], Any]:
825+
"""
826+
Send a request with a binary file-like for the body.
827+
828+
:param file_like: file-like object to use for the request body
829+
:return: ``(headers: dict, JSON Response: Any)``
830+
:raises: :class:`GithubException` for error status codes
831+
832+
"""
833+
775834
# The expected signature of encode means that the argument is ignored.
776835
def encode(_: Any) -> Tuple[str, Any]:
777836
return headers["Content-Type"], file_like

tests/GithubIntegration.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,6 @@ def testGetApp(self):
280280

281281
self.assertEqual(app.name, "PyGithubTest")
282282
self.assertEqual(app.url, "/apps/pygithubtest")
283+
284+
assert github_integration.requester is github_integration.__requester
285+
assert app.requester is app._requester

tests/Github_.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,3 +628,6 @@ def testGetEvents(self):
628628
lambda e: e.type,
629629
["PushEvent", "WatchEvent", "PushEvent", "CommitCommentEvent"],
630630
)
631+
632+
def testRequester(self):
633+
assert self.g.requester is self.g.__requester

tests/Installation.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,8 @@ def testGetGithubForInstallation(self):
9595

9696
repo = g.get_repo("PyGithub/PyGithub")
9797
self.assertEqual(repo.full_name, "PyGithub/PyGithub")
98+
99+
def testRequester(self):
100+
self.assertEqual(len(self.installations), 1)
101+
installation = self.installations[0]
102+
assert installation.requester is installation._requester

tests/Repository.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2081,3 +2081,7 @@ def testChangeAutomateFixWhenNoVulnerabilityAlert(self):
20812081
def testGetVulnerabilityAlertWhenTurnedOff(self):
20822082
lazy_repo = self.getEagerRepository()
20832083
self.assertFalse(lazy_repo.get_vulnerability_alert())
2084+
2085+
def testRequester(self):
2086+
lazy_repo = self.getLazyRepository()
2087+
assert lazy_repo.requester is lazy_repo._requester

0 commit comments

Comments
 (0)