Skip to content

Commit 7fd3e64

Browse files
[Tables] wrapper for 429s (Azure#18996)
This will allow us to remove all sleeps in the code
1 parent ee209c8 commit 7fd3e64

17 files changed

+185
-249
lines changed

sdk/tables/azure-data-tables/tests/_shared/asynctestcase.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@ async def _tear_down(self):
6767
if is_live():
6868
async for table in self.ts.list_tables():
6969
await self.ts.delete_table(table.name)
70-
if self.ts._cosmos_endpoint:
71-
self.sleep(SLEEP_DELAY)
7270
self.test_tables = []
7371
await self.ts.close()
7472

sdk/tables/azure-data-tables/tests/_shared/testcase.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,10 @@ def _create_table(self, ts, prefix=TEST_TABLE_PREFIX, table_list=None):
9999
return table
100100

101101
def _delete_all_tables(self, ts):
102-
for table in ts.list_tables():
103-
ts.delete_table(table.name)
102+
if self.is_live:
103+
for table in ts.list_tables():
104+
ts.delete_table(table.name)
105+
self.sleep(10)
104106

105107
def _create_pk_rk(self, pk, rk):
106108
try:
@@ -354,8 +356,6 @@ def _tear_down(self):
354356
if is_live():
355357
self._delete_all_tables(self.ts)
356358
self.test_tables = []
357-
if self.ts._cosmos_endpoint:
358-
self.sleep(SLEEP_DELAY)
359359
self.ts.close()
360360

361361
def _create_query_table(self, entity_count):
Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import functools
1+
import time
22

33
from azure.core.credentials import AzureNamedKeyCredential
4+
from azure.core.exceptions import HttpResponseError
5+
6+
from devtools_testutils import is_live
7+
48
from preparers import CosmosPreparer, TablesPreparer, trim_kwargs_from_test_function
59

6-
def cosmos_decorator_async(func, **kwargs):
710

11+
def cosmos_decorator_async(func, **kwargs):
812
@CosmosPreparer()
913
async def wrapper(*args, **kwargs):
1014
key = kwargs.pop("tables_primary_cosmos_account_key")
@@ -14,7 +18,7 @@ async def wrapper(*args, **kwargs):
1418
kwargs["tables_primary_cosmos_account_key"] = key
1519
kwargs["tables_cosmos_account_name"] = name
1620

17-
trimmed_kwargs = {k:v for k, v in kwargs.items()}
21+
trimmed_kwargs = {k: v for k, v in kwargs.items()}
1822
trim_kwargs_from_test_function(func, trimmed_kwargs)
1923

2024
return await func(*args, **trimmed_kwargs)
@@ -23,7 +27,6 @@ async def wrapper(*args, **kwargs):
2327

2428

2529
def tables_decorator_async(func, **kwargs):
26-
2730
@TablesPreparer()
2831
async def wrapper(*args, **kwargs):
2932
key = kwargs.pop("tables_primary_storage_account_key")
@@ -33,9 +36,28 @@ async def wrapper(*args, **kwargs):
3336
kwargs["tables_primary_storage_account_key"] = key
3437
kwargs["tables_storage_account_name"] = name
3538

36-
trimmed_kwargs = {k:v for k, v in kwargs.items()}
39+
trimmed_kwargs = {k: v for k, v in kwargs.items()}
3740
trim_kwargs_from_test_function(func, trimmed_kwargs)
3841

39-
return await func(*args, **trimmed_kwargs)
42+
EXPONENTIAL_BACKOFF = 1.5
43+
RETRY_COUNT = 0
44+
45+
try:
46+
return await func(*args, **trimmed_kwargs)
47+
except HttpResponseError as exc:
48+
if exc.status_code != 429:
49+
raise
50+
print("Retrying: {} {}".format(RETRY_COUNT, EXPONENTIAL_BACKOFF))
51+
while RETRY_COUNT < 6:
52+
if is_live():
53+
time.sleep(EXPONENTIAL_BACKOFF)
54+
try:
55+
return await func(*args, **trimmed_kwargs)
56+
except HttpResponseError as exc:
57+
print("Retrying: {} {}".format(RETRY_COUNT, EXPONENTIAL_BACKOFF))
58+
EXPONENTIAL_BACKOFF **= 2
59+
RETRY_COUNT += 1
60+
if exc.status_code != 429 or RETRY_COUNT >= 6:
61+
raise
4062

4163
return wrapper
Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,41 @@
11
import functools
22
import inspect
3+
import time
34

45
from azure.core.credentials import AzureNamedKeyCredential
5-
from devtools_testutils import PowerShellPreparer
6+
from azure.core.exceptions import HttpResponseError
7+
from devtools_testutils import PowerShellPreparer, is_live
68

79
CosmosPreparer = functools.partial(
8-
PowerShellPreparer, "tables",
10+
PowerShellPreparer,
11+
"tables",
912
tables_cosmos_account_name="fake_cosmos_account",
10-
tables_primary_cosmos_account_key="fakecosmosaccountkey"
13+
tables_primary_cosmos_account_key="fakecosmosaccountkey",
1114
)
1215

1316
TablesPreparer = functools.partial(
14-
PowerShellPreparer, "tables",
17+
PowerShellPreparer,
18+
"tables",
1519
tables_storage_account_name="fake_table_account",
16-
tables_primary_storage_account_key="faketablesaccountkey"
20+
tables_primary_storage_account_key="faketablesaccountkey",
1721
)
1822

1923

2024
def trim_kwargs_from_test_function(fn, kwargs):
2125
# the next function is the actual test function. the kwargs need to be trimmed so
2226
# that parameters which are not required will not be passed to it.
23-
if not getattr(fn, '__is_preparer', False):
27+
if not getattr(fn, "__is_preparer", False):
2428
try:
2529
args, _, kw, _, _, _, _ = inspect.getfullargspec(fn)
2630
except AttributeError:
27-
args, _, kw, _ = inspect.getargspec(fn) # pylint: disable=deprecated-method
31+
args, _, kw, _ = inspect.getargspec(fn) # pylint: disable=deprecated-method
2832
if kw is None:
2933
args = set(args)
3034
for key in [k for k in kwargs if k not in args]:
3135
del kwargs[key]
3236

3337

3438
def tables_decorator(func, **kwargs):
35-
3639
@TablesPreparer()
3740
def wrapper(*args, **kwargs):
3841
key = kwargs.pop("tables_primary_storage_account_key")
@@ -42,7 +45,7 @@ def wrapper(*args, **kwargs):
4245
kwargs["tables_primary_storage_account_key"] = key
4346
kwargs["tables_storage_account_name"] = name
4447

45-
trimmed_kwargs = {k:v for k, v in kwargs.items()}
48+
trimmed_kwargs = {k: v for k, v in kwargs.items()}
4649
trim_kwargs_from_test_function(func, trimmed_kwargs)
4750

4851
func(*args, **trimmed_kwargs)
@@ -51,7 +54,6 @@ def wrapper(*args, **kwargs):
5154

5255

5356
def cosmos_decorator(func, **kwargs):
54-
5557
@CosmosPreparer()
5658
def wrapper(*args, **kwargs):
5759
key = kwargs.pop("tables_primary_cosmos_account_key")
@@ -61,10 +63,28 @@ def wrapper(*args, **kwargs):
6163
kwargs["tables_primary_cosmos_account_key"] = key
6264
kwargs["tables_cosmos_account_name"] = name
6365

64-
trimmed_kwargs = {k:v for k, v in kwargs.items()}
66+
trimmed_kwargs = {k: v for k, v in kwargs.items()}
6567
trim_kwargs_from_test_function(func, trimmed_kwargs)
6668

67-
func(*args, **trimmed_kwargs)
69+
EXPONENTIAL_BACKOFF = 1
70+
RETRY_COUNT = 0
6871

69-
return wrapper
72+
try:
73+
return func(*args, **trimmed_kwargs)
74+
except HttpResponseError as exc:
75+
if exc.status_code != 429:
76+
raise
77+
print("Retrying: {} {}".format(RETRY_COUNT, EXPONENTIAL_BACKOFF))
78+
while RETRY_COUNT < 6:
79+
if is_live():
80+
time.sleep(EXPONENTIAL_BACKOFF)
81+
try:
82+
return func(*args, **trimmed_kwargs)
83+
except HttpResponseError as exc:
84+
print("Retrying: {} {}".format(RETRY_COUNT, EXPONENTIAL_BACKOFF))
85+
EXPONENTIAL_BACKOFF **= 2
86+
RETRY_COUNT += 1
87+
if exc.status_code != 429 or RETRY_COUNT >= 6:
88+
raise
7089

90+
return wrapper

0 commit comments

Comments
 (0)