Skip to content

Commit afd63b3

Browse files
HantingZhang2ci.datadog-api-spectherve
authored
Add retry (#1384)
* add retry and its configs * auto reformat * format * redo is_retry and rewrite retry config options * Reformat * Fix syntax & add test * Fixe test & adjusting retry impl * pre-commit fixes * Update template * Fix import * pre-commit fixes * change retry import * remove retry import * remove saving content of the response * update cassette * delete env file * update cassette * use mock for test_retry * pre-commit fixes * use urllib3 for requests * pre-commit fixes * fix return format * pre-commit fixes * add response header * pre-commit fixes * change retry test * pre-commit fixes * update test_retry * pre-commit fixes * use call count * pre-commit fixes * Update .generator/src/generator/templates/rest.j2 Co-authored-by: Thomas Hervé <[email protected]> * pre-commit fixes * Add back test * pre-commit fixes * Fix content encoding * touch up rest client and test * pre-commit fixes * add test without reset header * pre-commit fixes * change allowed method check * Minor tweaks * Use cassette for errors as well * Docs --------- Co-authored-by: ci.datadog-api-spec <[email protected]> Co-authored-by: Thomas Hervé <[email protected]>
1 parent fe2f82d commit afd63b3

File tree

8 files changed

+459
-8
lines changed

8 files changed

+459
-8
lines changed

.generator/src/generator/templates/configuration.j2

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ class Configuration:
158158
check_input_type=True,
159159
check_return_type=True,
160160
spec_property_naming=False,
161+
enable_retry=False,
162+
retry_backoff_factor=2,
163+
max_retries=3,
161164
):
162165
"""Constructor."""
163166
self._base_path = "https://api.datadoghq.com" if host is None else host
@@ -200,7 +203,6 @@ class Configuration:
200203
self.proxy = None
201204
self.proxy_headers = None
202205
self.safe_chars_for_path_param = ""
203-
self.retries = None
204206
# Enable client side validation
205207
self.client_side_validation = True
206208

@@ -217,6 +219,11 @@ class Configuration:
217219
self.check_return_type = check_return_type
218220
self.spec_property_naming = spec_property_naming
219221

