Skip to content

Commit 9d2567b

Browse files
hovaescohashhar
authored andcommitted
Add retry on 429 error code
1 parent 97c5a7f commit 9d2567b

File tree

2 files changed

+56
-3
lines changed

2 files changed

+56
-3
lines changed

tests/unit/test_client.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,33 @@ def test_5XX_error_retry(status_code, attempts, monkeypatch):
10661066
assert post_retry.retry_count == attempts
10671067

10681068

1069+
def test_429_error_retry(monkeypatch):
1070+
http_resp = TrinoRequest.http.Response()
1071+
http_resp.status_code = 429
1072+
http_resp.headers["Retry-After"] = 1
1073+
1074+
post_retry = RetryRecorder(result=http_resp)
1075+
monkeypatch.setattr(TrinoRequest.http.Session, "post", post_retry)
1076+
1077+
get_retry = RetryRecorder(result=http_resp)
1078+
monkeypatch.setattr(TrinoRequest.http.Session, "get", get_retry)
1079+
1080+
req = TrinoRequest(
1081+
host="coordinator",
1082+
port=8080,
1083+
client_session=ClientSession(
1084+
user="test",
1085+
),
1086+
max_attempts=3
1087+
)
1088+
1089+
req.post("URL")
1090+
assert post_retry.retry_count == 3
1091+
1092+
req.get("URL")
1093+
assert post_retry.retry_count == 3
1094+
1095+
10691096
@pytest.mark.parametrize("status_code", [
10701097
501
10711098
])

trino/client.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import urllib.parse
4444
import warnings
4545
from dataclasses import dataclass
46+
from datetime import datetime
47+
from email.utils import parsedate_to_datetime
4648
from time import sleep
4749
from typing import Any, Dict, List, Optional, Tuple, Union
4850

@@ -347,6 +349,14 @@ def retry(self, func, args, kwargs, err, attempt):
347349
sleep(delay)
348350

349351

352+
class _RetryAfterSleep(object):
353+
def __init__(self, retry_after_header):
354+
self._retry_after_header = retry_after_header
355+
356+
def retry(self):
357+
sleep(self._retry_after_header)
358+
359+
350360
class TrinoRequest(object):
351361
"""
352362
Manage the HTTP requests of a Trino query.
@@ -523,9 +533,9 @@ def max_attempts(self, value) -> None:
523533
self._handle_retry,
524534
handled_exceptions=self._exceptions,
525535
conditions=(
526-
# need retry when there is no exception but the status code is 502, 503, or 504
536+
# need retry when there is no exception but the status code is 429, 502, 503, or 504
527537
lambda response: getattr(response, "status_code", None)
528-
in (502, 503, 504),
538+
in (429, 502, 503, 504),
529539
),
530540
max_attempts=self._max_attempts,
531541
)
@@ -887,7 +897,12 @@ def decorated(*args, **kwargs):
887897
try:
888898
result = func(*args, **kwargs)
889899
if any(guard(result) for guard in conditions):
890-
handle_retry.retry(func, args, kwargs, None, attempt)
900+
if result.status_code == 429 and "Retry-After" in result.headers:
901+
retry_after = _parse_retry_after_header(result.headers.get("Retry-After"))
902+
handle_retry_sleep = _RetryAfterSleep(retry_after)
903+
handle_retry_sleep.retry()
904+
else:
905+
handle_retry.retry(func, args, kwargs, None, attempt)
891906
continue
892907
return result
893908
except Exception as err:
@@ -904,3 +919,14 @@ def decorated(*args, **kwargs):
904919
return decorated
905920

906921
return wrapper
922+
923+
924+
def _parse_retry_after_header(retry_after):
925+
if isinstance(retry_after, int):
926+
return retry_after
927+
elif isinstance(retry_after, str) and retry_after.isdigit():
928+
return int(retry_after)
929+
else:
930+
retry_date = parsedate_to_datetime(retry_after)
931+
now = datetime.utcnow()
932+
return (retry_date - now).total_seconds()

0 commit comments

Comments
 (0)