Giter Club home page Giter Club logo

tenacity's Introduction

Tenacity

https://circleci.com/gh/jd/tenacity.svg?style=svg Mergify Status

Please refer to the tenacity documentation for a better experience.

Tenacity is an Apache 2.0 licensed general-purpose retrying library, written in Python, to simplify the task of adding retry behavior to just about anything. It originates from a fork of retrying which is sadly no longer maintained. Tenacity isn't api compatible with retrying but adds significant new functionality and fixes a number of longstanding bugs.

The simplest use case is retrying a flaky function whenever an Exception occurs until a value is returned.

.. testcode::

    import random
    from tenacity import retry

    @retry
    def do_something_unreliable():
        if random.randint(0, 10) > 1:
            raise IOError("Broken sauce, everything is hosed!!!111one")
        else:
            return "Awesome sauce!"

    print(do_something_unreliable())

.. testoutput::
   :hide:

   Awesome sauce!


.. toctree::
    :hidden:
    :maxdepth: 2

    changelog
    api


Features

  • Generic Decorator API
  • Specify stop condition (i.e. limit by number of attempts)
  • Specify wait condition (i.e. exponential backoff sleeping between attempts)
  • Customize retrying on Exceptions
  • Customize retrying on expected returned result
  • Retry on coroutines
  • Retry code block with context manager

Installation

To install tenacity, simply:

$ pip install tenacity

Examples

Basic Retry

.. testsetup::

    import logging
    #
    # Note the following import is used for demonstration convenience only.
    # Production code should always explicitly import the names it needs.
    #
    from tenacity import *

    class MyException(Exception):
        pass

As you saw above, the default behavior is to retry forever without waiting when an exception is raised.

.. testcode::

    @retry
    def never_gonna_give_you_up():
        print("Retry forever ignoring Exceptions, don't wait between retries")
        raise Exception

Stopping

Let's be a little less persistent and set some boundaries, such as the number of attempts before giving up.

.. testcode::

    @retry(stop=stop_after_attempt(7))
    def stop_after_7_attempts():
        print("Stopping after 7 attempts")
        raise Exception

We don't have all day, so let's set a boundary for how long we should be retrying stuff.

.. testcode::

    @retry(stop=stop_after_delay(10))
    def stop_after_10_s():
        print("Stopping after 10 seconds")
        raise Exception

If you're on a tight deadline, and exceeding your delay time isn't ok, then you can give up on retries one attempt before you would exceed the delay.

.. testcode::

    @retry(stop=stop_before_delay(10))
    def stop_before_10_s():
        print("Stopping 1 attempt before 10 seconds")
        raise Exception

You can combine several stop conditions by using the | operator:

.. testcode::

    @retry(stop=(stop_after_delay(10) | stop_after_attempt(5)))
    def stop_after_10_s_or_5_retries():
        print("Stopping after 10 seconds or 5 retries")
        raise Exception

Waiting before retrying

Most things don't like to be polled as fast as possible, so let's just wait 2 seconds between retries.

.. testcode::

    @retry(wait=wait_fixed(2))
    def wait_2_s():
        print("Wait 2 second between retries")
        raise Exception

Some things perform best with a bit of randomness injected.

.. testcode::

    @retry(wait=wait_random(min=1, max=2))
    def wait_random_1_to_2_s():
        print("Randomly wait 1 to 2 seconds between retries")
        raise Exception

Then again, it's hard to beat exponential backoff when retrying distributed services and other remote endpoints.

.. testcode::

    @retry(wait=wait_exponential(multiplier=1, min=4, max=10))
    def wait_exponential_1():
        print("Wait 2^x * 1 second between each retry starting with 4 seconds, then up to 10 seconds, then 10 seconds afterwards")
        raise Exception


Then again, it's also hard to beat combining fixed waits and jitter (to help avoid thundering herds) when retrying distributed services and other remote endpoints.

.. testcode::

    @retry(wait=wait_fixed(3) + wait_random(0, 2))
    def wait_fixed_jitter():
        print("Wait at least 3 seconds, and add up to 2 seconds of random delay")
        raise Exception

When multiple processes are in contention for a shared resource, exponentially increasing jitter helps minimise collisions.

.. testcode::

    @retry(wait=wait_random_exponential(multiplier=1, max=60))
    def wait_exponential_jitter():
        print("Randomly wait up to 2^x * 1 seconds between each retry until the range reaches 60 seconds, then randomly up to 60 seconds afterwards")
        raise Exception


Sometimes it's necessary to build a chain of backoffs.

.. testcode::

    @retry(wait=wait_chain(*[wait_fixed(3) for i in range(3)] +
                           [wait_fixed(7) for i in range(2)] +
                           [wait_fixed(9)]))
    def wait_fixed_chained():
        print("Wait 3s for 3 attempts, 7s for the next 2 attempts and 9s for all attempts thereafter")
        raise Exception

Whether to retry

We have a few options for dealing with retries that raise specific or general exceptions, as in the cases here.

.. testcode::

    class ClientError(Exception):
        """Some type of client error."""

    @retry(retry=retry_if_exception_type(IOError))
    def might_io_error():
        print("Retry forever with no wait if an IOError occurs, raise any other errors")
        raise Exception

    @retry(retry=retry_if_not_exception_type(ClientError))
    def might_client_error():
        print("Retry forever with no wait if any error other than ClientError occurs. Immediately raise ClientError.")
        raise Exception

We can also use the result of the function to alter the behavior of retrying.

.. testcode::

    def is_none_p(value):
        """Return True if value is None"""
        return value is None

    @retry(retry=retry_if_result(is_none_p))
    def might_return_none():
        print("Retry with no wait if return value is None")

See also these methods:

.. testcode::

    retry_if_exception
    retry_if_exception_type
    retry_if_not_exception_type
    retry_unless_exception_type
    retry_if_result
    retry_if_not_result
    retry_if_exception_message
    retry_if_not_exception_message
    retry_any
    retry_all

We can also combine several conditions:

.. testcode::

    def is_none_p(value):
        """Return True if value is None"""
        return value is None

    @retry(retry=(retry_if_result(is_none_p) | retry_if_exception_type()))
    def might_return_none():
        print("Retry forever ignoring Exceptions with no wait if return value is None")

Any combination of stop, wait, etc. is also supported to give you the freedom to mix and match.

