Recipes#

Retry Web Requests#

Want to retry web requests selectively, e.g. server-side HTTP errors? Wrap requests.request with the retry_decorator:

@retry_decorator(exceptions=(requests.HTTPError))
def request_with_retry(
    method: str, url: str, **kwargs: Any
) -> requests.models.Response:
    """
    Wrapper function for `requests.request` that retries
    server-side http errors with exponential backoff and jitter.
    For more info:
    https://docs.python-requests.org/en/latest/api/#requests.request
    """
    response = requests.request(method, url, **kwargs)
    if 500 <= response.status_code < 600:
        response.raise_for_status()
    return response

Retrying a property, staticmethod, or classmethod#

Retrying a method is simple enough, but what about fancier object-oriented code? The @retry_decorator goes on the inside:

>>> class Class:
...     @classmethod
...     @retry_decorator(exceptions=ConnectionError)
...     def cls_meth(cls):
...         return get_ip()
...
...     @staticmethod
...     @retry_decorator(exceptions=ConnectionError)
...     def stat_meth():
...         return get_ip()
...
...     @property
...     @retry_decorator(exceptions=ConnectionError)
...     def prop(self):
...         return get_ip()
...
>>> Class.cls_meth()
WARNING: Function threw exception below on try 1, retrying in 0.844422 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}
>>> obj = Class()
>>> obj.stat_meth()
WARNING: Function threw exception below on try 1, retrying in 0.420572 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}
>>> obj.prop
WARNING: Function threw exception below on try 1, retrying in 0.511275 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

Constant Backoff#

Not a believer in exponential, jittered backoff, and would prefer a constant backoff duration? Set exponential=1 and jitter=False:

>>> retry(get_ip, jitter=False, exponential=1, exceptions=ConnectionError)
WARNING: Function threw exception below on try 1, retrying in 1 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
WARNING: Function threw exception below on try 2, retrying in 1 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

Instantaneous Retries#

Feeling impatient, but still want the logging benefits of tubthumper? Set init_backoff=0:

>>> retry(get_ip, init_backoff=0, exceptions=ConnectionError)
WARNING: Function threw exception below on try 1, retrying in 0 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
WARNING: Function threw exception below on try 2, retrying in 0 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
{'ip': '8.8.8.8'}

Retrying a Generator Function#

Retrying a generator has surprising results:

>>> @retry_decorator(exceptions=ConnectionError)
... def gen_func():
...     response = get_ip()
...     for val in response["ip"].split('.'):
...         yield val
...
>>> for thing in gen_func():
...     print(thing)
...
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com

This is because, when a generator function is called, instead of executing the function, Python pauses it, returning a generator object:

>>> gen_func()
<generator object gen_func at 0x...>

To retry the guts of a generator function, you’ll want to put the retry inside:

>>> def gen_func():
...     response = retry(get_ip, exceptions=ConnectionError)
...     for val in response["ip"].split('.'):
...         yield val
...
>>> for thing in gen_func():
...     print(thing)
...
WARNING: Function threw exception below on try 1, retrying in 0.476597 seconds
Traceback (most recent call last):
  ...
requests.exceptions.ConnectionError: http://ip.jsontest.com
8
8
8
8