Skip to content

Commit 619bb46

Browse files
committed
Resolve pending requested changes
1 parent d373b05 commit 619bb46

File tree

5 files changed

+65
-90
lines changed

5 files changed

+65
-90
lines changed

msgraph/core/_graph_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
'scopes',
1313

1414
# Retry Options
15-
'retry_total',
15+
'max_retries',
1616
'retry_backoff_factor',
1717
'retry_backoff_max',
1818
'retry_time_limit',

msgraph/core/middleware/retry.py

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,38 @@ class RetryHandler(BaseMiddleware):
1313
retry policy for all requests
1414
"""
1515

16-
DEFAULT_TOTAL_RETRIES = 3
17-
MAX_TOTAL_RETRIES = 10
16+
DEFAULT_MAX_RETRIES = 3
17+
MAX_RETRIES = 10
18+
DEFAULT_DELAY = 3
19+
MAX_DELAY = 180
1820
DEFAULT_BACKOFF_FACTOR = 0.5
19-
DEFAULT_RETRY_TIME_LIMIT = 180
2021
MAXIMUM_BACKOFF = 120
21-
_DEFAULT_RETRY_CODES = {429, 503, 504}
22+
_DEFAULT_RETRY_STATUS_CODES = {429, 503, 504}
2223

2324
def __init__(self, **kwargs):
2425
super().__init__()
25-
self.total_retries: int = min(
26-
kwargs.pop('retry_total', self.DEFAULT_TOTAL_RETRIES), self.MAX_TOTAL_RETRIES
26+
self.max_retries: int = min(
27+
kwargs.pop('max_retries', self.DEFAULT_MAX_RETRIES), self.MAX_RETRIES
2728
)
2829
self.backoff_factor: float = kwargs.pop('retry_backoff_factor', self.DEFAULT_BACKOFF_FACTOR)
2930
self.backoff_max: int = kwargs.pop('retry_backoff_max', self.MAXIMUM_BACKOFF)
30-
self.timeout: int = kwargs.pop('retry_time_limit', self.DEFAULT_RETRY_TIME_LIMIT)
31+
self.timeout: int = kwargs.pop('retry_time_limit', self.MAX_DELAY)
3132

3233
status_codes: [int] = kwargs.pop('retry_on_status_codes', [])
3334

34-
self._retry_on_status_codes = set(status_codes) | self._DEFAULT_RETRY_CODES
35-
self._allowed_methods = frozenset(
35+
self._retry_on_status_codes: set = set(status_codes) | self._DEFAULT_RETRY_STATUS_CODES
36+
self._allowed_methods: set = frozenset(
3637
['HEAD', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
3738
)
38-
self._respect_retry_after_header = True
39+
self._respect_retry_after_header: bool = True
3940

4041
@classmethod
4142
def disable_retries(cls):
4243
"""
4344
Disable retries by setting retry_total to zero.
4445
retry_total takes precedence over all other counts.
4546
"""
46-
return cls(retry_total=0)
47+
return cls(max_retries=0)
4748

4849
def get_retry_options(self, middleware_control):
4950
"""
@@ -53,10 +54,7 @@ def get_retry_options(self, middleware_control):
5354
if middleware_control:
5455
return {
5556
'total':
56-
min(
57-
middleware_control.get('retry_total', self.total_retries),
58-
self.MAX_TOTAL_RETRIES
59-
),
57+
min(middleware_control.get('max_retries', self.max_retries), self.MAX_RETRIES),
6058
'backoff':
6159
middleware_control.get('retry_backoff_factor', self.backoff_factor),
6260
'max_backoff':
@@ -65,12 +63,12 @@ def get_retry_options(self, middleware_control):
6563
middleware_control.get('retry_time_limit', self.timeout),
6664
'retry_codes':
6765
set(middleware_control.get('retry_on_status_codes', self._retry_on_status_codes))
68-
| set(self._DEFAULT_RETRY_CODES),
66+
| set(self._DEFAULT_RETRY_STATUS_CODES),
6967
'methods':
7068
self._allowed_methods,
7169
}
7270
return {
73-
'total': self.total_retries,
71+
'total': self.max_retries,
7472
'backoff': self.backoff_factor,
7573
'max_backoff': self.backoff_max,
7674
'timeout': self.timeout,
@@ -83,26 +81,32 @@ def send(self, request, **kwargs):
8381
Sends the http request object to the next middleware or retries the request if necessary.
8482
"""
8583
retry_options = self.get_retry_options(request.context.middleware_control)
86-
retry_active = True
87-
absolute_time_limit = retry_options['timeout']
84+
absolute_time_limit = min(retry_options['timeout'], self.MAX_DELAY)
8885
response = None
8986
retry_count = 0
87+
retry_valid = True
9088

91-
while retry_active:
89+
while retry_valid:
9290
try:
9391
start_time = time.time()
94-
request.headers.update({'Retry-Attempt': '{}'.format(retry_count)})
92+
request.headers.update({'retry-attempt': '{}'.format(retry_count)})
9593
response = super().send(request, **kwargs)
96-
# Check if the request needs to be retried based on the response
94+
# Check if the request needs to be retried based on the response method
95+
# and status code
9796
if self.should_retry(retry_options, response):
98-
retry_active = self.increment_counter(retry_options)
97+
# check that max retries has not been hit
98+
retry_valid = self.check_retry_valid(retry_options, retry_count)
99+
99100
# Get the delay time between retries
100-
sleep_time = self.get_sleep_time(retry_options, retry_count, response)
101-
if retry_active and sleep_time < absolute_time_limit:
102-
time.sleep(sleep_time)
101+
delay = self.get_delay_time(retry_options, retry_count, response)
102+
103+
if retry_valid and delay < absolute_time_limit:
104+
time.sleep(delay)
103105
end_time = time.time()
104106
absolute_time_limit -= (end_time - start_time)
107+
# increment the count for retries
105108
retry_count += 1
109+
106110
continue
107111
break
108112
except Exception as error:
@@ -142,35 +146,29 @@ def _is_request_payload_buffered(self, response):
142146
return False
143147
return True
144148

145-
def increment_counter(self, retry_options):
149+
def check_retry_valid(self, retry_options, retry_count):
146150
"""
147-
Increment the retry counters on every valid retry
151+
Check that the max retries limit has not been hit
148152
"""
149-
if self.retries_exhausted(retry_options):
150-
return False
151-
retry_options['total'] -= 1
152-
return True
153-
154-
def retries_exhausted(self, retry_options):
155-
retries_remaining = retry_options['total']
156-
if retries_remaining <= 0:
153+
if retry_count < retry_options['total']:
157154
return True
158155
return False
159156

160-
def get_sleep_time(self, retry_options, retry_count, response=None):
157+
def get_delay_time(self, retry_options, retry_count, response=None):
161158
"""
162-
Get the time in seconds to sleep between retry attempts.
159+
Get the time in seconds to delay between retry attempts.
163160
Respects a retry-after header in the response if provided
164161
If no retry-after response header, it defaults to exponential backoff
165162
"""
166163
retry_after = self._get_retry_after(response)
167164
if retry_after:
168165
return retry_after
169-
return self._get_sleep_time_exp_backoff(retry_options, retry_count)
166+
return self._get_delay_time_exp_backoff(retry_options, retry_count)
170167

171-
def _get_sleep_time_exp_backoff(self, retry_options, retry_count):
168+
def _get_delay_time_exp_backoff(self, retry_options, retry_count):
172169
"""
173-
Get time to sleep based on exponential backoff value.
170+
Get time in seconds to delay between retry attempts based on an exponential
171+
backoff value.
174172
"""
175173
exp_backoff_value = retry_options['backoff'] * +(2**(retry_count - 1))
176174
backoff_value = exp_backoff_value + (random.randint(0, 1000) / 1000)
@@ -182,7 +180,7 @@ def _get_retry_after(self, response):
182180
"""
183181
Check if retry-after is specified in the response header and get the value
184182
"""
185-
retry_after = response.headers.get("Retry-After")
183+
retry_after = response.headers.get("retry-after")
186184
if retry_after:
187185
return self._parse_retry_after(retry_after)
188186
return None

samples/retry_handler_samples.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def sample_http_client_with_custom_retry_defaults():
2424
request using the client."""
2525

2626
client = HTTPClientFactory().create_with_default_middleware(
27-
browser_credential, retry_total=5, retry_backoff_factor=0.1, retry_time_limit=60
27+
browser_credential, max_retries=5, retry_backoff_factor=0.1, retry_time_limit=60
2828
)
2929
result = client.get('/me/messages', scopes=['mail.read'])
3030
pprint(result.json())
@@ -35,7 +35,7 @@ def sample_graph_client_with_custom_retry_defaults():
3535
handler. These defaults will be used for every subsequent request using the client unless
3636
per request options are passed"""
3737

38-
client = GraphClient(credential=browser_credential, retry_total=2, retry_backoff_factor=0.5)
38+
client = GraphClient(credential=browser_credential, max_retries=2, retry_backoff_factor=0.5)
3939
result = client.get('/me/messages', scopes=['mail.read'])
4040
pprint(result.json())
4141

tests/integration/test_retry_middleware.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def test_no_retry_success_response(graph_client):
2525
)
2626

2727
assert response.status_code == 200
28-
assert response.request.headers["Retry-Attempt"] == "0"
28+
assert response.request.headers["retry-attempt"] == "0"
2929

3030

3131
def test_valid_retry_429(graph_client):
@@ -35,7 +35,7 @@ def test_valid_retry_429(graph_client):
3535
response = graph_client.get('https://httpbin.org/status/429')
3636

3737
assert response.status_code == 429
38-
assert response.request.headers["Retry-Attempt"] == "3"
38+
assert response.request.headers["retry-attempt"] == "3"
3939

4040

4141
def test_valid_retry_503(graph_client):
@@ -45,7 +45,7 @@ def test_valid_retry_503(graph_client):
4545
response = graph_client.get('https://httpbin.org/status/503')
4646

4747
assert response.status_code == 503
48-
assert response.request.headers["Retry-Attempt"] == "3"
48+
assert response.request.headers["retry-attempt"] == "3"
4949

5050

5151
def test_valid_retry_504(graph_client):
@@ -55,7 +55,7 @@ def test_valid_retry_504(graph_client):
5555
response = graph_client.get('https://httpbin.org/status/504')
5656

5757
assert response.status_code == 504
58-
assert response.request.headers["Retry-Attempt"] == "3"
58+
assert response.request.headers["retry-attempt"] == "3"
5959

6060

6161
def test_request_specific_options_override_default(graph_client):
@@ -64,9 +64,9 @@ def test_request_specific_options_override_default(graph_client):
6464
the default options.
6565
"""
6666
response_1 = graph_client.get('https://httpbin.org/status/429')
67-
response_2 = graph_client.get('https://httpbin.org/status/503', retry_total=2)
67+
response_2 = graph_client.get('https://httpbin.org/status/503', max_retries=2)
6868
response_3 = graph_client.get('https://httpbin.org/status/504')
69-
response_4 = graph_client.get('https://httpbin.org/status/429', retry_total=1)
69+
response_4 = graph_client.get('https://httpbin.org/status/429', max_retries=1)
7070

7171
assert response_1.status_code == 429
7272
assert response_1.request.headers["Retry-Attempt"] == "3"
@@ -88,4 +88,4 @@ def test_retries_time_limit(graph_client):
8888

8989
assert response.status_code == 503
9090
headers = response.request.headers
91-
assert headers["Retry-Attempt"] == "0"
91+
assert headers["retry-attempt"] == "0"

tests/unit/test_retry_handler.py

Lines changed: 15 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,30 @@ def test_no_config():
1616
Test that default values are used if no custom confguration is passed
1717
"""
1818
retry_handler = RetryHandler()
19-
assert retry_handler.total_retries == retry_handler.DEFAULT_TOTAL_RETRIES
20-
assert retry_handler.timeout == retry_handler.DEFAULT_RETRY_TIME_LIMIT
19+
assert retry_handler.max_retries == retry_handler.DEFAULT_MAX_RETRIES
20+
assert retry_handler.timeout == retry_handler.MAX_DELAY
2121
assert retry_handler.backoff_max == retry_handler.MAXIMUM_BACKOFF
2222
assert retry_handler.backoff_factor == retry_handler.DEFAULT_BACKOFF_FACTOR
2323
assert retry_handler._allowed_methods == frozenset(
2424
['HEAD', 'GET', 'PUT', 'POST', 'PATCH', 'DELETE', 'OPTIONS']
2525
)
2626
assert retry_handler._respect_retry_after_header
27-
assert retry_handler._retry_on_status_codes == retry_handler._DEFAULT_RETRY_CODES
27+
assert retry_handler._retry_on_status_codes == retry_handler._DEFAULT_RETRY_STATUS_CODES
2828

2929

3030
def test_custom_config():
3131
"""
3232
Test that default configuration is overrriden if custom configuration is provided
3333
"""
3434
retry_handler = RetryHandler(
35-
retry_total=10,
35+
max_retries=10,
3636
retry_backoff_factor=0.2,
3737
retry_backoff_max=200,
3838
retry_time_limit=100,
3939
retry_on_status_codes=[502, 503]
4040
)
4141

42-
assert retry_handler.total_retries == 10
42+
assert retry_handler.max_retries == 10
4343
assert retry_handler.timeout == 100
4444
assert retry_handler.backoff_max == 200
4545
assert retry_handler.backoff_factor == 0.2
@@ -52,10 +52,9 @@ def test_disable_retries():
5252
"""
5353
retry_handler = RetryHandler()
5454
retry_handler = retry_handler.disable_retries()
55-
assert retry_handler.total_retries == 0
55+
assert retry_handler.max_retries == 0
5656
retry_options = retry_handler.get_retry_options({})
57-
assert not retry_handler.increment_counter(retry_options)
58-
assert retry_handler.retries_exhausted(retry_options)
57+
assert not retry_handler.check_retry_valid(retry_options, 0)
5958

6059

6160
@responses.activate
@@ -128,46 +127,24 @@ def test_is_request_payload_buffered_invalid():
128127
assert not retry_handler._is_request_payload_buffered(response)
129128

130129

131-
def test_retries_exhausted():
130+
def test_check_retry_valid():
132131
"""
133-
Test that the retries exhausted method works correctly when total_retries is greater than zero
132+
Test that a retry is valid if the maximum number of retries has not been reached
134133
"""
135134
retry_handler = RetryHandler()
136135
settings = retry_handler.get_retry_options({})
137136

138-
assert not retry_handler.retries_exhausted(settings)
137+
assert retry_handler.check_retry_valid(settings, 0)
139138

140139

141-
def test_retries_exhausted_zero_total():
140+
def test_check_retry_valid_no_retries():
142141
"""
143-
Test that the retries exhausted method works correctly when total retries are set to zero
142+
Test that a retry is not valid if maximum number of retries has been reached
144143
"""
145-
retry_handler = RetryHandler(retry_total=0)
144+
retry_handler = RetryHandler(max_retries=2)
146145
settings = retry_handler.get_retry_options({})
147146

148-
assert retry_handler.retries_exhausted(settings)
149-
150-
151-
def test_increment_counter():
152-
"""
153-
Test that retry counter is incremented on a valid retry
154-
"""
155-
retry_handler = RetryHandler()
156-
settings = retry_handler.get_retry_options({})
157-
158-
assert retry_handler.increment_counter(settings)
159-
assert settings['total'] == retry_handler.DEFAULT_TOTAL_RETRIES - 1
160-
161-
162-
def test_increment_counter_invalid_retry():
163-
"""
164-
Test that retry counter is not incremented when a retry is not valid
165-
"""
166-
retry_handler = RetryHandler(retry_total=0)
167-
settings = retry_handler.get_retry_options({})
168-
169-
assert not retry_handler.increment_counter(settings)
170-
assert settings['total'] == 0
147+
assert not retry_handler.check_retry_valid(settings, 2)
171148

172149

173150
@responses.activate
@@ -203,7 +180,7 @@ def test_get_retry_after_http_date():
203180
"""
204181
timevalue = time() + 120
205182
http_date = formatdate(timeval=timevalue, localtime=False, usegmt=True)
206-
responses.add(responses.GET, BASE_URL, headers={'Retry-After': f'{http_date}'}, status=503)
183+
responses.add(responses.GET, BASE_URL, headers={'retry-after': f'{http_date}'}, status=503)
207184
response = requests.get(BASE_URL)
208185

209186
retry_handler = RetryHandler()

0 commit comments

Comments
 (0)