It's also possible to retry explicitly at any time by raising the TryAgain exception:

.. testcode::

   @retry
   def do_something():
       result = something_else()
       if result == 23:
          raise TryAgain

Error Handling

Normally when your function fails its final time (and will not be retried again based on your settings), a RetryError is raised. The exception your code encountered will be shown somewhere in the middle of the stack trace.

If you would rather see the exception your code encountered at the end of the stack trace (where it is most visible), you can set reraise=True.

.. testcode::

    @retry(reraise=True, stop=stop_after_attempt(3))
    def raise_my_exception():
        raise MyException("Fail")

    try:
        raise_my_exception()
    except MyException:
        # timed out retrying
        pass

Before and After Retry, and Logging

It's possible to execute an action before any attempt of calling the function by using the before callback function:

.. testcode::

    import logging
    import sys

    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

    logger = logging.getLogger(__name__)

    @retry(stop=stop_after_attempt(3), before=before_log(logger, logging.DEBUG))
    def raise_my_exception():
        raise MyException("Fail")

In the same spirit, It's possible to execute after a call that failed:

.. testcode::

    import logging
    import sys

    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

    logger = logging.getLogger(__name__)

    @retry(stop=stop_after_attempt(3), after=after_log(logger, logging.DEBUG))
    def raise_my_exception():
        raise MyException("Fail")

It's also possible to only log failures that are going to be retried. Normally retries happen after a wait interval, so the keyword argument is called before_sleep:

.. testcode::

    import logging
    import sys

    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

    logger = logging.getLogger(__name__)

    @retry(stop=stop_after_attempt(3),
           before_sleep=before_sleep_log(logger, logging.DEBUG))
    def raise_my_exception():
        raise MyException("Fail")


Statistics

You can access the statistics about the retry made over a function by using the statistics attribute attached to the function:

.. testcode::

    @retry(stop=stop_after_attempt(3))
    def raise_my_exception():
        raise MyException("Fail")

    try:
        raise_my_exception()
    except Exception:
        pass

    print(raise_my_exception.statistics)

.. testoutput::
   :hide:

   ...

Custom Callbacks

You can also define your own callbacks. The callback should accept one parameter called retry_state that contains all information about current retry invocation.

For example, you can call a custom callback function after all retries failed, without raising an exception (or you can re-raise or do anything really)

.. testcode::

    def return_last_value(retry_state):
        """return the result of the last call attempt"""
        return retry_state.outcome.result()

    def is_false(value):
        """Return True if value is False"""
        return value is False

    # will return False after trying 3 times to get a different result
    @retry(stop=stop_after_attempt(3),
           retry_error_callback=return_last_value,
           retry=retry_if_result(is_false))
    def eventually_return_false():
        return False

RetryCallState

retry_state argument is an object of :class:`~tenacity.RetryCallState` class.

Other Custom Callbacks

It's also possible to define custom callbacks for other keyword arguments.

.. function:: my_stop(retry_state)

   :param RetryCallState retry_state: info about current retry invocation
   :return: whether or not retrying should stop
   :rtype: bool

.. function:: my_wait(retry_state)

   :param RetryCallState retry_state: info about current retry invocation
   :return: number of seconds to wait before next retry
   :rtype: float

.. function:: my_retry(retry_state)

   :param RetryCallState retry_state: info about current retry invocation
   :return: whether or not retrying should continue
   :rtype: bool

.. function:: my_before(retry_state)

   :param RetryCallState retry_state: info about current retry invocation

.. function:: my_after(retry_state)

   :param RetryCallState retry_state: info about current retry invocation

.. function:: my_before_sleep(retry_state)

   :param RetryCallState retry_state: info about current retry invocation

Here's an example with a custom before_sleep function:

.. testcode::

    import logging

    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

    logger = logging.getLogger(__name__)

    def my_before_sleep(retry_state):
        if retry_state.attempt_number < 1:
            loglevel = logging.INFO
        else:
            loglevel = logging.WARNING
        logger.log(
            loglevel, 'Retrying %s: attempt %s ended with: %s',
            retry_state.fn, retry_state.attempt_number, retry_state.outcome)

    @retry(stop=stop_after_attempt(3), before_sleep=my_before_sleep)
    def raise_my_exception():
        raise MyException("Fail")

    try:
        raise_my_exception()
    except RetryError:
        pass


Changing Arguments at Run Time

You can change the arguments of a retry decorator as needed when calling it by using the retry_with function attached to the wrapped function:

.. testcode::

    @retry(stop=stop_after_attempt(3))
    def raise_my_exception():
        raise MyException("Fail")

    try:
        raise_my_exception.retry_with(stop=stop_after_attempt(4))()
    except Exception:
        pass

    print(raise_my_exception.statistics)

.. testoutput::
   :hide:

   ...

If you want to use variables to set up the retry parameters, you don't have to use the retry decorator - you can instead use Retrying directly:

.. testcode::

    def never_good_enough(arg1):
        raise Exception('Invalid argument: {}'.format(arg1))

    def try_never_good_enough(max_attempts=3):
        retryer = Retrying(stop=stop_after_attempt(max_attempts), reraise=True)
        retryer(never_good_enough, 'I really do try')

You may also want to change the behaviour of a decorated function temporarily, like in tests to avoid unnecessary wait times. You can modify/patch the retry attribute attached to the function. Bear in mind this is a write-only attribute, statistics should be read from the function statistics attribute.

.. testcode::

    @retry(stop=stop_after_attempt(3), wait=wait_fixed(3))
    def raise_my_exception():
        raise MyException("Fail")

    from unittest import mock

    with mock.patch.object(raise_my_exception.retry, "wait", wait_fixed(0)):
        try:
            raise_my_exception()
        except Exception:
            pass

    print(raise_my_exception.statistics)

.. testoutput::
   :hide:

   ...

Retrying code block

Tenacity allows you to retry a code block without the need to wraps it in an isolated function. This makes it easy to isolate failing block while sharing context. The trick is to combine a for loop and a context manager.

.. testcode::

   from tenacity import Retrying, RetryError, stop_after_attempt

   try:
       for attempt in Retrying(stop=stop_after_attempt(3)):
           with attempt:
               raise Exception('My code is failing!')
   except RetryError:
       pass

You can configure every details of retry policy by configuring the Retrying object.

With async code you can use AsyncRetrying.

