Repository: hikari-py/hikari (python)

Issue:
Toggle to allow n amount of automatic-retries for internal server errors
### Summary
For this feature request I propose that an option be added to the rest client to allow for automatic retries on some if not all 5xx error codes either unlimited or up to a certain number of retries.

### Problem
Right now the behaviour of raising on 500s can lead to the code consuming the REST client having to put a ton of messy retry logic around requests to handle both 500s and ratelimited errors.

### Ideal implementation
Add an argument to the rest client and relevant bot clients which defaults to idk maybe 5 retires and allows for specify the amount of times the lib should automatically retry on a 5xx error code (with it likely using an exponential back off strategy between requests) which can be set to 0 to signify that it should always raise. Alternatively this argument could be a boolean  argument where true signifies unlimited retries on 5xx and False signifies always raise

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [ ] I have searched the issue tracker and have made sure it's not a duplicate.
      If it is a follow up of another issue, I have specified it.


Pull request description:
Feature/747 retry request on 5xx error
Added retry feature for HTTP requests. If a request fails with a `5xx` status, it will retry for `n` amount of times. By default, the amount of retries is 5. It is passed as an argument to the `RESTClient` constructor as `max_retries`.

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [x] I have run `nox` and all the pipelines have passed.
- [ ] I have made unittests according to the code I have added/modified/deleted.

### Related issues
Closes #747 


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 27d14feb..2ccd9da6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,8 +13,7 @@ jobs:
       fail-fast: false
       matrix:
         os: [ubuntu-20.04, macos-10.15, windows-2019]
-        # CPython 3.10-dev temporarily removed due to https://github.com/python/typing/issues/865
-        python-version: [3.8, 3.9]
+        python-version: [3.8, 3.9, 3.10-dev]
 
     runs-on: ${{ matrix.os }}
 
diff --git a/.gitignore b/.gitignore
index c46fd678..37b24446 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ venv*/
 # CI pages, coverage reports, etc
 public/
 node_modules/
+package.json
 package-lock.json
 
 # PyCharm/IntelliJ-generated files
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 319cc462..b6593585 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,9 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
  - Helpful methods to Application object.
  - Missing Audit Log change keys.
  - Sticker Audit Log event types.
+ - Retries for HTTP requests which fail with `5xx` status codes.
 
 ### Changed
  - Fix errors in rest sticker method docstrings.
+ - Remove duplicate raise type in api and guilds docstrings.
 
 ### Fixed
  - Handling of interaction models passed to the webhook message endpoints as the "webhook" field.
