A Github-specific implementation of `urllib3.Retry` This retries 403 responses if they are retry-able. Github requests are retry-able when the response provides a `"Retry-After"` header, or the content indicates a rate limit error. By default, response codes 403, and 500 up to 599
| 52 | |
| 53 | |
| 54 | class GithubRetry(Retry): |
| 55 | """ |
| 56 | A Github-specific implementation of `urllib3.Retry` |
| 57 | |
| 58 | This retries 403 responses if they are retry-able. Github requests are retry-able when |
| 59 | the response provides a `"Retry-After"` header, or the content indicates a rate limit error. |
| 60 | |
| 61 | By default, response codes 403, and 500 up to 599 are retried. This can be configured |
| 62 | via the `status_forcelist` argument. |
| 63 | |
| 64 | By default, all methods defined in `Retry.DEFAULT_ALLOWED_METHODS` are retried, plus GET and POST. |
| 65 | This can be configured via the `allowed_methods` argument. |
| 66 | |
| 67 | """ |
| 68 | |
| 69 | __logger: Logger | None = None |
| 70 | |
| 71 | # used to mock datetime, mock.patch("github.GithubRetry.date") does not work as this |
| 72 | # references the class, not the module (due to re-exporting in github/__init__.py) |
| 73 | __datetime = datetime |
| 74 | |
| 75 | def __init__(self, secondary_rate_wait: float = DEFAULT_SECONDARY_RATE_WAIT, **kwargs: Any) -> None: |
| 76 | """ |
| 77 | :param secondary_rate_wait: seconds to wait before retrying secondary rate limit errors |
| 78 | :param kwargs: see urllib3.Retry for more arguments |
| 79 | """ |
| 80 | self.secondary_rate_wait = secondary_rate_wait |
| 81 | # 403 is too broad to be retried, but GitHub API signals rate limits via 403 |
| 82 | # we retry 403 and look into the response header via Retry.increment |
| 83 | # to determine if we really retry that 403 |
| 84 | kwargs["status_forcelist"] = kwargs.get("status_forcelist", list(range(500, 600))) + [403] |
| 85 | kwargs["allowed_methods"] = kwargs.get("allowed_methods", Retry.DEFAULT_ALLOWED_METHODS.union({"GET", "POST"})) |
| 86 | super().__init__(**kwargs) |
| 87 | |
| 88 | def new(self, **kw: Any) -> Self: |
| 89 | kw.update(dict(secondary_rate_wait=self.secondary_rate_wait)) |
| 90 | return super().new(**kw) # type: ignore |
| 91 | |
| 92 | def increment( # type: ignore[override] |
| 93 | self, |
| 94 | method: str | None = None, |
| 95 | url: str | None = None, |
| 96 | response: HTTPResponse | None = None, # type: ignore[override] |
| 97 | error: Exception | None = None, |
| 98 | _pool: ConnectionPool | None = None, |
| 99 | _stacktrace: TracebackType | None = None, |
| 100 | ) -> Retry: |
| 101 | if response: |
| 102 | # we retry 403 only when there is a Retry-After header (indicating it is retry-able) |
| 103 | # or the body message does imply a rate limit error |
| 104 | if response.status == 403: |
| 105 | self.__log( |
| 106 | logging.INFO, |
| 107 | f"Request {method} {url} failed with {response.status}: {response.reason}", |
| 108 | ) |
| 109 | if "Retry-After" in response.headers: |
| 110 | # Sleeping 'Retry-After' seconds is implemented in urllib3.Retry.sleep() and called by urllib3 |
| 111 | self.__log( |
no outgoing calls
no test coverage detected
searching dependent graphs…