|
6 | 6 | from abc import abstractmethod
|
7 | 7 | from collections.abc import Iterable, Iterator
|
8 | 8 | from contextlib import AbstractContextManager, contextmanager, suppress
|
| 9 | +from http import HTTPStatus |
9 | 10 | from tempfile import NamedTemporaryFile
|
| 11 | +from time import time |
10 | 12 | from typing import TYPE_CHECKING, Any, Optional
|
11 | 13 |
|
12 | 14 | import aiohttp
|
@@ -64,11 +66,12 @@ async def get_client(**kwargs):
|
64 | 66 | sock_connect=self._REQUEST_TIMEOUT,
|
65 | 67 | sock_read=self._REQUEST_TIMEOUT,
|
66 | 68 | ),
|
67 |
| - retry_options=ExponentialRetry( |
| 69 | + retry_options=_ExponentialRetry( |
68 | 70 | attempts=self._SESSION_RETRIES,
|
69 | 71 | factor=self._SESSION_BACKOFF_FACTOR,
|
70 | 72 | max_timeout=self._REQUEST_TIMEOUT,
|
71 | 73 | exceptions={aiohttp.ClientError},
|
| 74 | + statuses={HTTPStatus.TOO_MANY_REQUESTS}, |
72 | 75 | ),
|
73 | 76 | **kwargs,
|
74 | 77 | )
|
@@ -272,3 +275,18 @@ def _as_atomic(to_info: str, create_parents: bool = False) -> Iterator[str]:
|
272 | 275 | raise
|
273 | 276 | else:
|
274 | 277 | shutil.move(tmp_file.name, to_info)
|
| 278 | + |
| 279 | + |
| 280 | +class _ExponentialRetry(ExponentialRetry): |
| 281 | + def get_timeout( |
| 282 | + self, attempt: int, response: Optional[aiohttp.ClientResponse] = None |
| 283 | + ) -> float: |
| 284 | + if response is not None and response.status == HTTPStatus.TOO_MANY_REQUESTS: |
| 285 | + if "Retry-After" in response.headers: |
| 286 | + with suppress(ValueError): |
| 287 | + return float(response.headers["Retry-After"]) |
| 288 | + for k in ["RateLimit-Reset", "X-RateLimit-Reset"]: |
| 289 | + if k in response.headers: |
| 290 | + with suppress(ValueError): |
| 291 | + return float(response.headers[k]) - time() |
| 292 | + return super().get_timeout(attempt, response) |
0 commit comments