diff --git a/README.md b/README.md
index 0959289d..ba4008f8 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,16 @@
-[![Hikari PyPI version badge](https://img.shields.io/pypi/v/hikari)](https://pypi.org/project/hikari)
-[![Github Actions Status badge](https://github.com/hikari-py/hikari/actions/workflows/ci.yml/badge.svg?branch=master&event=push)](https://github.com/hikari-py/hikari/actions)
-[![Supported Python versions badge](https://img.shields.io/pypi/pyversions/hikari)](https://pypi.org/project/hikari)
-[![Maintainability badge](https://api.codeclimate.com/v1/badges/f95070b25136a69b0589/maintainability)](https://codeclimate.com/github/hikari-py/hikari/maintainability)
-[![Test Coverage badge](https://api.codeclimate.com/v1/badges/f95070b25136a69b0589/test_coverage)](https://codeclimate.com/github/hikari-py/hikari/test_coverage)
-[![Open Pull Requests badge](https://img.shields.io/github/issues-pr/hikari-py/hikari)](https://github.com/hikari-py/hikari/pulls)
-[![Issues badge](https://img.shields.io/github/issues-raw/hikari-py/hikari)](https://github.com/hikari-py/hikari/issues)
-[![This project uses Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://pypi.org/project/black/)
-[![This project uses MyPy](http://www.mypy-lang.org/static/mypy_badge.svg)](https://pypi.org/project/mypy/)
-[![Documentation badge](https://img.shields.io/badge/documentation-up-00FF00.svg)](https://hikari-py.github.io/hikari/hikari)
-
-[![Discord](https://discord.com/api/guilds/574921006817476608/widget.png?style=banner2)](https://discord.gg/Jx4cNGG)
-
-# hikari
+<h1 align="center">hikari</h1>
+<p align="center">
+<a href="https://pypi.org/project/hikari"><img height="20" alt="PyPI version" src="https://img.shields.io/pypi/v/hikari"></a>
+<a href="https://pypi.org/project/hikari"><img height="20" alt="Supported python versions" src="https://img.shields.io/pypi/pyversions/hikari"></a>
+<br>
+<a href="https://github.com/hikari-py/hikari/actions"><img height="20" alt="CI status" src="https://github.com/hikari-py/hikari/actions/workflows/ci.yml/badge.svg?branch=master&event=push"></a>
+<a href="https://pypi.org/project/mypy/"><img height="20" alt="Mypy badge" src="http://www.mypy-lang.org/static/mypy_badge.svg"></a>
+<a href="https://pypi.org/project/black"><img height="20" alt="Black badge" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
+<a href="https://codeclimate.com/github/hikari-py/hikari/test_coverage"><img height="20" alt="Test coverage" src="https://api.codeclimate.com/v1/badges/f95070b25136a69b0589/test_coverage"></a>
+<br>
+<a href="https://discord.gg/Jx4cNGG"><img height="20" alt="Discord invite" src="https://discord.com/api/guilds/574921006817476608/widget.png"></a>
+<a href="https://hikari-py.github.io/hikari/hikari"><img height="20" alt="Documentation status" src="https://img.shields.io/badge/documentation-up-00FF00.svg"></a>
+</p>
 
 An opinionated, static typed Discord microframework for Python3 and asyncio that supports Discord's V8 REST API and
 Gateway.
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 26e4e1c3..28589b60 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -6,7 +6,7 @@
 mock==4.0.3
 
 # Py.test stuff.
-pytest==6.2.4
+pytest==6.2.5
 pytest-asyncio==0.15.1
 pytest-cov==2.12.1
 pytest-randomly==3.10.1
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index 90186a9a..1e508a68 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -177,9 +177,6 @@ async def fetch_channel(
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
diff --git a/hikari/guilds.py b/hikari/guilds.py
index 47fc6a7b..3cba854f 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -304,9 +304,6 @@ async def fetch_channel(self) -> typing.Optional[channels_.GuildChannel]:
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
@@ -3021,9 +3018,6 @@ async def fetch_widget_channel(self) -> typing.Optional[channels_.GuildChannel]:
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
@@ -3061,9 +3055,6 @@ async def fetch_afk_channel(self) -> typing.Optional[channels_.GuildVoiceChannel
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
@@ -3102,9 +3093,6 @@ async def fetch_system_channel(self) -> typing.Optional[channels_.GuildTextChann
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
@@ -3144,9 +3132,6 @@ async def fetch_rules_channel(self) -> typing.Optional[channels_.GuildTextChanne
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
@@ -3187,9 +3172,6 @@ async def fetch_public_updates_channel(self) -> typing.Optional[channels_.GuildT
         hikari.errors.RateLimitTooLongError
             Raised in the event that a rate limit occurs that is
             longer than `max_rate_limit` when making a request.
-        hikari.errors.RateLimitTooLongError
-            Raised in the event that a rate limit occurs that is
-            longer than `max_rate_limit` when making a request.
         hikari.errors.RateLimitedError
             Usually, Hikari will handle and retry on hitting
             rate-limits automatically. This includes most bucket-specific
diff --git a/hikari/impl/bot.py b/hikari/impl/bot.py
index ab31b18a..9c5040c4 100644
--- a/hikari/impl/bot.py
+++ b/hikari/impl/bot.py
@@ -167,6 +167,9 @@ class GatewayBot(traits.GatewayBotAware):
         Note that this only applies to the REST API component that communicates
         with Discord, and will not affect sharding or third party HTTP endpoints
         that may be in use.
+    max_retries : typing.Optional[builtins.int]
+        Maximum number of times a request will be retried if
+        it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`.
     proxy_settings : typing.Optional[config.ProxySettings]
         Custom proxy settings to use with network-layer logic
         in your application to get through an HTTP-proxy.
@@ -242,6 +245,7 @@ def __init__(
         intents: intents_.Intents = intents_.Intents.ALL_UNPRIVILEGED,
         logs: typing.Union[None, int, str, typing.Dict[str, typing.Any]] = "INFO",
         max_rate_limit: float = 300,
+        max_retries: int = 3,
         proxy_settings: typing.Optional[config.ProxySettings] = None,
         rest_url: typing.Optional[str] = None,
     ) -> None:
@@ -284,6 +288,7 @@ def __init__(
             max_rate_limit=max_rate_limit,
             proxy_settings=self._proxy_settings,
             rest_url=rest_url,
+            max_retries=max_retries,
             token=token,
             token_type=applications.TokenType.BOT,
         )
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 71729571..9fa42e78 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -114,6 +114,8 @@
 _X_RATELIMIT_LIMIT_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Limit")
 _X_RATELIMIT_REMAINING_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Remaining")
 _X_RATELIMIT_RESET_AFTER_HEADER: typing.Final[str] = sys.intern("X-RateLimit-Reset-After")
+_RETRY_ERROR_CODES: typing.Final[typing.Set[int]] = {500, 502, 503, 504}
+_MAX_BACKOFF_DURATION: typing.Final[int] = 16
 
 
 class ClientCredentialsStrategy(rest_api.TokenStrategy):
@@ -288,6 +290,9 @@ class RESTApp(traits.ExecutorAware):
         rate limits.
 
         Defaults to five minutes if unspecified.
+    max_retries : typing.Optional[builtins.int]
+        Maximum number of times a request will be retried if
+        it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`.
     proxy_settings : typing.Optional[hikari.config.ProxySettings]
         Proxy settings to use. If `builtins.None` then no proxy configuration
         will be used.
@@ -304,6 +309,7 @@ class RESTApp(traits.ExecutorAware):
         "_executor",
         "_http_settings",
         "_max_rate_limit",
+        "_max_retries",
         "_proxy_settings",
         "_url",
     )
@@ -314,6 +320,7 @@ def __init__(
         executor: typing.Optional[concurrent.futures.Executor] = None,
         http_settings: typing.Optional[config.HTTPSettings] = None,
         max_rate_limit: float = 300,
+        max_retries: int = 3,
         proxy_settings: typing.Optional[config.ProxySettings] = None,
         url: typing.Optional[str] = None,
     ) -> None:
@@ -321,6 +328,7 @@ def __init__(
         self._proxy_settings = config.ProxySettings() if proxy_settings is None else proxy_settings
         self._executor = executor
         self._max_rate_limit = max_rate_limit
+        self._max_retries = max_retries
         self._url = url
 
     @property
@@ -409,6 +417,7 @@ def acquire(
             executor=self._executor,
             http_settings=self._http_settings,
             max_rate_limit=self._max_rate_limit,
+            max_retries=self._max_retries,
             proxy_settings=self._proxy_settings,
             token=token,
             token_type=token_type,
@@ -503,6 +512,9 @@ class RESTClientImpl(rest_api.RESTClient):
 
         This is provided since some endpoints may respond with non-sensible
         rate limits.
+    max_retries : typing.Optional[builtins.int]
+        Maximum number of times a request will be retried if
+        it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`.
     token : typing.Union[builtins.str, builtins.None, hikari.api.rest.TokenStrategy]
         The bot or bearer token. If no token is to be used,
         this can be undefined.
@@ -522,6 +534,7 @@ class RESTClientImpl(rest_api.RESTClient):
     builtins.ValueError
         * If `token_type` is provided when a token strategy is passed for `token`.
         * if `token_type` is left as `builtins.None` when a string is passed for `token`.
+        * If the a value more than 5 is provided for `max_retries`
     """
 
     __slots__: typing.Sequence[str] = (
@@ -531,6 +544,7 @@ class RESTClientImpl(rest_api.RESTClient):
         "_http_settings",
         "_live_attributes",
         "_max_rate_limit",
+        "_max_retries",
         "_proxy_settings",
         "_rest_url",
         "_token",
@@ -549,17 +563,22 @@ def __init__(
         executor: typing.Optional[concurrent.futures.Executor],
         http_settings: config.HTTPSettings,
         max_rate_limit: float,
+        max_retries: int = 3,
         proxy_settings: config.ProxySettings,
         token: typing.Union[str, None, rest_api.TokenStrategy],
         token_type: typing.Union[applications.TokenType, str, None],
         rest_url: typing.Optional[str],
     ) -> None:
+        if max_retries > 5:
+            raise ValueError("A value above 5 was provided for 'max_retries'")
+
         self._cache = cache
         self._entity_factory = entity_factory
         self._executor = executor
         self._http_settings = http_settings
         self._live_attributes: typing.Optional[_LiveAttributes] = None
         self._max_rate_limit = max_rate_limit
+        self._max_retries = max_retries
         self._proxy_settings = proxy_settings
 
         self._token: typing.Union[str, rest_api.TokenStrategy, None] = None
@@ -696,6 +715,11 @@ async def _request(
         headers.put(_X_AUDIT_LOG_REASON_HEADER, reason)
 
         url = compiled_route.create_url(self._rest_url)
+
+        # This is initiated the first time we hit a 5xx error to save memory when nothing goes wrong
+        backoff: typing.Optional[rate_limits.ExponentialBackOff] = None
+        retries_done = 0
+
         while True:
             try:
                 uuid = time.uuid()
@@ -759,6 +783,23 @@ async def _request(
                     real_url = str(response.real_url)
                     raise errors.HTTPError(f"Expected JSON [{response.content_type=}, {real_url=}]")
 
+                # Handling 5xx errors
+                if response.status in _RETRY_ERROR_CODES and retries_done < self._max_retries:
+                    if backoff is None:
+                        backoff = rate_limits.ExponentialBackOff(maximum=_MAX_BACKOFF_DURATION)
+
+                    sleep_time = next(backoff)
+                    _LOGGER.warning(
+                        "Received status %s on request, backing off for %.2fs and retrying. Retries remaining: %s",
+                        response.status,
+                        sleep_time,
+                        self._max_retries - retries_done,
+                    )
+                    retries_done += 1
+
+                    await asyncio.sleep(sleep_time)
+                    raise self._RetryRequest
+
                 can_re_auth = response.status == 401 and not (auth or no_auth or retried)
                 if can_re_auth and isinstance(self._token, rest_api.TokenStrategy):
                     assert token is not None
diff --git a/hikari/impl/rest_bot.py b/hikari/impl/rest_bot.py
index 24d82c45..99d793e1 100644
--- a/hikari/impl/rest_bot.py
+++ b/hikari/impl/rest_bot.py
@@ -149,6 +149,9 @@ class RESTBot(traits.RESTBotAware, interaction_server_.InteractionServer):
         Note that this only applies to the REST API component that communicates
         with Discord, and will not affect sharding or third party HTTP endpoints
         that may be in use.
+    max_retries : typing.Optional[builtins.int]
+        Maximum number of times a request will be retried if
+        it fails with a `5xx` status. Defaults to 3 if set to `builtins.None`.
     proxy_settings : typing.Optional[config.ProxySettings]
         Custom proxy settings to use with network-layer logic
         in your application to get through an HTTP-proxy.
@@ -197,6 +200,7 @@ def __init__(
         http_settings: typing.Optional[config.HTTPSettings] = None,
         logs: typing.Union[None, int, str, typing.Dict[str, typing.Any]] = "INFO",
         max_rate_limit: float = 300.0,
+        max_retries: int = 3,
         proxy_settings: typing.Optional[config.ProxySettings] = None,
         rest_url: typing.Optional[str] = None,
     ) -> None:
@@ -216,6 +220,7 @@ def __init__(
         http_settings: typing.Optional[config.HTTPSettings] = None,
         logs: typing.Union[None, int, str, typing.Dict[str, typing.Any]] = "INFO",
         max_rate_limit: float = 300.0,
+        max_retries: int = 3,
         proxy_settings: typing.Optional[config.ProxySettings] = None,
         rest_url: typing.Optional[str] = None,
     ) -> None:
@@ -234,6 +239,7 @@ def __init__(
         http_settings: typing.Optional[config.HTTPSettings] = None,
         logs: typing.Union[None, int, str, typing.Dict[str, typing.Any]] = "INFO",
         max_rate_limit: float = 300.0,
+        max_retries: int = 3,
         proxy_settings: typing.Optional[config.ProxySettings] = None,
         rest_url: typing.Optional[str] = None,
     ) -> None:
@@ -261,6 +267,7 @@ def __init__(
             executor=self._executor,
             http_settings=self._http_settings,
             max_rate_limit=max_rate_limit,
+            max_retries=max_retries,
             proxy_settings=self._proxy_settings,
             rest_url=rest_url,
             token=token,


Test patch:
diff --git a/tests/hikari/impl/test_bot.py b/tests/hikari/impl/test_bot.py
index 9958cee1..4c7f7cad 100644
--- a/tests/hikari/impl/test_bot.py
+++ b/tests/hikari/impl/test_bot.py
@@ -113,7 +113,12 @@ def bot(
 
         with stack:
             return bot_impl.GatewayBot(
-                "token", executor=executor, http_settings=http_settings, proxy_settings=proxy_settings, intents=intents
+                "token",
+                executor=executor,
+                http_settings=http_settings,
+                proxy_settings=proxy_settings,
+                intents=intents,
+                max_retries=0,
             )
 
     def test_init(self):
@@ -144,6 +149,7 @@ def test_init(self):
                 intents=intents,
                 logs="DEBUG",
                 max_rate_limit=200,
+                max_retries=0,
                 proxy_settings=proxy_settings,
                 rest_url="somewhere.com",
             )
@@ -167,6 +173,7 @@ def test_init(self):
             executor=executor,
             http_settings=bot._http_settings,
             max_rate_limit=200,
+            max_retries=0,
             proxy_settings=bot._proxy_settings,
             rest_url="somewhere.com",
             token="token",
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index 827abba6..43f915db 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -331,6 +331,7 @@ def rest_app():
         executor=None,
         http_settings=mock.Mock(spec_set=config.HTTPSettings),
         max_rate_limit=float("inf"),
+        max_retries=0,
         proxy_settings=mock.Mock(spec_set=config.ProxySettings),
         url="https://some.url",
     )
@@ -370,6 +371,7 @@ def test_acquire(self, rest_app):
             executor=rest_app._executor,
             http_settings=rest_app._http_settings,
             max_rate_limit=float("inf"),
+            max_retries=0,
             proxy_settings=rest_app._proxy_settings,
             token="token",
             token_type="Type",
@@ -394,6 +396,7 @@ def test_acquire_defaults_to_bearer_for_a_string_token(self, rest_app):
             executor=rest_app._executor,
             http_settings=rest_app._http_settings,
             max_rate_limit=float("inf"),
+            max_retries=0,
             proxy_settings=rest_app._proxy_settings,
             token="token",
             token_type=applications.TokenType.BEARER,
@@ -436,6 +439,7 @@ def rest_client(rest_client_class, live_attributes, mock_cache):
         proxy_settings=mock.Mock(spec=config.ProxySettings),
         token="some_token",
         token_type="tYpe",
+        max_retries=0,
         rest_url="https://some.where/api/v3",
         executor=mock.Mock(),
         entity_factory=mock.Mock(),
diff --git a/tests/hikari/impl/test_rest_bot.py b/tests/hikari/impl/test_rest_bot.py
index 26078818..109957ec 100644
--- a/tests/hikari/impl/test_rest_bot.py
+++ b/tests/hikari/impl/test_rest_bot.py
@@ -85,7 +85,11 @@ def mock_rest_bot(
 
         with stack:
             return hikari_test_helpers.mock_class_namespace(rest_bot_impl.RESTBot, slots_=False)(
-                "token", http_settings=mock_http_settings, proxy_settings=mock_proxy_settings, executor=mock_executor
+                "token",
+                http_settings=mock_http_settings,
+                proxy_settings=mock_proxy_settings,
+                executor=mock_executor,
+                max_retries=0,
             )
 
     def test___init__(
@@ -116,6 +120,7 @@ def test___init__(
                 http_settings=mock_http_settings,
                 logs="ERROR",
                 max_rate_limit=32123123,
+                max_retries=0,
                 proxy_settings=mock_proxy_settings,
                 rest_url="hresresres",
             )
@@ -128,6 +133,7 @@ def test___init__(
                 executor=mock_executor,
                 http_settings=mock_http_settings,
                 max_rate_limit=32123123,
+                max_retries=0,
                 proxy_settings=mock_proxy_settings,
                 rest_url="hresresres",
                 token="token",
@@ -176,6 +182,7 @@ def test___init___generates_default_settings(self):
                 executor=None,
                 http_settings=config.HTTPSettings.return_value,
                 max_rate_limit=300.0,
+                max_retries=3,
                 proxy_settings=config.ProxySettings.return_value,
                 rest_url=None,
                 token="token",