.. testcode::

   from tenacity import AsyncRetrying, RetryError, stop_after_attempt

   async def function():
      try:
          async for attempt in AsyncRetrying(stop=stop_after_attempt(3)):
              with attempt:
                  raise Exception('My code is failing!')
      except RetryError:
          pass

In both cases, you may want to set the result to the attempt so it's available in retry strategies like retry_if_result. This can be done accessing the retry_state property:

.. testcode::

    from tenacity import AsyncRetrying, retry_if_result

    async def function():
       async for attempt in AsyncRetrying(retry=retry_if_result(lambda x: x < 3)):
           with attempt:
               result = 1  # Some complex calculation, function call, etc.
           if not attempt.retry_state.outcome.failed:
               attempt.retry_state.set_result(result)
       return result

Async and retry

Finally, retry works also on asyncio, Trio, and Tornado (>= 4.5) coroutines. Sleeps are done asynchronously too.

@retry
async def my_asyncio_function(loop):
    await loop.getaddrinfo('8.8.8.8', 53)
@retry
async def my_async_trio_function():
    await trio.socket.getaddrinfo('8.8.8.8', 53)
@retry
@tornado.gen.coroutine
def my_async_tornado_function(http_client, url):
    yield http_client.fetch(url)

You can even use alternative event loops such as curio by passing the correct sleep function:

@retry(sleep=curio.sleep)
async def my_async_curio_function():
    await asks.get('https://example.org')

Contribute

  1. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug.
  2. Fork the repository on GitHub to start making your changes to the main branch (or branch off of it).
  3. Write a test which shows that the bug was fixed or that the feature works as expected.
  4. Add a changelog
  5. Make the docs better (or more detailed, or more easier to read, or ...)

Changelogs

reno is used for managing changelogs. Take a look at their usage docs.

The doc generation will automatically compile the changelogs. You just need to add them.

# Opens a template file in an editor
tox -e reno -- new some-slug-for-my-change --edit

tenacity's People

Contributors

alexwlchan avatar and-semakin avatar andersea avatar asqui avatar bersace avatar bodenr avatar brian-williams avatar cyounkins avatar dependabot[bot] avatar harlowja avatar hasier avatar hugovk avatar immerrr avatar isaacg avatar jackdesert avatar jakkdl avatar jd avatar mergify[bot] avatar mezgerj avatar miracle2k avatar penguinolog avatar rholder avatar sileht avatar simondolle avatar theopilbeam avatar tipabu avatar webknjaz avatar william-silversmith avatar wouldyoukindly avatar zaneb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tenacity's Issues

Trouble building rpms on Centos 7.3

I am trying to build the rpm for this project for python 2.7
when running command:
python setup.py bdist_rpm --python /usr/bin/python

I receive the error

+ cd tenacity-4.4.1.dev1
+ /usr/bin/python setup.py install --single-version-externally-managed -O1 --root=/home/jnaulty/workspace/tenacity/build/bdist.linux-x86_64/rpm/BUILDROOT/tenacity-4.4.1.dev1-1.x86_64 --record=INSTALLED_FILES
  File "/usr/lib/python2.7/site-packages/tenacity/tests/test_async.py", line 39
    yield from asyncio.sleep(0.00001)
             ^
SyntaxError: invalid syntax

  File "/usr/lib/python2.7/site-packages/tenacity/async.py", line 44
    result = yield from fn(*args, **kwargs)
                      ^
SyntaxError: invalid syntax

  File "/usr/lib/python2.7/site-packages/tenacity/tests/test_async.py", line 39
    yield from asyncio.sleep(0.00001)
             ^
SyntaxError: invalid syntax

  File "/usr/lib/python2.7/site-packages/tenacity/async.py", line 44
    result = yield from fn(*args, **kwargs)
                      ^
SyntaxError: invalid syntax

+ /usr/lib/rpm/find-debuginfo.sh --strict-build-id -m --run-dwz --dwz-low-mem-die-limit 10000000 --dwz-max-die-limit 110000000 /home/jnaulty/workspace/tenacity/build/bdist.linux-x86_64/rpm/BUILD/tenacity-4.4.1.dev1
find: 'debug': No such file or directory
+ /usr/lib/rpm/check-buildroot
+ /usr/lib/rpm/redhat/brp-compress
+ /usr/lib/rpm/redhat/brp-strip-static-archive /usr/bin/strip
+ /usr/lib/rpm/brp-python-bytecompile /usr/bin/python 1
error: Bad exit status from /var/tmp/rpm-tmp.8cn8w2 (%install)
    Bad exit status from /var/tmp/rpm-tmp.8cn8w2 (%install)
error: command 'rpmbuild' failed with exit status 1

What is the workaround for this?

Retrying compatibility: seconds vs. ms

By default seconds are used for most stop/retry predicates in tenacity right now. However in retrying ms was the default.

While I don't disagree with seconds being the base of choice here, it causes more work for anyone migrating from retrying -> tenacity.

For example migrating retrying code like [1] to tenacity requires the measurements parameters passed to be changed to account for the fact that tenacity uses seconds.

I assume this is intended correct, and folks migrating just have to deal with it?

[1] https://github.com/openstack/neutron/blob/master/neutron/agent/common/ovs_lib.py#L80

weird installation error with 2.7.1: tenacity-3.7.1/setup.cfg: TypeError: dist must be a Distribution instance',)

I got this weird error here:

File "/home/ubuntu/virtualenvs/venv-system/lib/python2.7/site-packages/pbr/core.py", line 119, in pbr
    'Error parsing %s: %s: %s' % (path, e.__class__.__name__, e))
setuptools.sandbox.UnpickleableException: DistutilsSetupError('Error parsing /tmp/easy_install-81hJTS/tenacity-3.7.1/setup.cfg: TypeError: dist must be a Distribution instance',)

tenacity is only listed as a dependency so I am not sure what causes it but I suspect that its setup.cfg may be more than distutils can handle.

Documentation examples don't work

When only mentioning @Retry decorator, the function retry only when exception raises.
Both of the first two examples (never_give_up_never_surrender and stop_after_7_attempts) suppose to run forever but run only once.

What is the definition for failure? exception only?

Thanks

add to docs how to retrieve the actual function Exception

example:

@retry(stop=stop_after_attempt(1))
def divisionByZero():
   print 1/0

try:
    divisionByZero
except:
    except Exception as e: 
        print e.last_attempt.exception()
       ### or  e.reraise

