Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pull request with context manager improvements. #54

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Python application

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pip install git+https://github.com/brookman1/retry.git
pytest
235 changes: 112 additions & 123 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,160 +1,149 @@
retry
=====
# reretry

.. image:: https://img.shields.io/pypi/dm/retry.svg?maxAge=2592000
:target: https://pypi.python.org/pypi/retry/
![](https://img.shields.io/pypi/dm/reretry.svg?maxAge=2592000)
![](https://img.shields.io/pypi/v/reretry.svg?maxAge=2592000)
![](https://img.shields.io/pypi/l/reretry.svg?maxAge=2592000)

.. image:: https://img.shields.io/pypi/v/retry.svg?maxAge=2592000
:target: https://pypi.python.org/pypi/retry/
An easy to use retry decorator.

.. image:: https://img.shields.io/pypi/l/retry.svg?maxAge=2592000
:target: https://pypi.python.org/pypi/retry/
This package is a fork from the [`retry`](https://github.com/invl/retry) package, but with some of added community-sourced features.


Easy to use retry decorator.
## Features


Features
--------

- No external dependency (stdlib only).
From original `retry`:
- Retry on specific exceptions.
- Set a maximum number of retries.
- Set a delay between retries.
- Set a maximum delay between retries.
- Set backoff and jitter parameters.
- Use a custom logger.
- No external dependencies (stdlib only).
- (Optionally) Preserve function signatures (`pip install decorator`).
- Original traceback, easy to debug.


Installation
------------

.. code-block:: bash
New features in `reretry`:
- Log traceback of an error that lead to a failed attempt.
- Call a custom callback after each failed attempt.
- Can be used with async functions.

$ pip install retry

## Installation

API
---
```bash
$ pip install reretry
```

retry decorator
^^^^^^^^^^^^^^^
## API
### The @retry decorator

.. code:: python
#### Usage
`@retry(exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1, jitter=0, show_traceback=False, logger=logging_logger, fail_callback=None, condition=threading.Condition())`

def retry(exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1, jitter=0, logger=logging_logger):
"""Return a retry decorator.
#### Arguments
- `exceptions`: An exception or a tuple of exceptions to catch. Default: Exception.

:param exceptions: an exception or a tuple of exceptions to catch. default: Exception.
:param tries: the maximum number of attempts. default: -1 (infinite).
:param delay: initial delay between attempts. default: 0.
:param max_delay: the maximum value of delay. default: None (no limit).
:param backoff: multiplier applied to delay between attempts. default: 1 (no backoff).
:param jitter: extra seconds added to delay between attempts. default: 0.
fixed if a number, random if a range tuple (min, max)
:param logger: logger.warning(fmt, error, delay) will be called on failed attempts.
default: retry.logging_logger. if None, logging is disabled.
"""
- `tries`: The maximum number of attempts. default: -1 (infinite).

Various retrying logic can be achieved by combination of arguments.
- `delay`: Initial delay between attempts (in seconds). default: 0.

- `max_delay`: The maximum value of delay (in seconds). default: None (no limit).

Examples
""""""""
- `backoff`: Multiplier applied to delay between attempts. default: 1 (no backoff).

.. code:: python
- `jitter`: Extra seconds added to delay between attempts. default: 0. Fixed if a number, random if a range tuple (min, max).

from retry import retry
- `show_traceback`: Print traceback before retrying (Python3 only). default: False.

.. code:: python
- `logger`: `logger.warning(fmt, error, delay)` will be called on failed attempts. default: retry.logging_logger. if None, logging is disabled.

@retry()
def make_trouble():
'''Retry until succeed'''
- `fail_callback`: `fail_callback(e)` will be called after failed attempts.
- `condition`: `condition` is a construct that has ...acquire / ...release
and ...wait(n_seconds)

.. code:: python

@retry(ZeroDivisionError, tries=3, delay=2)
def make_trouble():
'''Retry on ZeroDivisionError, raise error after 3 attempts, sleep 2 seconds between attempts.'''
#### Examples
```python
from reretry import retry

.. code:: python
@retry()
def make_trouble():
'''Retry until succeeds'''

@retry((ValueError, TypeError), delay=1, backoff=2)
def make_trouble():
'''Retry on ValueError or TypeError, sleep 1, 2, 4, 8, ... seconds between attempts.'''
@retry()
async def async_make_trouble():
'''Retry an async function until it succeeds'''

.. code:: python
@retry(ZeroDivisionError, tries=3, delay=2)
def make_trouble():
'''Retry on ZeroDivisionError, raise error after 3 attempts,
sleep 2 seconds between attempts.'''

@retry((ValueError, TypeError), delay=1, backoff=2, max_delay=4)
def make_trouble():
'''Retry on ValueError or TypeError, sleep 1, 2, 4, 4, ... seconds between attempts.'''
@retry((ValueError, TypeError), delay=1, backoff=2)
def make_trouble():
'''Retry on ValueError or TypeError, sleep 1, 2, 4, 8, ... seconds between attempts.'''

.. code:: python
@retry((ValueError, TypeError), delay=1, backoff=2, max_delay=4)
def make_trouble():
'''Retry on ValueError or TypeError, sleep 1, 2, 4, 4, ... seconds between attempts.'''

@retry(ValueError, delay=1, jitter=1)
def make_trouble():
'''Retry on ValueError, sleep 1, 2, 3, 4, ... seconds between attempts.'''
@retry(ValueError, delay=1, jitter=1)
def make_trouble():
'''Retry on ValueError, sleep 1, 2, 3, 4, ... seconds between attempts.'''

.. code:: python
def callback(e: Exception):
'''Print error message'''
print(e)

# If you enable logging, you can get warnings like 'ValueError, retrying in
# 1 seconds'
if __name__ == '__main__':
import logging
logging.basicConfig()
make_trouble()
@retry(ValueError, fail_callback=callback):
def make_trouble():
'''Retry on ValueError, between attempts call callback(e)
(where e is the Exception raised).'''

retry_call
^^^^^^^^^^
# If you enable logging, you can get warnings like 'ValueError, retrying in
# 1 seconds'
if __name__ == '__main__':
import logging
logging.basicConfig()
make_trouble()
```

.. code:: python

def retry_call(f, fargs=None, fkwargs=None, exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1,
jitter=0,
logger=logging_logger):
"""
Calls a function and re-executes it if it failed.

:param f: the function to execute.
:param fargs: the positional arguments of the function to execute.
:param fkwargs: the named arguments of the function to execute.
:param exceptions: an exception or a tuple of exceptions to catch. default: Exception.
:param tries: the maximum number of attempts. default: -1 (infinite).
:param delay: initial delay between attempts. default: 0.
:param max_delay: the maximum value of delay. default: None (no limit).
:param backoff: multiplier applied to delay between attempts. default: 1 (no backoff).
:param jitter: extra seconds added to delay between attempts. default: 0.
fixed if a number, random if a range tuple (min, max)
:param logger: logger.warning(fmt, error, delay) will be called on failed attempts.
default: retry.logging_logger. if None, logging is disabled.
:returns: the result of the f function.
"""
### The `retry_call` function
Calls a function and re-executes it if it failed.

This is very similar to the decorator, except that it takes a function and its arguments as parameters. The use case behind it is to be able to dynamically adjust the retry arguments.

.. code:: python

import requests

from retry.api import retry_call


def make_trouble(service, info=None):
if not info:
info = ''
r = requests.get(service + info)
return r.text


def what_is_my_ip(approach=None):
if approach == "optimistic":
tries = 1
elif approach == "conservative":
tries = 3
else:
# skeptical
tries = -1
result = retry_call(make_trouble, fargs=["http://ipinfo.io/"], fkwargs={"info": "ip"}, tries=tries)
print(result)

what_is_my_ip("conservative")



#### Usage
`retry_call(f, fargs=None, fkwargs=None, exceptions=Exception, tries=-1, delay=0, max_delay=None, backoff=1, jitter=0, show_traceback=False, logger=logging_logger, fail_callback=None, condition=threading.Condition())`

#### Example
```python
import requests

from reretry.api import retry_call


def make_trouble(service, info=None):
if not info:
info = ''
r = requests.get(service + info)
return r.text


def what_is_my_ip(approach=None):
if approach == "optimistic":
tries = 1
elif approach == "conservative":
tries = 3
else:
# skeptical
tries = -1
result = retry_call(
make_trouble,
fargs=["http://ipinfo.io/"],
fkwargs={"info": "ip"},
tries=tries
)
print(result)

what_is_my_ip("conservative")
```
File renamed without changes.
Loading