| 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( |
| 112 | logging.INFO, |
| 113 | f'Retrying after {response.headers.get("Retry-After")} seconds', |
| 114 | ) |
| 115 | else: |
| 116 | content = response.reason |
| 117 | |
| 118 | # to identify retry-able methods, we inspect the response body |
| 119 | try: |
| 120 | content = self.get_content(response, url) # type: ignore |
| 121 | content = json.loads(content) # type: ignore |
| 122 | message = content.get("message") # type: ignore |
| 123 | except Exception as e: |
| 124 | # we want to fall back to the actual github exception (probably a rate limit error) |
| 125 | # but provide some context why we could not deal with it without another exception |
| 126 | try: |
| 127 | raise RuntimeError("Failed to inspect response message") from e |
| 128 | except RuntimeError as e: |
| 129 | raise GithubException(response.status, content, response.headers) from e # type: ignore |
| 130 | |
| 131 | try: |
| 132 | if Requester.isRateLimitError(message): |
| 133 | rate_type = "primary" if Requester.isPrimaryRateLimitError(message) else "secondary" |
| 134 | self.__log( |
| 135 | logging.DEBUG, |
| 136 | f"Response body indicates retry-able {rate_type} rate limit error: {message}", |
| 137 | ) |
| 138 | |
| 139 | # check early that we are retrying at all |
| 140 | retry = super().increment(method, url, response, error, _pool, _stacktrace) |
| 141 | |
| 142 | # we backoff primary rate limit at least until X-RateLimit-Reset, |
| 143 | # we backoff secondary rate limit at for secondary_rate_wait seconds |
| 144 | backoff = 0.0 |
| 145 | |
| 146 | if Requester.isPrimaryRateLimitError(message): |
| 147 | if "X-RateLimit-Reset" in response.headers: |
| 148 | value = response.headers.get("X-RateLimit-Reset") |
| 149 | if value and value.isdigit(): |