Also would be nice if it could be added a functionality that catches the last exception so We won't have to add try/except controls. What I mean is:

@retry(onFiinalException=userFunction)

def userFunction(Exception):
   #gets the final exception and does something 
   logger.error(Exception)
    logger.error(e.reraise)

Optionally calling sleep/after on no retry

Today tenacity only invokessleep or after if a called function will retry [1].

What are your thoughts on making these configurable to run even if there's no retry?

Potential use cases might be introducing delay on certain conditions and/or invoking after hooks. For example [2].

Or even something as simple as:

# introduce a random sleep when a timeout occurs and log it
@retry(retry= retry_if_exception_type(TimeoutErr),  wait= wait_random(), retry=retry_none(), after=log_timeout)
def might_raise_timeout(...):

While this usage may teeter the definition of retrying, it does provide some additional use cases.

[1] https://github.com/jd/tenacity/blob/master/tenacity/__init__.py#L187
[2] https://github.com/openstack/neutron/blob/master/neutron/common/rpc.py#L128

Retry for Asynchronous Generator (PEP 525)

Could we support retry for Asynchronous Generator?

import asyncio
import random
from tenacity import retry

@retry
async def ticker(delay, to):
    """Yield numbers from 0 to `to` every `delay` seconds."""
    for i in range(to):
        if random.randint(0, 10) > 1:
            raise IOError("Broken sauce, everything is hosed!!!111one")
        else:
            yield i
        await asyncio.sleep(delay)

async for i in ticker(1, 10):
    print(i)

See: PEP 525

Upload wheels to PyPI

tenacity can build as a universal wheel, but it seems like these wheels aren't being upload to PyPI. Pretty please upload wheels? :)

Discussion: Retry hook for logging or other

One of the PRs I had on retrying [1] was to support a generic wait hook, suitable for logging or other per-retry operations.

I was looking at tenacity and wanted to confirm how I'd do this:

def logged_wait(*args, **kwargs):
    # no-op wait just to log on each attempt
    print("Yet another logged attempt")
    return 0


@retry(wait=wait_combine(logged_wait, wait_fixed(1000)), ...)
def my_retried_func():
    # interesting retryable stuff here

Is this about right?

[1] rholder/retrying#50

raising TryAgain when the retrying should be stopped causes deadlock

I have a simple function - it checks if a request result is a certain value and if not raises TryAgain to try again.
I'm not sure why, but after the function should stop retrying, it enters a deadlock when TryAgain is raised - no new attempts are made and function doesn't return.
This simple example demonstrate it:

@tenacity.retry(stop=stop_after_delay(10),wait=wait_fixed(1))
def some_function():
    raise tenacity.TryAgain()

I'm using python 3.5.2.

Can't install Win7 python 2.7.13

I'm unable to install via pip or setup.py due to dependency issues with altgraph.compat. Looking at altgraph's change history it would seem that compat was removed a while ago v0.71.

>python setup.py install
> ERROR:root:Error parsing
> Traceback (most recent call last):
>   File "C:\Python27\lib\site-packages\pbr\core.py", line 111, in pbr
>     attrs = util.cfg_to_args(path, dist.script_args)
>   File "C:\Python27\lib\site-packages\pbr\util.py", line 267, in cfg_to_args
>     wrap_commands(kwargs)
>   File "C:\Python27\lib\site-packages\pbr\util.py", line 569, in wrap_commands
>     cmdclass = ep.resolve()
>   File "C:\Python27\lib\site-packages\pkg_resources\__init__.py", line 2297, in
> resolve
>     module = __import__(self.module_name, fromlist=['__name__'], level=0)
>   File "C:\Python27\lib\site-packages\bbfreeze\__init__.py", line 8, in <module>
> 
>     from bbfreeze.freezer import Freezer
>   File "C:\Python27\lib\site-packages\bbfreeze\freezer.py", line 13, in <module>
> 
>     from modulegraph import modulegraph
>   File "C:\Python27\lib\site-packages\bbfreeze\modulegraph\modulegraph.py", line
>  24, in <module>
>     from altgraph.compat import *
> ImportError: No module named compat
> error in setup command: Error parsing C:\Users\bmeredyk\Downloads\Python\tenacit
> y\tenacity-4.4.0\setup.cfg: ImportError: No module named compat

Installing with pip also errors due to altgraph.compat

>>pip install tenacity
> Collecting tenacity
>   Using cached tenacity-4.4.0.tar.gz
>     Complete output from command python setup.py egg_info:
>     ERROR:root:Error parsing
>     Traceback (most recent call last):
>       File "c:\python27\lib\site-packages\pbr\core.py", line 111, in pbr
>         attrs = util.cfg_to_args(path, dist.script_args)
>       File "c:\python27\lib\site-packages\pbr\util.py", line 267, in cfg_to_args
> 
>         wrap_commands(kwargs)
>       File "c:\python27\lib\site-packages\pbr\util.py", line 569, in wrap_comman
> ds
>         cmdclass = ep.resolve()
>       File "c:\python27\lib\site-packages\pkg_resources\__init__.py", line 2297,
>  in resolve
>         module = __import__(self.module_name, fromlist=['__name__'], level=0)
>       File "c:\python27\lib\site-packages\bbfreeze\__init__.py", line 8, in <mod
> ule>
>         from bbfreeze.freezer import Freezer
>       File "c:\python27\lib\site-packages\bbfreeze\freezer.py", line 13, in <mod
> ule>
>         from modulegraph import modulegraph
>       File "c:\python27\lib\site-packages\bbfreeze\modulegraph\modulegraph.py",
> line 24, in <module>
>         from altgraph.compat import *
>     ImportError: No module named compat
>     error in setup command: Error parsing c:\users\bmeredyk\appdata\local\temp\p
> ip-build-8hde6b\tenacity\setup.cfg: ImportError: No module named compat
> 
>     ----------------------------------------
> Command "python setup.py egg_info" failed with error code 1 in c:\users\bmeredyk
> \appdata\local\temp\pip-build-8hde6b\tenacity\
> 

Multiple "stop" conditions

Is this possible? For example, 7 attempts with 10 seconds timeout each.
The documentation is unclear of it. :c

Retries count

Is it possible to access the number of retries which have occurred?

any plan to support python 2.7?

Hi,

