Skip to content

Commit 5d237d7

Browse files
authored
lfs: add support for rate limit retries (#340)
1 parent a96c366 commit 5d237d7

File tree

1 file changed

+19
-1
lines changed

1 file changed

+19
-1
lines changed

src/scmrepo/git/lfs/client.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from abc import abstractmethod
77
from collections.abc import Iterable, Iterator
88
from contextlib import AbstractContextManager, contextmanager, suppress
9+
from http import HTTPStatus
910
from tempfile import NamedTemporaryFile
11+
from time import time
1012
from typing import TYPE_CHECKING, Any, Optional
1113

1214
import aiohttp
@@ -64,11 +66,12 @@ async def get_client(**kwargs):
6466
sock_connect=self._REQUEST_TIMEOUT,
6567
sock_read=self._REQUEST_TIMEOUT,
6668
),
67-
retry_options=ExponentialRetry(
69+
retry_options=_ExponentialRetry(
6870
attempts=self._SESSION_RETRIES,
6971
factor=self._SESSION_BACKOFF_FACTOR,
7072
max_timeout=self._REQUEST_TIMEOUT,
7173
exceptions={aiohttp.ClientError},
74+
statuses={HTTPStatus.TOO_MANY_REQUESTS},
7275
),
7376
**kwargs,
7477
)
@@ -272,3 +275,18 @@ def _as_atomic(to_info: str, create_parents: bool = False) -> Iterator[str]:
272275
raise
273276
else:
274277
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

Comments
 (0)