222+
# Options for http retry
223+
self.enable_retry = enable_retry
224+
self.retry_backoff_factor = retry_backoff_factor
225+
self.max_retries = max_retries
226+
220227
# Keep track of unstable operations
221228
self.unstable_operations = _UnstableOperations({
222229
{%- for version, api in apis.items() %}

.generator/src/generator/templates/rest.j2

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import re
66
import ssl
77
from urllib.parse import urlencode
88
import zlib
9-
109
import urllib3 # type: ignore
1110

1211
from {{ package }}.exceptions import (
@@ -22,6 +21,30 @@ from {{ package }}.exceptions import (
2221
logger = logging.getLogger(__name__)
2322

2423

24+
class ClientRetry(urllib3.util.Retry):
25+
RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 500, 501, 502, 503, 504, 505, 506, 507, 509, 510, 511])
26+
DEFAULT_ALLOWED_METHODS = frozenset(["GET", "PUT", "DELETE", "POST", "PATCH"])
27+
28+
def get_retry_after(self, response):
29+
"""
30+
This method overrides the default "Retry-after" header and uses dd's X-Ratelimit-Reset header
31+
and gets the value of X-Ratelimit-Reset in seconds.
32+
"""
33+
retry_after = response.headers.get("X-Ratelimit-Reset")
34+
35+
if retry_after is None:
36+
return None
37+
return self.parse_retry_after(retry_after)
38+
39+
def is_retry(self, method, status_code, has_retry_after=False):
40+
if method not in self.DEFAULT_ALLOWED_METHODS:
41+
return False
42+
43+
if self.status_forcelist and status_code in self.status_forcelist:
44+
return True
45+
return self.total and self.respect_retry_after_header and (status_code in self.RETRY_AFTER_STATUS_CODES)
46+
47+
2548
class RESTClientObject:
2649
def __init__(self, configuration, pools_size=4, maxsize=4):
2750
# urllib3.PoolManager will pass all kw parameters to connectionpool
@@ -40,8 +63,12 @@ class RESTClientObject:
4063
if configuration.assert_hostname is not None:
4164
addition_pool_args["assert_hostname"] = configuration.assert_hostname
4265

43-
if configuration.retries is not None:
44-
addition_pool_args["retries"] = configuration.retries
66+
if configuration.enable_retry:
67+
retries = ClientRetry(
68+
total=configuration.max_retries,
69+
backoff_factor=configuration.retry_backoff_factor,
70+
)
71+
addition_pool_args["retries"] = retries
4572

4673
if configuration.socket_options is not None:
4774
addition_pool_args["socket_options"] = configuration.socket_options

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ If you want to enable requests logging, set the `debug` flag on your configurati
8585
configuration.debug = True
8686
```
8787

88+
### Enable retry
89+
90+
If you want to enable retry when getting status code `429` rate-limited, set `enable_retry` to `True`
91+
92+
```python
93+
configuration.enable_retry = True
94+
```
95+
96+
The default max retry is `3`, you can change it with `max_retries`
97+
98+
```python
99+
configuration.max_retries = 5
100+
```
101+
88102
### Configure proxy
89103

90104
You can configure the client to use proxy by setting the `proxy` key on configuration object:

src/datadog_api_client/configuration.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ def __init__(
159159
check_input_type=True,
160160
check_return_type=True,
161161
spec_property_naming=False,
162+
enable_retry=False,
163+
retry_backoff_factor=2,
164+
max_retries=3,
162165
):
163166
"""Constructor."""
164167
self._base_path = "https://api.datadoghq.com" if host is None else host
@@ -201,7 +204,6 @@ def __init__(
201204
self.proxy = None
202205
self.proxy_headers = None
203206
self.safe_chars_for_path_param = ""
204-
self.retries = None
205207
# Enable client side validation
206208
self.client_side_validation = True
207209

@@ -218,6 +220,11 @@ def __init__(
218220
self.check_return_type = check_return_type
219221
self.spec_property_naming = spec_property_naming
220222

223+
# Options for http retry
224+
self.enable_retry = enable_retry
225+
self.retry_backoff_factor = retry_backoff_factor
226+
self.max_retries = max_retries
227+
221228
# Keep track of unstable operations
222229
self.unstable_operations = _UnstableOperations(
223230
{

src/datadog_api_client/rest.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import ssl
99
from urllib.parse import urlencode
1010
import zlib
11-
1211
import urllib3 # type: ignore
1312

1413
from datadog_api_client.exceptions import (
@@ -24,6 +23,30 @@
2423
logger = logging.getLogger(__name__)
2524

2625

26+
class ClientRetry(urllib3.util.Retry):
27+
RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 500, 501, 502, 503, 504, 505, 506, 507, 509, 510, 511])
28+
DEFAULT_ALLOWED_METHODS = frozenset(["GET", "PUT", "DELETE", "POST", "PATCH"])
29+
30+
def get_retry_after(self, response):
31+
"""
32+
This method overrides the default "Retry-after" header and uses dd's X-Ratelimit-Reset header
33+
and gets the value of X-Ratelimit-Reset in seconds.
34+
"""
35+
retry_after = response.headers.get("X-Ratelimit-Reset")
36+
37+
if retry_after is None:
38+
return None
39+
return self.parse_retry_after(retry_after)
40+
41+
def is_retry(self, method, status_code, has_retry_after=False):
42+
if method not in self.DEFAULT_ALLOWED_METHODS:
43+
return False
44+
45+
if self.status_forcelist and status_code in self.status_forcelist:
46+
return True
47+
return self.total and self.respect_retry_after_header and (status_code in self.RETRY_AFTER_STATUS_CODES)
48+
49+
2750
class RESTClientObject:
2851
def __init__(self, configuration, pools_size=4, maxsize=4):
2952
# urllib3.PoolManager will pass all kw parameters to connectionpool
@@ -42,8 +65,12 @@ def __init__(self, configuration, pools_size=4, maxsize=4):
4265
if configuration.assert_hostname is not None:
4366
addition_pool_args["assert_hostname"] = configuration.assert_hostname
4467

45-
if configuration.retries is not None:
46-
addition_pool_args["retries"] = configuration.retries
68+
if configuration.enable_retry:
69+
retries = ClientRetry(
70+
total=configuration.max_retries,
71+
backoff_factor=configuration.retry_backoff_factor,
72+
)
73+
addition_pool_args["retries"] = retries
4774

4875
if configuration.socket_options is not None:
4976
addition_pool_args["socket_options"] = configuration.socket_options

0 commit comments

Comments
 (0)