ur module tenacity is quite useful actually. but noticed it depends on concurrent module in python 3.
so I wonder if any plan to support python 2.7? that would help for old systems.

Regards

Provide a generator decorator

Would it make sense to have a @retry variant that better support generator functions?

Something like:

def retry_generator(max_retries, exc_type=Exception, exc_check=None):
    def wrap(gen):
        def wrapped(*args, **kwargs):
            retry = 0
            rpc_error = None
            while retry <= max_retries:
                try:
                    for i in gen(*args, **kwargs):
                        yield i
                    return
                except exc_type as e:
                    if exc_check and exc_check(e):
                        retry += 1
                        rpc_error = e
                        continue
                    raise
            raise rpc_error
        return wrapped
    return wrap

implement @retry_once and @retry_twice

Because the default behaviour is to retry forever I would like to suggest implementing @retry_once and @retry_twice as much easier to use alternatives.

Changelog?

Please excuse me if this exists and I just couldn't find it – but is there a changelog available somewhere? The releases at https://github.com/jd/tenacity/releases are not annotated and I couldn't find anything like a changelog in the documentation (http://tenacity.readthedocs.io/).

A number of tools exist that can automatically generate changelog entries from commit history – at a minimum it would be helpful to have something like this so that consumers of tenacity can understand what we're getting when we upgrade from one version to another.

Discussion: Test util for mocking time.sleep()

The way retrying is written and captures time.sleep() in a closure within Retrying.init(), consumers cannot patch time.sleep() in their UT code.

To get around this I've been doing some ad-hoc mocking like [1]. While this works I have to replicate it in each openstack project.

Is there some way to can devise for consumers to more easily mockout time.sleep() for their UTs? Maybe there's an easy way to do it as-is and I'm missing it.

[1] https://review.openstack.org/#/c/368235/2/cinder/tests/unit/test_utils.py@38

Tests take forever

I think some of the time changes may not have been reflected in tests (I didnt wait for the below to finish, cause that might have meant a few days, haha): thus the tests take long like:

$ /usr/bin/time -v nosetests -x -v -s
test_after_attempts (tenacity.tests.test_tenacity.TestBeforeAfterAttempts) ... ok
test_before_attempts (tenacity.tests.test_tenacity.TestBeforeAfterAttempts) ... ok
test_defaults (tenacity.tests.test_tenacity.TestDecoratorWrapper) ... ok
test_retry_if_exception_of_type (tenacity.tests.test_tenacity.TestDecoratorWrapper) ... Hi there, I'm a NameError
Hi there, I'm a NameError
ok
test_with_stop_on_exception (tenacity.tests.test_tenacity.TestDecoratorWrapper) ... Hi there, I'm an IOError
ok
test_with_stop_on_return_value (tenacity.tests.test_tenacity.TestDecoratorWrapper) ... RetryError[<Future at 0x7f8c6a0bfe10 state=finished returned NoneType>]
ok
test_with_wait (tenacity.tests.test_tenacity.TestDecoratorWrapper) ... ok
test_retry_any (tenacity.tests.test_tenacity.TestRetryConditions) ... ok
test_retry_try_again (tenacity.tests.test_tenacity.TestRetryConditions) ... ok
test_legacy_explicit_stop_type (tenacity.tests.test_tenacity.TestStopConditions) ... ok
test_never_stop (tenacity.tests.test_tenacity.TestStopConditions) ... ok
test_stop_after_attempt (tenacity.tests.test_tenacity.TestStopConditions) ... ok
test_stop_after_delay (tenacity.tests.test_tenacity.TestStopConditions) ... ok
test_stop_func (tenacity.tests.test_tenacity.TestStopConditions) ... ok
test_exponential (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_exponential_with_max_wait (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_exponential_with_max_wait_and_multiplier (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_fixed_sleep (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_incrementing_sleep (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_legacy_explicit_wait_type (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_no_sleep (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_random_sleep (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_random_sleep_without_min (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_wait_chain (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_wait_combine (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_wait_func (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
test_wait_jitter (tenacity.tests.test_tenacity.TestWaitConditions) ... ok
^C
----------------------------------------------------------------------
Ran 27 tests in 142.524s

OK
        Command being timed: "nosetests -x -v -s"
        User time (seconds): 1.18
        System time (seconds): 0.04
        Percent of CPU this job got: 0%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 2:22.81

How is this package different from retrying?

What was the purpose of forking the retrying repo? This project's README is identical besides mentioning:

It originates from a fork of Retrying

It might be helpful to add more documentation + a changelog that makes it easier for new users to decide whether to use this package vs. retrying.

Allow to raise custom Exception on failure

Request to support raising a custom Exception on failure.

e.g.

@retry(stop=stop_after_attempt(7), raise=Exception("Retry failed"))
def stop_after_7_attempts():
    print("Stopping after 7 attempts")
    raise Exception("Error")

stop_after_7_attempts()

With output:
Stopping after 7 attempts
Stopping after 7 attempts
Stopping after 7 attempts
Stopping after 7 attempts
Stopping after 7 attempts
Stopping after 7 attempts
Stopping after 7 attempts
Exception: Retry failed

Get away from using milliseconds everywhere

The usage of milliseconds never quite seemed to make sense (all time routines in python take floating point numbers) to myself, so I"m not really such a big fan of keeping with that. Thoughts about moving away from that model and just using the normal floating point numbers to do time tracking and such (not forcing everything into milliseconds?); perhaps if really needed we can make it configurable (although I'd rather not).

Provide a context manager

I like the idea from https://github.com/bhearsum/redo to have retrying contextmanager:

from tenacity import retry
from tenacity.stop import stop_after_attempt


@contextmanager
def retrying(func, *retry_args, **retry_kwargs):
    yield retry(*retry_args, **retry_kwargs)(func)


def foo(max_count):
    global count
    count += 1
    print(count)
    if count < max_count:
        raise ValueError("count too small")
    return "success!"

count = 0
ret = None
max_attempt=5
with retrying(foo, stop=stop_after_attempt(max_attempt)) as f:
    ret = f(3)
print(ret)

Do you think is may be included in the tenacity? I have no clue if it works with async and futures.

Do not use debtcollector

debtcollector is used for just one warning, and it requires to install 2 more packages pbr, wrapt. It's useful for large projects, but for such small library, like tenacity, it's IMHO better to keep it's dependencies small and write the DeprecationWarning programatically.

Installation issue on macOS-10.12.6 (version 4.4.0)

Hello, I intended to use this pretty cool library but I encountered the following error while installing it on my Mac (Python 2.7.13)
Any help would be much appreciated ! Thx ;)

pip install tenacity --user
Collecting tenacity
  Using cached tenacity-4.4.0.tar.gz
    Complete output from command python setup.py egg_info:
    ERROR:root:Error parsing
    Traceback (most recent call last):
      File "/Users/jcdevil/Dev/Forgerock/Stash/PyForge/archives/third_party/lib/python/site-packages/pbr/core.py", line 111, in pbr
        attrs = util.cfg_to_args(path, dist.script_args)
      File "/Users/jcdevil/Dev/Forgerock/Stash/PyForge/archives/third_party/lib/python/site-packages/pbr/util.py", line 267, in cfg_to_args
        wrap_commands(kwargs)
      File "/Users/jcdevil/Dev/Forgerock/Stash/PyForge/archives/third_party/lib/python/site-packages/pbr/util.py", line 569, in wrap_commands
        cmdclass = ep.resolve()
      File "/usr/local/lib/python2.7/site-packages/pkg_resources/__init__.py", line 2297, in resolve
        module = __import__(self.module_name, fromlist=['__name__'], level=0)
      File "/usr/local/lib/python2.7/site-packages/sphinx/setup_command.py", line 23, in <module>
        from sphinx.application import Sphinx
      File "/usr/local/lib/python2.7/site-packages/sphinx/application.py", line 25, in <module>
        from docutils import nodes
    ImportError: No module named docutils
    error in setup command: Error parsing /private/var/folders/fs/b8r0x9ts6pq9nm_b50rzvwh00000gn/T/pip-build-MtmpTa/tenacity/setup.cfg: ImportError: No module named docutils

    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/fs/b8r0x9ts6pq9nm_b50rzvwh00000gn/T/pip-build-MtmpTa/tenacity/

dead code reported by vulture

We used vulture (https://github.com/jendrikseipp/vulture) to search for unused code in your project. You can find the report below. It would be great if you could give us feedback about which items are actually used or unused. This would allow us to improve vulture and ideally it also helps you to remove obsolete code or even find typos and bugs.

Command:

vulture tenacity

Raw Results:

tenacity/doc/source/conf.py:1: Unused variable 'master_doc'
tenacity/doc/source/conf.py:2: Unused variable 'project'
tenacity/tenacity/__init__.py:34: Unused import 'retry_always'
tenacity/tenacity/__init__.py:38: Unused import 'retry_if_not_result'
tenacity/tenacity/__init__.py:39: Unused import 'retry_if_result'
tenacity/tenacity/__init__.py:40: Unused import 'retry_never'
tenacity/tenacity/__init__.py:43: Unused import 'stop_after_attempt'
tenacity/tenacity/__init__.py:44: Unused import 'stop_after_delay'
tenacity/tenacity/__init__.py:50: Unused import 'wait_chain'
tenacity/tenacity/__init__.py:52: Unused import 'wait_exponential'
tenacity/tenacity/__init__.py:54: Unused import 'wait_incrementing'
tenacity/tenacity/__init__.py:56: Unused import 'wait_random'
tenacity/tenacity/__init__.py:59: Unused import 'before_log'
tenacity/tenacity/__init__.py:63: Unused import 'after_log'
tenacity/tenacity/after.py:24: Unused function 'after_log'
tenacity/tenacity/before.py:24: Unused function 'before_log'
tenacity/tenacity/retry.py:52: Unused variable 'retry_always'
tenacity/tenacity/tests/test_tenacity.py:429: Unused function '_retryable_test_with_exception_type_io_attempt_limit'
tenacity/tenacity/tests/test_tenacity.py:451: Unused function '_retryable_test_with_exception_type_custom_attempt_limit'
tenacity/tenacity/tests/test_tenacity.py:536: Unused variable 'trial_time_taken_ms'

Obvious False positives:

tenacity/tenacity/__init__.py:34: Unused import 'retry_always'
tenacity/tenacity/__init__.py:38: Unused import 'retry_if_not_result'
tenacity/tenacity/__init__.py:39: Unused import 'retry_if_result'
tenacity/tenacity/__init__.py:40: Unused import 'retry_never'
tenacity/tenacity/__init__.py:43: Unused import 'stop_after_attempt'
tenacity/tenacity/__init__.py:44: Unused import 'stop_after_delay'
tenacity/tenacity/__init__.py:50: Unused import 'wait_chain'
tenacity/tenacity/__init__.py:52: Unused import 'wait_exponential'
tenacity/tenacity/__init__.py:54: Unused import 'wait_incrementing'
tenacity/tenacity/__init__.py:56: Unused import 'wait_random'
tenacity/tenacity/__init__.py:59: Unused import 'before_log'
tenacity/tenacity/__init__.py:63: Unused import 'after_log'

Unused code or False positive:

tenacity/doc/source/conf.py:1: Unused variable 'master_doc'
tenacity/doc/source/conf.py:2: Unused variable 'project'
tenacity/tenacity/retry.py:52: Unused variable 'retry_always'
tenacity/tenacity/tests/test_tenacity.py:429: Unused function '_retryable_test_with_exception_type_io_attempt_limit'
tenacity/tenacity/tests/test_tenacity.py:451: Unused function '_retryable_test_with_exception_type_custom_attempt_limit'
tenacity/tenacity/tests/test_tenacity.py:536: Unused variable 'trial_time_taken_ms'

There might be false positives, which can be prevented by adding them to a
whitelist file. You may find more info here

Regards,
vulture team

Catch exception after retries

Hey guys,

How can I catch the exception after some unsuccessful tries?

For example:

from tenacity import retry, wait_fixed, stop_after_attempt

>>> @retry(stop=stop_after_attempt(5), wait=wait_fixed(1))
... def test():
...     return 1/0
... 
>>> 
>>> try:
...     test()
... except ZeroDivisionError:
...     print("ok")
... 

Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/tenacity/__init__.py", line 242, in call
    result = fn(*args, **kwargs)
  File "<stdin>", line 3, in test
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/usr/local/lib/python3.5/dist-packages/tenacity/__init__.py", line 87, in wrapped_f
    return r.call(f, *args, **kw)
  File "/usr/local/lib/python3.5/dist-packages/tenacity/__init__.py", line 239, in call
    do = self.iter(result=result, exc_info=exc_info)
  File "/usr/local/lib/python3.5/dist-packages/tenacity/__init__.py", line 210, in iter
    six.raise_from(RetryError(fut), fut.exception())
  File "<string>", line 2, in raise_from
tenacity.RetryError: RetryError[<Future at 0x7f2e0f95c940 state=finished raised ZeroDivisionError>]



Raise last exception vs always raise RetryError

Today, tenacity will always raise a RetryError if the callable times out in terms of its retries. This behavior is different from retrying that always raised the last raise exception.

While I agree that the behavior taken by default in tenacity is a good choice, porting code from retrying to tenacity becomes error prone with this change in behavior as any callers in the chain must now expect a RetryError.

Yes callers can access the last exception and reraise it with something like:

        try:
            my_retried_fn()
        except tenacity.RetryError as retry_err:
            ex, tr = retry_err.last_attempt.exception_info()
            six.reraise(type(ex), ex, tr)

But this will become tedious to replicate across openstack projects where it's "risky" to now raise the RetryError.

What do you guys think about adding some way to toggle on reraising the last exception in tenacity (a backwards compat flag if you will)?

Or if you want to get crazy, perhaps even a pluggable raise.. e.g.

@retry(raise=raise_last_exception()|raise_first_exception()|raise_exception(my_raise_fn)...)

Make sure doc examples run

We have a similar things in tooz, where we make sure examples work. We might be able to use doctest for this via Sphinx, I don't know.

run_in_executor_call() in tenacity.AsyncRetrying

Many users might need to use traditional blocking libraries within their new asyncio projects or frameworks (e.g. uvloop, Sanic, aiohttp, aiosmtpd).

Provide a conveinience method for shortening the below within tenacity.AsyncRetrying class.

loop = asyncio.get_event_loop()
executor = None   # Uses default executor

retrying = tenacity.AsyncRetrying()
response = yield from retrying.call(
    loop.run_in_executor,
    executor,
    legacy_library.blocking_callable,
    request, args...)

Reset stop conditions

Assume we have next function:

@retry(stop=stop_after_attempt(3))
def test_function():
    # while loop that does jobs
    # every successful iteration is a completed job
    while not stop_event.is_set():
        # some code that can raise exception

Here we have counter that decreases on every failed job. But due to the while loop, it becomes a counter of failed jobs instead of attempt counter.
Although I could separate the job completion code into another function, there's another problem: every next job needs data from some previous jobs. So I will need to use outer data, what is slightly non-pythonic, I believe.

Proposal is to add functions to reset stop conditions (all and/or specific).

Need to rewind Generators that are passed as argument

Found an issue while using tenacity.
For a wrapped function that receive a generator as arguments, the generator is not rewound before each retry attempt, causing subsequent attempts to skip.

import random
from tenacity import retry

@retry
def do_something_unreliable(generator):
    for i in generator:
        if random.randint(0, 10) > 1:
            raise IOError("Broken sauce, everything is hosed!!!111one")
        else:
            return i

def firstn(n):
     num = 0
     while num < n:
         yield num
         num += 1

generator = first(100)
print(do_something_unreliable(generator))

Not sure whether this can be solved within the core of this library.

End without exception

 @tenacity.retry(retry=tenacity.retry_if_result(Common.IsNonePtr), stop=tenacity.stop_after_attempt(7), reraise=False)
    def get_net_addr(self, uid):
             return None

Is it possible to end this without RetryError? After 7 attpemts it's fine to return None.

Retry for Synchronous Generator

Could we support retry for Synchronous Generator?

import random
from tenacity import retry

@retry
def ticker(to):
    """Yield numbers from 0 to `to`."""
    for i in range(to):
        if random.randint(0, 10) > 1:
            raise IOError("Broken sauce, everything is hosed!!!111one")
        else:
            yield i

for i in ticker(10):
    print(i)

`before` callback on first success

@retry(before=lambda *args: print('This should only occur on retries(?)'))
def unstable():
    return

This prints out This should only occur on retries(?)

Is this intended? I thought the callback should only be invoked on retries of the method unstable -- not if it succeeds on the first try.

I've been using after instead. But I was just unsure of the intentions here...

Improve on-retry logging

Hi!

And thank you for forking the one and only library for better retries in Python. Tenacity API looks better, although I'm still to find an opportunity to use it in my projects.

I'd like to propose an improvement. More often than not, if there's a retry, I'd like it logged somewhere. With retrying I found myself using retry_on_exception functions that contained log messages with smth like

X failed, retrying (maybe): <error message>

because retry_on_exception function doesn't know whether or not there will be another retry attempt.

tenacity makes a step in the right direction with after=after_log function, but it's kind of lacking because different bits of information that can be useful to log are available in different places, such as:

  • first attempt start time or total time elapsed since then: available to stop functions
  • next attempt delay: available as a return value of wait functions
  • attempt number: available to all (?) but retry functions
  • invalid value returned or exception raised: available to retry functions

Providing just one logger/level/msgformat combination might not be enough as the user could want more flexibility, like

  • reporting retried attempts as WARNING and the last one as ERROR
  • using different loglevels for different errors
  • provide more information about the error in the log where possible

So I presume it should be another kwarg that accepts a function, e.g. log_retry= or on_retry=. And that function could accept an attempt with all interesting metadata attached (right now only attempt_number is attached to the Future instance), so as to avoid function signature issues when available metadata is altered.

If it sounds interesting, I can put together a POC.

add opt in logging

while retrying, i'd like some automated logging to happen, a way to provide a logger for stop conditions/retries/waits would be nice

im not sure about a good api yet

im predisposed to the wait_for lib

Broken install: "ImportError: No module named 'tasks'"

  • Py 3.5
  • Ubuntu Zesty
⏵ sudo -H pip3 install --upgrade tenacity
Collecting tenacity
  Using cached tenacity-4.4.0.tar.gz
    Complete output from command python setup.py egg_info:
    ERROR:root:Error parsing
    Traceback (most recent call last):
      File "/usr/local/lib/python3.5/dist-packages/pbr/core.py", line 111, in pbr
        attrs = util.cfg_to_args(path, dist.script_args)
      File "/usr/local/lib/python3.5/dist-packages/pbr/util.py", line 267, in cfg_to_args
        wrap_commands(kwargs)
      File "/usr/local/lib/python3.5/dist-packages/pbr/util.py", line 569, in wrap_commands
        cmdclass = ep.resolve()
      File "/usr/local/lib/python3.5/dist-packages/pkg_resources/__init__.py", line 2322, in resolve
        module = __import__(self.module_name, fromlist=['__name__'], level=0)
    ImportError: No module named 'tasks'
    error in setup command: Error parsing /tmp/pip-build-_zmuo5av/tenacity/setup.cfg: ImportError: No module named 'tasks'
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-_zmuo5av/tenacity/

ImportError: No module named 'tasks'

I am getting the following error while installing tenacity 4.4.0. I did not have this error with the previous versions:

pip install

root@2b8b4fe6b21d:~# pip3 install tenacity==4.4.0
Collecting tenacity==4.4.0
  Using cached tenacity-4.4.0.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 154, in save_modules
        yield saved
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 195, in setup_context
        yield
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 243, in run_setup
        DirectorySandbox(setup_dir).run(runner)
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 273, in run
        return func()
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 242, in runner
        _execfile(setup_script, ns)
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 46, in _execfile
        exec(code, globals, locals)
      File "/tmp/easy_install-6dm2lcpa/pbr-3.1.1/setup.py", line 22, in <module>
      File "/tmp/easy_install-6dm2lcpa/pbr-3.1.1/pbr/util.py", line 267, in cfg_to_args
      File "/tmp/easy_install-6dm2lcpa/pbr-3.1.1/pbr/util.py", line 569, in wrap_commands
      File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2235, in resolve
        module = __import__(self.module_name, fromlist=['__name__'], level=0)
    ImportError: No module named 'tasks'
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-build-2wkcdafj/tenacity/setup.py", line 20, in <module>
        pbr=True)
      File "/usr/lib/python3.5/distutils/core.py", line 108, in setup
        _setup_distribution = dist = klass(attrs)
      File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 269, in __init__
        self.fetch_build_eggs(attrs['setup_requires'])
      File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 313, in fetch_build_eggs
        replace_conflicting=True,
      File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 826, in resolve
        dist = best[req.key] = env.best_match(req, ws, installer)
      File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 1092, in best_match
        return self.obtain(req, installer)
      File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 1104, in obtain
        return installer(requirement)
      File "/usr/lib/python3/dist-packages/setuptools/dist.py", line 380, in fetch_build_egg
        return cmd.easy_install(req)
      File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 663, in easy_install
        return self.install_item(spec, dist.location, tmpdir, deps)
      File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 693, in install_item
        dists = self.install_eggs(spec, download, tmpdir)
      File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 873, in install_eggs
        return self.build_and_install(setup_script, setup_base)
      File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 1101, in build_and_install
        self.run_setup(setup_script, setup_base, args)
      File "/usr/lib/python3/dist-packages/setuptools/command/easy_install.py", line 1087, in run_setup
        run_setup(setup_script, args)
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 246, in run_setup
        raise
      File "/usr/lib/python3.5/contextlib.py", line 77, in __exit__
        self.gen.throw(type, value, traceback)
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 195, in setup_context
        yield
      File "/usr/lib/python3.5/contextlib.py", line 77, in __exit__
        self.gen.throw(type, value, traceback)
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 166, in save_modules
        saved_exc.resume()
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 141, in resume
        six.reraise(type, exc, self._tb)
      File "/usr/lib/python3/dist-packages/pkg_resources/_vendor/six.py", line 685, in reraise
        raise value.with_traceback(tb)
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 154, in save_modules
        yield saved
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 195, in setup_context
        yield
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 243, in run_setup
        DirectorySandbox(setup_dir).run(runner)
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 273, in run
        return func()
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 242, in runner
        _execfile(setup_script, ns)
      File "/usr/lib/python3/dist-packages/setuptools/sandbox.py", line 46, in _execfile
        exec(code, globals, locals)
      File "/tmp/easy_install-6dm2lcpa/pbr-3.1.1/setup.py", line 22, in <module>
      File "/tmp/easy_install-6dm2lcpa/pbr-3.1.1/pbr/util.py", line 267, in cfg_to_args
      File "/tmp/easy_install-6dm2lcpa/pbr-3.1.1/pbr/util.py", line 569, in wrap_commands
      File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2235, in resolve
        module = __import__(self.module_name, fromlist=['__name__'], level=0)
    ImportError: No module named 'tasks'
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-2wkcdafj/tenacity/

Python version

root@2b8b4fe6b21d:~# python3 --version
Python 3.5.2

pip list

root@2b8b4fe6b21d:~# pip3 list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
asn1crypto (0.22.0)
backports.csv (1.0.5)
cffi (1.10.0)
chardet (2.3.0)
cli-helpers (0.2.3)
click (6.7)
configobj (5.0.6)
cryptography (2.0.3)
dbschema (1.1.1)
humanize (0.5.1)
idna (2.6)
mycli (1.12.0)
pgcli (1.7.0)
pgspecial (1.8.0)
pip (9.0.1)
prettytable (0.7.2)
prompt-toolkit (1.0.15)
psycopg2 (2.7.3)
pycparser (2.18)
pycurl (7.43.0)
Pygments (2.2.0)
pygobject (3.20.0)
PyMySQL (0.7.11)
python-apt (1.1.0b1)
PyYAML (3.12)
redis (2.10.5)
requests (2.9.1)
rpq (2.0)
setproctitle (1.1.10)
setuptools (20.7.0)
six (1.10.0)
sqlparse (0.2.3)
ssh-import-id (5.5)
terminaltables (3.1.0)
unattended-upgrades (0.1)
urllib3 (1.13.1)
virtualenv (15.1.0)
wcwidth (0.1.7)
wheel (0.29.0)
root@2b8b4fe6b21d:~# 

Distribution used

root@2b8b4fe6b21d:~# lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 16.04.2 LTS
Release:	16.04
Codename:	xenial

3.2 and 3.3 support?

Why have these?

Programming Language :: Python :: 3.2

Programming Language :: Python :: 3.3

Remove them?

Access to elapsed time

Is there any way to access the elapsed time and how many retries were attempted for a particular method?

Required version of Six incorrect

Hi, requirements.txt says it needs Six version 1.7 or greater, but it looks like you are using the raise_from() method which, as far as I can tell, wasn't introduced until Six version 1.9.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.