Skip to content

Commit ccf3f2a

Browse files
authored
chore: Replace archived backoff with tenacity (#573)
1 parent a3a4597 commit ccf3f2a

File tree

6 files changed

+102
-57
lines changed

6 files changed

+102
-57
lines changed

docs/advanced/async_advanced_usage.rst

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Async advanced usage
66
It is possible to send multiple GraphQL queries (query, mutation or subscription) in parallel,
77
on the same websocket connection, using asyncio tasks.
88

9-
In order to retry in case of connection failure, we can use the great `backoff`_ module.
9+
In order to retry in case of connection failure, we can use the great `tenacity`_ module.
1010

1111
.. code-block:: python
1212
@@ -28,10 +28,22 @@ In order to retry in case of connection failure, we can use the great `backoff`_
2828
async for result in session.subscribe(subscription2):
2929
print(result)
3030
31-
# Then create a couroutine which will connect to your API and run all your queries as tasks.
32-
# We use a `backoff` decorator to reconnect using exponential backoff in case of connection failure.
33-
34-
@backoff.on_exception(backoff.expo, Exception, max_time=300)
31+
# Then create a couroutine which will connect to your API and run all your
32+
# queries as tasks. We use a `tenacity` retry decorator to reconnect using
33+
# exponential backoff in case of connection failure.
34+
35+
from tenacity import (
36+
retry,
37+
retry_if_exception_type,
38+
stop_after_delay,
39+
wait_exponential,
40+
)
41+
42+
@retry(
43+
retry=retry_if_exception_type(Exception),
44+
stop=stop_after_delay(300), # max_time in seconds
45+
wait=wait_exponential(),
46+
)
3547
async def graphql_connection():
3648
3749
transport = WebsocketsTransport(url="wss://YOUR_URL")
@@ -54,4 +66,4 @@ Subscriptions tasks can be stopped at any time by running
5466
5567
task.cancel()
5668
57-
.. _backoff: https://github.com/litl/backoff
69+
.. _tenacity: https://github.com/jd/tenacity

docs/advanced/async_permanent_session.rst

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,22 @@ Retries
3636
Connection retries
3737
^^^^^^^^^^^^^^^^^^
3838

39-
With :code:`reconnecting=True`, gql will use the `backoff`_ module to repeatedly try to connect with
40-
exponential backoff and jitter with a maximum delay of 60 seconds by default.
39+
With :code:`reconnecting=True`, gql will use the `tenacity`_ module to repeatedly
40+
try to connect with exponential backoff and jitter with a maximum delay of
41+
60 seconds by default.
4142

4243
You can change the default reconnecting profile by providing your own
43-
backoff decorator to the :code:`retry_connect` argument.
44+
retry decorator (from tenacity) to the :code:`retry_connect` argument.
4445

4546
.. code-block:: python
4647
48+
from tenacity import retry, retry_if_exception_type, wait_exponential
49+
4750
# Here wait maximum 5 minutes between connection retries
48-
retry_connect = backoff.on_exception(
49-
backoff.expo, # wait generator (here: exponential backoff)
50-
Exception, # which exceptions should cause a retry (here: everything)
51-
max_value=300, # max wait time in seconds
51+
retry_connect = retry(
52+
# which exceptions should cause a retry (here: everything)
53+
retry=retry_if_exception_type(Exception),
54+
wait=wait_exponential(max=300), # max wait time in seconds
5255
)
5356
session = await client.connect_async(
5457
reconnecting=True,
@@ -66,32 +69,49 @@ There is no retry in case of a :code:`TransportQueryError` exception as it indic
6669
the connection to the backend is working correctly.
6770

6871
You can change the default execute retry profile by providing your own
69-
backoff decorator to the :code:`retry_execute` argument.
72+
retry decorator (from tenacity) to the :code:`retry_execute` argument.
7073

7174
.. code-block:: python
7275
76+
from tenacity import (
77+
retry,
78+
retry_if_exception_type,
79+
stop_after_attempt,
80+
wait_exponential,
81+
)
82+
7383
# Here Only 3 tries for execute calls
74-
retry_execute = backoff.on_exception(
75-
backoff.expo,
76-
Exception,
77-
max_tries=3,
84+
retry_execute = retry(
85+
retry=retry_if_exception_type(Exception),
86+
stop=stop_after_attempt(3),
87+
wait=wait_exponential(),
7888
)
7989
session = await client.connect_async(
8090
reconnecting=True,
8191
retry_execute=retry_execute,
8292
)
8393
84-
If you don't want any retry on the execute calls, you can disable the retries with :code:`retry_execute=False`
94+
If you don't want any retry on the execute calls, you can disable the retries
95+
with :code:`retry_execute=False`
8596

8697
.. note::
8798
If you want to retry even with :code:`TransportQueryError` exceptions,
88-
then you need to make your own backoff decorator on your own method:
99+
then you need to make your own retry decorator (from tenacity) on your own method:
89100

90101
.. code-block:: python
91102
92-
@backoff.on_exception(backoff.expo,
93-
Exception,
94-
max_tries=3)
103+
from tenacity import (
104+
retry,
105+
retry_if_exception_type,
106+
stop_after_attempt,
107+
wait_exponential,
108+
)
109+
110+
@retry(
111+
retry=retry_if_exception_type(Exception),
112+
stop=stop_after_attempt(3),
113+
wait=wait_exponential(),
114+
)
95115
async def execute_with_retry(session, query):
96116
return await session.execute(query)
97117
@@ -100,14 +120,25 @@ Subscription retries
100120

101121
There is no :code:`retry_subscribe` as it is not feasible with async generators.
102122
If you want retries for your subscriptions, then you can do it yourself
103-
with backoff decorators on your methods.
123+
with retry decorators (from tenacity) on your methods.
104124

105125
.. code-block:: python
106126
107-
@backoff.on_exception(backoff.expo,
108-
Exception,
109-
max_tries=3,
110-
giveup=lambda e: isinstance(e, TransportQueryError))
127+
from tenacity import (
128+
retry,
129+
retry_if_exception_type,
130+
retry_unless_exception_type,
131+
stop_after_attempt,
132+
wait_exponential,
133+
)
134+
from gql.transport.exceptions import TransportQueryError
135+
136+
@retry(
137+
retry=retry_if_exception_type(Exception)
138+
& retry_unless_exception_type(TransportQueryError),
139+
stop=stop_after_attempt(3),
140+
wait=wait_exponential(),
141+
)
111142
async def execute_subscription1(session):
112143
async for result in session.subscribe(subscription1):
113144
print(result)
@@ -123,4 +154,4 @@ Console example
123154
.. literalinclude:: ../code_examples/console_async.py
124155

125156
.. _difficult to manage: https://github.com/graphql-python/gql/issues/179
126-
.. _backoff: https://github.com/litl/backoff
157+
.. _tenacity: https://github.com/jd/tenacity

docs/code_examples/reconnecting_mutation_http.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import logging
33

4-
import backoff
4+
from tenacity import retry, retry_if_exception_type, wait_exponential
55

66
from gql import Client, gql
77
from gql.transport.aiohttp import AIOHTTPTransport
@@ -17,11 +17,9 @@ async def main():
1717

1818
client = Client(transport=transport)
1919

20-
retry_connect = backoff.on_exception(
21-
backoff.expo,
22-
Exception,
23-
max_value=10,
24-
jitter=None,
20+
retry_connect = retry(
21+
retry=retry_if_exception_type(Exception),
22+
wait=wait_exponential(max=10),
2523
)
2624
session = await client.connect_async(reconnecting=True, retry_connect=retry_connect)
2725

docs/code_examples/reconnecting_mutation_ws.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import logging
33

4-
import backoff
4+
from tenacity import retry, retry_if_exception_type, wait_exponential
55

66
from gql import Client, gql
77
from gql.transport.websockets import WebsocketsTransport
@@ -17,11 +17,9 @@ async def main():
1717

1818
client = Client(transport=transport)
1919

20-
retry_connect = backoff.on_exception(
21-
backoff.expo,
22-
Exception,
23-
max_value=10,
24-
jitter=None,
20+
retry_connect = retry(
21+
retry=retry_if_exception_type(Exception),
22+
wait=wait_exponential(max=10),
2523
)
2624
session = await client.connect_async(reconnecting=True, retry_connect=retry_connect)
2725

gql/client.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
overload,
2222
)
2323

24-
import backoff
2524
from anyio import fail_after
2625
from graphql import (
2726
ExecutionResult,
@@ -31,6 +30,13 @@
3130
parse,
3231
validate,
3332
)
33+
from tenacity import (
34+
retry,
35+
retry_if_exception_type,
36+
retry_unless_exception_type,
37+
stop_after_attempt,
38+
wait_exponential,
39+
)
3440

3541
from .graphql_request import GraphQLRequest, support_deprecated_request
3642
from .transport.async_transport import AsyncTransport
@@ -1902,11 +1908,12 @@ def __init__(
19021908
"""
19031909
:param client: the :class:`client <gql.client.Client>` used.
19041910
:param retry_connect: Either a Boolean to activate/deactivate the retries
1905-
for the connection to the transport OR a backoff decorator to
1906-
provide specific retries parameters for the connections.
1911+
for the connection to the transport OR a retry decorator
1912+
(e.g., from tenacity) to provide specific retries parameters
1913+
for the connections.
19071914
:param retry_execute: Either a Boolean to activate/deactivate the retries
1908-
for the execute method OR a backoff decorator to
1909-
provide specific retries parameters for this method.
1915+
for the execute method OR a retry decorator (e.g., from tenacity)
1916+
to provide specific retries parameters for this method.
19101917
"""
19111918
self.client = client
19121919
self._connect_task = None
@@ -1917,10 +1924,9 @@ def __init__(
19171924
if retry_connect is True:
19181925
# By default, retry again and again, with maximum 60 seconds
19191926
# between retries
1920-
self.retry_connect = backoff.on_exception(
1921-
backoff.expo,
1922-
Exception,
1923-
max_value=60,
1927+
self.retry_connect = retry(
1928+
retry=retry_if_exception_type(Exception),
1929+
wait=wait_exponential(max=60),
19241930
)
19251931
elif retry_connect is False:
19261932
self.retry_connect = lambda e: e
@@ -1930,11 +1936,11 @@ def __init__(
19301936

19311937
if retry_execute is True:
19321938
# By default, retry 5 times, except if we receive a TransportQueryError
1933-
self.retry_execute = backoff.on_exception(
1934-
backoff.expo,
1935-
Exception,
1936-
max_tries=5,
1937-
giveup=lambda e: isinstance(e, TransportQueryError),
1939+
self.retry_execute = retry(
1940+
retry=retry_if_exception_type(Exception)
1941+
& retry_unless_exception_type(TransportQueryError),
1942+
stop=stop_after_attempt(5),
1943+
wait=wait_exponential(),
19381944
)
19391945
elif retry_execute is False:
19401946
self.retry_execute = lambda e: e
@@ -1943,7 +1949,7 @@ def __init__(
19431949
self.retry_execute = retry_execute
19441950

19451951
# Creating the _execute_with_retries and _connect_with_retries methods
1946-
# using the provided backoff decorators
1952+
# using the provided retry decorators
19471953
self._execute_with_retries = self.retry_execute(self._execute_once)
19481954
self._connect_with_retries = self.retry_connect(self.transport.connect)
19491955

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
install_requires = [
66
"graphql-core>=3.3.0a3,<3.4",
77
"yarl>=1.6,<2.0",
8-
"backoff>=1.11.1,<3.0",
8+
"tenacity>=9.1.2,<10.0",
99
"anyio>=3.0,<5",
1010
"typing_extensions>=4.0.0; python_version<'3.11'",
1111
]

0 commit comments

Comments
 (0)