Repository: PyGithub/PyGithub

Issue:
Handling hitting abuse detection limits and using Retry-After
Hey

Thanks a lot for the library. It really helped us with our CI setup.

However recently we have started hitting the abuse detection limits imposed by GH https://docs.github.com/en/free-pro-team@latest/rest/guides/best-practices-for-integrators#dealing-with-abuse-rate-limits

When this happens the library seems to just throw an exception. How can I get the Retry-After header? It is part of the exception?


Discussion:
We sadly do not currently preserve the headers when creating an exception. However, this is an amazing idea, and I'm going to reflect on how to implement this.
The headers could be added to the `GithubException` as another field. 

Resolving patch:
diff --git a/github/GithubException.py b/github/GithubException.py
--- a/github/GithubException.py
+++ b/github/GithubException.py
@@ -38,11 +38,12 @@ class GithubException(Exception):
     Some other types of exceptions might be raised by underlying libraries, for example for network-related issues.
     """
 
-    def __init__(self, status, data):
+    def __init__(self, status, data, headers):
         super().__init__()
         self.__status = status
         self.__data = data
-        self.args = [status, data]
+        self.__headers = headers
+        self.args = [status, data, headers]
 
     @property
     def status(self):
@@ -58,6 +59,13 @@ def data(self):
         """
         return self.__data
 
+    @property
+    def headers(self):
+        """
+        The headers returned by the Github API
+        """
+        return self.__headers
+
     def __str__(self):
         return "{status} {data}".format(status=self.status, data=json.dumps(self.data))
 
diff --git a/github/GithubObject.py b/github/GithubObject.py
--- a/github/GithubObject.py
+++ b/github/GithubObject.py
@@ -300,7 +300,7 @@ def _completeIfNeeded(self):
     def __complete(self):
         if self._url.value is None:
             raise GithubException.IncompletableObject(
-                400, "Returned object contains no URL"
+                400, "Returned object contains no URL", None
             )
         headers, data = self._requester.requestJsonAndCheck("GET", self._url.value)
         self._storeAndUseAttributes(headers, data)
diff --git a/github/Requester.py b/github/Requester.py
--- a/github/Requester.py
+++ b/github/Requester.py
@@ -435,7 +435,7 @@ def __createException(self, status, headers, output):
             cls = GithubException.UnknownObjectException
         else:
             cls = GithubException.GithubException
-        return cls(status, output)
+        return cls(status, output, headers)
 
     def __structuredFromJson(self, data):
         if len(data) == 0:


Test patch:
diff --git a/tests/Exceptions.py b/tests/Exceptions.py
--- a/tests/Exceptions.py
+++ b/tests/Exceptions.py
@@ -90,7 +90,7 @@ def testBadAuthentication(self):
         self.assertEqual(str(raisedexp.exception), '401 {"message": "Bad credentials"}')
 
     def testExceptionPickling(self):
-        pickle.loads(pickle.dumps(github.GithubException("foo", "bar")))
+        pickle.loads(pickle.dumps(github.GithubException("foo", "bar", None)))
 
 
 class SpecificExceptions(Framework.TestCase):
@@ -134,7 +134,9 @@ def exceed():
                 res = self.g.search_code("jacquev6")
                 res.get_page(0)
 
-        self.assertRaises(github.RateLimitExceededException, exceed)
+        with self.assertRaises(github.RateLimitExceededException) as raised:
+            exceed()
+        self.assertEqual(raised.exception.headers.get("retry-after"), "60")
 
     def testIncompletableObject(self):
         github.UserKey.UserKey.setCheckAfterInitFlag(False)