aioretry


Nameaioretry JSON
Version 6.2.0 PyPI version JSON
download
home_pageNone
SummaryAsyncio retry utility for Python 3.7+
upload_time2024-10-28 15:29:44
maintainerNone
docs_urlNone
authorNone
requires_python>=3.7
licenseCopyright (c) 2013 kaelzhang <>, contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords aioretry
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            [![](https://github.com/kaelzhang/python-aioretry/actions/workflows/python.yml/badge.svg)](https://github.com/kaelzhang/python-aioretry/actions/workflows/python.yml)
[![](https://codecov.io/gh/kaelzhang/python-aioretry/branch/master/graph/badge.svg)](https://codecov.io/gh/kaelzhang/python-aioretry)
[![](https://img.shields.io/pypi/v/aioretry.svg)](https://pypi.org/project/aioretry/)
[![](https://img.shields.io/pypi/l/aioretry.svg)](https://github.com/kaelzhang/python-aioretry)

# aioretry

Asyncio retry utility for Python 3.7+

- [Upgrade guide](#upgrade-guide)

## Install

```sh
$ pip install aioretry
```

A [conda-forge recipe](https://github.com/conda-forge/aioretry-feedstock) is also available, so you can also use

```sh
conda install -c conda-forge aioretry
```

## Usage

```py
import asyncio

from aioretry import (
    retry,
    # Tuple[bool, Union[int, float]]
    RetryPolicyStrategy,
    RetryInfo
)

# This example shows the usage with python typings
def retry_policy(info: RetryInfo) -> RetryPolicyStrategy:
    """
    - It will always retry until succeeded: abandon = False
    - If fails for the first time, it will retry immediately,
    - If it fails again,
      aioretry will perform a 100ms delay before the second retry,
      200ms delay before the 3rd retry,
      the 4th retry immediately,
      100ms delay before the 5th retry,
      etc...
    """
    return False, (info.fails - 1) % 3 * 0.1


@retry(retry_policy)
async def connect_to_server():
    # connec to server
    ...

asyncio.run(connect_to_server())
```

### Use as class instance method decorator

We could also use `retry` as a decorator for instance method

```py
class Client:
    @retry(retry_policy)
    async def connect(self):
        await self._connect()

asyncio.run(Client().connect())
```

### Use instance method as retry policy

`retry_policy` could be the method name of the class if `retry` is used as a decorator for instance method.

```py
class ClientWithConfigurableRetryPolicy(Client):
    def __init__(self, max_retries: int = 3):
        self._max_retries = max_retries

    def _retry_policy(self, info: RetryInfo) -> RetryPolicyStrategy:
        return info.fails > self._max_retries, info.fails * 0.1

    # Then aioretry will use `self._retry_policy` as the retry policy.
    # And by using a str as the parameter `retry_policy`,
    # the decorator must be used for instance methods
    @retry('_retry_policy')
    async def connect(self):
        await self._connect()

asyncio.run(ClientWithConfigurableRetryPolicy(10).connect())
```

### Register an `before_retry` callback

We could also register an `before_retry` callback which will be executed after every failure of the target function if the corresponding retry is not abandoned.

```py
class ClientTrackableFailures(ClientWithConfigurableRetryPolicy):
    # `before_retry` could either be a sync function or an async function
    async def _before_retry(self, info: RetryInfo) -> None:
        await self._send_failure_log(info.exception, info.fails)

    @retry(
      retry_policy='_retry_policy',

      # Similar to `retry_policy`,
      # `before_retry` could either be a Callable or a str
      before_retry='_before_retry'
    )
    async def connect(self):
        await self._connect()
```

### Only retry for certain types of exceptions

```py
def retry_policy(info: RetryInfo) -> RetryPolicyStrategy:
    if isinstance(info.exception, (KeyError, ValueError)):
        # If it raises a KeyError or a ValueError, it will not retry.
        return True, 0

    # Otherwise, retry immediately
    return False, 0

@retry(retry_policy)
async def foo():
    # do something that might raise KeyError, ValueError or RuntimeError
    ...
```

## APIs

### retry(retry_policy, before_retry)(fn)

- **fn** `Callable[[...], Awaitable]` the function to be wrapped. The function should be an async function or normal function returns an awaitable.
- **retry_policy** `Union[str, RetryPolicy]`
- **before_retry?** `Optional[Union[str, Callable[[RetryInfo], Optional[Awaitable]]]]` If specified, `before_retry` is called after each failure of `fn` and before the corresponding retry. If the retry is abandoned, `before_retry` will not be executed.

Returns a wrapped function which accepts the same arguments as `fn` and returns an `Awaitable`.

### RetryPolicy

```py
RetryPolicyStrategy = Tuple[bool, int | float]
RetryPolicy = Callable[[RetryInfo], RetryPolicyStrategy | Awaitable[RetryPolicyStrategy]]
```

Retry policy is used to determine what to do next after the `fn` fails to do some certain thing.

```py
rt = retry_policy(info)

abandon, delay = (
    # Since 6.2.0, retry_policy could also return an `Awaitable`
    await rt
    if inspect.isawaitable(rt)
    else rt
)
```

- **info** `RetryInfo`
  - **info.fails** `int` is the counter number of how many times function `fn` performs as a failure. If `fn` fails for the first time, then `fails` will be `1`.
  - **info.exception** `Exception` is the exception that `fn` raised.
  - **info.since** `float` is the fractional time seconds generated by [`time.monotonic()`](https://docs.python.org/3/library/time.html#time.monotonic) when the first failure happens.
- If `abandon` is `True`, then aioretry will give up the retry and raise the exception directly, otherwise aioretry will sleep `delay` seconds (`asyncio.sleep(delay)`) before the next retry.

```py
def retry_policy(info: RetryInfo):
    if isinstance(info.exception, KeyError):
        # Just raise exceptions of type KeyError
        return True, 0

    return False, info.fails * 0.1
```

### Python typings

```py
from aioretry import (
    # The type of retry_policy function
    RetryPolicy,
    # The type of the return value of retry_policy function
    RetryPolicyStrategy,
    # The type of before_retry function
    BeforeRetry,
    RetryInfo
)
```

## Upgrade guide

Since `5.0.0`, aioretry introduces `RetryInfo` as the only parameter of `retry_policy` or `before_retry`

### 2.x -> 5.x

2.x

```py
def retry_policy(fails: int):
    """A policy that gives no chances to retry
    """

    return True, 0.1 * fails
```

5.x

```py
def retry_policy(info: RetryInfo):
    return True, 0.1 * info.fails
```

### 3.x -> 5.x

3.x

```py
def before_retry(e: Exception, fails: int):
    ...
```

5.x

```py
# Change the sequence of the parameters
def before_retry(info: RetryInfo):
    info.exception
    info.fails
    ...
```

### 4.x -> 5.x

Since `5.0.0`, both `retry_policy` and `before_retry` have only one parameter of type `RetryInfo` respectively.

### 5.x -> 6.x

Since `6.0.0`, `RetryInfo::since` is a `float` value which is generated by [`time.monotonic()`](https://docs.python.org/3/library/time.html#time.monotonic) and is better for measuring intervals than `datetime`, while in `5.x` `RetryInfo::since` is a `datetime`

```py
# 5.x
from datetime import datetime

def retry_policy(info: RetryInfo) -> RetryPolicyStrategy:
    print('error occurred', (datetime.now() - info.since).total_seconds(), 'seconds ago')

    ...
```

```py
# 6.x
import time

def retry_policy(info: RetryInfo) -> RetryPolicyStrategy:
    print('error occurred', time.monotonic() - info.since, 'seconds ago')

    ...
```

## License

[MIT](LICENSE)

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "aioretry",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": null,
    "keywords": "aioretry",
    "author": null,
    "author_email": "Kael Zhang <i+pypi@kael.me>",
    "download_url": "https://files.pythonhosted.org/packages/bb/17/cb58d7f9a5f4d255002206f7bf8109ed587e543edab49a461589a8391bdd/aioretry-6.2.0.tar.gz",
    "platform": null,
    "description": "[![](https://github.com/kaelzhang/python-aioretry/actions/workflows/python.yml/badge.svg)](https://github.com/kaelzhang/python-aioretry/actions/workflows/python.yml)\n[![](https://codecov.io/gh/kaelzhang/python-aioretry/branch/master/graph/badge.svg)](https://codecov.io/gh/kaelzhang/python-aioretry)\n[![](https://img.shields.io/pypi/v/aioretry.svg)](https://pypi.org/project/aioretry/)\n[![](https://img.shields.io/pypi/l/aioretry.svg)](https://github.com/kaelzhang/python-aioretry)\n\n# aioretry\n\nAsyncio retry utility for Python 3.7+\n\n- [Upgrade guide](#upgrade-guide)\n\n## Install\n\n```sh\n$ pip install aioretry\n```\n\nA [conda-forge recipe](https://github.com/conda-forge/aioretry-feedstock) is also available, so you can also use\n\n```sh\nconda install -c conda-forge aioretry\n```\n\n## Usage\n\n```py\nimport asyncio\n\nfrom aioretry import (\n    retry,\n    # Tuple[bool, Union[int, float]]\n    RetryPolicyStrategy,\n    RetryInfo\n)\n\n# This example shows the usage with python typings\ndef retry_policy(info: RetryInfo) -> RetryPolicyStrategy:\n    \"\"\"\n    - It will always retry until succeeded: abandon = False\n    - If fails for the first time, it will retry immediately,\n    - If it fails again,\n      aioretry will perform a 100ms delay before the second retry,\n      200ms delay before the 3rd retry,\n      the 4th retry immediately,\n      100ms delay before the 5th retry,\n      etc...\n    \"\"\"\n    return False, (info.fails - 1) % 3 * 0.1\n\n\n@retry(retry_policy)\nasync def connect_to_server():\n    # connec to server\n    ...\n\nasyncio.run(connect_to_server())\n```\n\n### Use as class instance method decorator\n\nWe could also use `retry` as a decorator for instance method\n\n```py\nclass Client:\n    @retry(retry_policy)\n    async def connect(self):\n        await self._connect()\n\nasyncio.run(Client().connect())\n```\n\n### Use instance method as retry policy\n\n`retry_policy` could be the method name of the class if `retry` is used as a decorator for instance method.\n\n```py\nclass ClientWithConfigurableRetryPolicy(Client):\n    def __init__(self, max_retries: int = 3):\n        self._max_retries = max_retries\n\n    def _retry_policy(self, info: RetryInfo) -> RetryPolicyStrategy:\n        return info.fails > self._max_retries, info.fails * 0.1\n\n    # Then aioretry will use `self._retry_policy` as the retry policy.\n    # And by using a str as the parameter `retry_policy`,\n    # the decorator must be used for instance methods\n    @retry('_retry_policy')\n    async def connect(self):\n        await self._connect()\n\nasyncio.run(ClientWithConfigurableRetryPolicy(10).connect())\n```\n\n### Register an `before_retry` callback\n\nWe could also register an `before_retry` callback which will be executed after every failure of the target function if the corresponding retry is not abandoned.\n\n```py\nclass ClientTrackableFailures(ClientWithConfigurableRetryPolicy):\n    # `before_retry` could either be a sync function or an async function\n    async def _before_retry(self, info: RetryInfo) -> None:\n        await self._send_failure_log(info.exception, info.fails)\n\n    @retry(\n      retry_policy='_retry_policy',\n\n      # Similar to `retry_policy`,\n      # `before_retry` could either be a Callable or a str\n      before_retry='_before_retry'\n    )\n    async def connect(self):\n        await self._connect()\n```\n\n### Only retry for certain types of exceptions\n\n```py\ndef retry_policy(info: RetryInfo) -> RetryPolicyStrategy:\n    if isinstance(info.exception, (KeyError, ValueError)):\n        # If it raises a KeyError or a ValueError, it will not retry.\n        return True, 0\n\n    # Otherwise, retry immediately\n    return False, 0\n\n@retry(retry_policy)\nasync def foo():\n    # do something that might raise KeyError, ValueError or RuntimeError\n    ...\n```\n\n## APIs\n\n### retry(retry_policy, before_retry)(fn)\n\n- **fn** `Callable[[...], Awaitable]` the function to be wrapped. The function should be an async function or normal function returns an awaitable.\n- **retry_policy** `Union[str, RetryPolicy]`\n- **before_retry?** `Optional[Union[str, Callable[[RetryInfo], Optional[Awaitable]]]]` If specified, `before_retry` is called after each failure of `fn` and before the corresponding retry. If the retry is abandoned, `before_retry` will not be executed.\n\nReturns a wrapped function which accepts the same arguments as `fn` and returns an `Awaitable`.\n\n### RetryPolicy\n\n```py\nRetryPolicyStrategy = Tuple[bool, int | float]\nRetryPolicy = Callable[[RetryInfo], RetryPolicyStrategy | Awaitable[RetryPolicyStrategy]]\n```\n\nRetry policy is used to determine what to do next after the `fn` fails to do some certain thing.\n\n```py\nrt = retry_policy(info)\n\nabandon, delay = (\n    # Since 6.2.0, retry_policy could also return an `Awaitable`\n    await rt\n    if inspect.isawaitable(rt)\n    else rt\n)\n```\n\n- **info** `RetryInfo`\n  - **info.fails** `int` is the counter number of how many times function `fn` performs as a failure. If `fn` fails for the first time, then `fails` will be `1`.\n  - **info.exception** `Exception` is the exception that `fn` raised.\n  - **info.since** `float` is the fractional time seconds generated by [`time.monotonic()`](https://docs.python.org/3/library/time.html#time.monotonic) when the first failure happens.\n- If `abandon` is `True`, then aioretry will give up the retry and raise the exception directly, otherwise aioretry will sleep `delay` seconds (`asyncio.sleep(delay)`) before the next retry.\n\n```py\ndef retry_policy(info: RetryInfo):\n    if isinstance(info.exception, KeyError):\n        # Just raise exceptions of type KeyError\n        return True, 0\n\n    return False, info.fails * 0.1\n```\n\n### Python typings\n\n```py\nfrom aioretry import (\n    # The type of retry_policy function\n    RetryPolicy,\n    # The type of the return value of retry_policy function\n    RetryPolicyStrategy,\n    # The type of before_retry function\n    BeforeRetry,\n    RetryInfo\n)\n```\n\n## Upgrade guide\n\nSince `5.0.0`, aioretry introduces `RetryInfo` as the only parameter of `retry_policy` or `before_retry`\n\n### 2.x -> 5.x\n\n2.x\n\n```py\ndef retry_policy(fails: int):\n    \"\"\"A policy that gives no chances to retry\n    \"\"\"\n\n    return True, 0.1 * fails\n```\n\n5.x\n\n```py\ndef retry_policy(info: RetryInfo):\n    return True, 0.1 * info.fails\n```\n\n### 3.x -> 5.x\n\n3.x\n\n```py\ndef before_retry(e: Exception, fails: int):\n    ...\n```\n\n5.x\n\n```py\n# Change the sequence of the parameters\ndef before_retry(info: RetryInfo):\n    info.exception\n    info.fails\n    ...\n```\n\n### 4.x -> 5.x\n\nSince `5.0.0`, both `retry_policy` and `before_retry` have only one parameter of type `RetryInfo` respectively.\n\n### 5.x -> 6.x\n\nSince `6.0.0`, `RetryInfo::since` is a `float` value which is generated by [`time.monotonic()`](https://docs.python.org/3/library/time.html#time.monotonic) and is better for measuring intervals than `datetime`, while in `5.x` `RetryInfo::since` is a `datetime`\n\n```py\n# 5.x\nfrom datetime import datetime\n\ndef retry_policy(info: RetryInfo) -> RetryPolicyStrategy:\n    print('error occurred', (datetime.now() - info.since).total_seconds(), 'seconds ago')\n\n    ...\n```\n\n```py\n# 6.x\nimport time\n\ndef retry_policy(info: RetryInfo) -> RetryPolicyStrategy:\n    print('error occurred', time.monotonic() - info.since, 'seconds ago')\n\n    ...\n```\n\n## License\n\n[MIT](LICENSE)\n",
    "bugtrack_url": null,
    "license": "Copyright (c) 2013 kaelzhang <>, contributors  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
    "summary": "Asyncio retry utility for Python 3.7+",
    "version": "6.2.0",
    "project_urls": {
        "Homepage": "https://github.com/kaelzhang/python-aioretry"
    },
    "split_keywords": [
        "aioretry"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "244778dfc37956ffbdab9e210ab05afde41dd38b3979cf41af32ad20b8d6125c",
                "md5": "d4f8f13f3cf6a787129772ecbfd2c085",
                "sha256": "b78ff24552f0e166fe9f5f3b39d07922a405709ed37cd622634b3d00892d4c0f"
            },
            "downloads": -1,
            "filename": "aioretry-6.2.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "d4f8f13f3cf6a787129772ecbfd2c085",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 7239,
            "upload_time": "2024-10-28T15:29:42",
            "upload_time_iso_8601": "2024-10-28T15:29:42.604648Z",
            "url": "https://files.pythonhosted.org/packages/24/47/78dfc37956ffbdab9e210ab05afde41dd38b3979cf41af32ad20b8d6125c/aioretry-6.2.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bb17cb58d7f9a5f4d255002206f7bf8109ed587e543edab49a461589a8391bdd",
                "md5": "55ce5cf4c16a546ae38353b80dba4fe4",
                "sha256": "dd672dfe270cae4defbe41513b3cb5e971ebc09d6d45ad270e27e004b2f4e083"
            },
            "downloads": -1,
            "filename": "aioretry-6.2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "55ce5cf4c16a546ae38353b80dba4fe4",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 8670,
            "upload_time": "2024-10-28T15:29:44",
            "upload_time_iso_8601": "2024-10-28T15:29:44.456302Z",
            "url": "https://files.pythonhosted.org/packages/bb/17/cb58d7f9a5f4d255002206f7bf8109ed587e543edab49a461589a8391bdd/aioretry-6.2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-10-28 15:29:44",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "kaelzhang",
    "github_project": "python-aioretry",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "aioretry"
}
        
Elapsed time: 0.38797s