|
| 1 | +.. _async_permanent_session: |
| 2 | + |
| 3 | +Async permanent session |
| 4 | +======================= |
| 5 | + |
| 6 | +Sometimes you want to have a single permanent reconnecting async session to a GraphQL backend, |
| 7 | +and that can be `difficult to manage`_ manually with the :code:`async with client as session` syntax. |
| 8 | + |
| 9 | +It is now possible to have a single reconnecting session using the |
| 10 | +:meth:`connect_async <gql.Client.connect_async>` method of Client |
| 11 | +with a :code:`reconnecting=True` argument. |
| 12 | + |
| 13 | +.. code-block:: python |
| 14 | +
|
| 15 | + # Create a session from the client which will reconnect automatically. |
| 16 | + # This session can be kept in a class for example to provide a way |
| 17 | + # to execute GraphQL queries from many different places |
| 18 | + session = await client.connect_async(reconnecting=True) |
| 19 | +
|
| 20 | + # You can run execute or subscribe method on this session |
| 21 | + result = await session.execute(query) |
| 22 | +
|
| 23 | + # When you want the connection to close (for cleanup), |
| 24 | + # you call close_async |
| 25 | + await client.close_async() |
| 26 | +
|
| 27 | +
|
| 28 | +When you use :code:`reconnecting=True`, gql will watch the exceptions generated |
| 29 | +during the execute and subscribe calls and, if it detects a TransportClosed exception |
| 30 | +(indicating that the link to the underlying transport is broken), |
| 31 | +it will try to reconnect to the backend again. |
| 32 | + |
| 33 | +Retries |
| 34 | +------- |
| 35 | + |
| 36 | +Connection retries |
| 37 | +^^^^^^^^^^^^^^^^^^ |
| 38 | + |
| 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. |
| 41 | + |
| 42 | +You can change the default reconnecting profile by providing your own |
| 43 | +backoff decorator to the :code:`retry_connect` argument. |
| 44 | + |
| 45 | +.. code-block:: python |
| 46 | +
|
| 47 | + # 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 |
| 52 | + ) |
| 53 | + session = await client.connect_async( |
| 54 | + reconnecting=True, |
| 55 | + retry_connect=retry_connect, |
| 56 | + ) |
| 57 | +
|
| 58 | +Execution retries |
| 59 | +^^^^^^^^^^^^^^^^^ |
| 60 | + |
| 61 | +With :code:`reconnecting=True`, by default we will also retry up to 5 times |
| 62 | +when an exception happens during an execute call (to manage a possible loss in the connection |
| 63 | +to the transport). |
| 64 | + |
| 65 | +There is no retry in case of a :code:`TransportQueryError` exception as it indicates that |
| 66 | +the connection to the backend is working correctly. |
| 67 | + |
| 68 | +You can change the default execute retry profile by providing your own |
| 69 | +backoff decorator to the :code:`retry_execute` argument. |
| 70 | + |
| 71 | +.. code-block:: python |
| 72 | +
|
| 73 | + # Here Only 3 tries for execute calls |
| 74 | + retry_execute = backoff.on_exception( |
| 75 | + backoff.expo, |
| 76 | + Exception, |
| 77 | + max_tries=3, |
| 78 | + giveup=lambda e: isinstance(e, TransportQueryError), |
| 79 | + ) |
| 80 | + session = await client.connect_async( |
| 81 | + reconnecting=True, |
| 82 | + retry_execute=retry_execute, |
| 83 | + ) |
| 84 | +
|
| 85 | +If you don't want any retry on the execute calls, you can disable the retries with :code:`retry_execute=False` |
| 86 | + |
| 87 | +Subscription retries |
| 88 | +^^^^^^^^^^^^^^^^^^^^ |
| 89 | + |
| 90 | +There is no :code:`retry_subscribe` as it is not feasible with async generators. |
| 91 | +If you want retries for your subscriptions, then you can do it yourself |
| 92 | +with backoff decorators on your methods. |
| 93 | + |
| 94 | +.. code-block:: python |
| 95 | +
|
| 96 | + @backoff.on_exception(backoff.expo, |
| 97 | + Exception, |
| 98 | + max_tries=3, |
| 99 | + giveup=lambda e: isinstance(e, TransportQueryError)) |
| 100 | + async def execute_subscription1(session): |
| 101 | + async for result in session.subscribe(subscription1): |
| 102 | + print(result) |
| 103 | +
|
| 104 | +FastAPI example |
| 105 | +--------------- |
| 106 | + |
| 107 | +.. literalinclude:: ../code_examples/fastapi_async.py |
| 108 | + |
| 109 | +Console example |
| 110 | +--------------- |
| 111 | + |
| 112 | +.. literalinclude:: ../code_examples/console_async.py |
| 113 | + |
| 114 | +.. _difficult to manage: https://github.com/graphql-python/gql/issues/179 |
| 115 | +.. _backoff: https://github.com/litl/backoff |
0 commit comments