Retrying library for Python

Overview

Tenacity

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

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_give_up_never_surrender():
        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

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::

    @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

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")

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

While callables that "timeout" retrying raise a RetryError by default, we can reraise the last attempt's exception if needed:

.. 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

    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

    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

    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 retry attribute attached to the function and its statistics attribute:

.. 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.retry.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 RetryCallState class:

.. autoclass:: tenacity.RetryCallState

   Constant attributes:

   .. autoattribute:: start_time(float)
      :annotation:

   .. autoattribute:: retry_object(BaseRetrying)
      :annotation:

   .. autoattribute:: fn(callable)
      :annotation:

   .. autoattribute:: args(tuple)
      :annotation:

   .. autoattribute:: kwargs(dict)
      :annotation:

   Variable attributes:

   .. autoattribute:: attempt_number(int)
      :annotation:

   .. autoattribute:: outcome(tenacity.Future or None)
      :annotation:

   .. autoattribute:: outcome_timestamp(float or None)
      :annotation:

   .. autoattribute:: idle_for(float)
      :annotation:

   .. autoattribute:: next_action(tenacity.RetryAction or None)
      :annotation:

Other Custom Callbacks

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

.. function:: my_stop(retry_state)

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

.. function:: my_wait(retry_state)

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

.. function:: my_retry(retry_state)

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

.. function:: my_before(retry_state)

   :param RetryState retry_state: info about current retry invocation

.. function:: my_after(retry_state)

   :param RetryState retry_state: info about current retry invocation

.. function:: my_before_sleep(retry_state)

   :param RetryState 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.retry.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')

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

Async and retry

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

@retry
async def my_async_function(loop):
    await loop.getaddrinfo('8.8.8.8', 53)
@retry
@tornado.gen.coroutine
def my_async_function(http_client, url):
    yield http_client.fetch(url)

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

@retry(sleep=trio.sleep)
async def my_async_function(loop):
    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 master 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
Comments
  • Discussion: Test util for mocking time.sleep()

    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/[email protected]

    opened by bodenr 20
  • Cannot easily mock out sleep function in order to simulate time in tests

    Cannot easily mock out sleep function in order to simulate time in tests

    Given the level of flexibility of this library, and a slightly elaborate context which I am using it in, it is desirable to be able to write an integration test where the concepts of 'time' (i.e. time.monotonic) and 'sleep' (i.e. tenacity.nap.sleep) are mocked out so that we can simulate in full fidelity how a retry scenario plays out, without having to actually sleep.

    Using the testfixtures library and mock.patch I'm mocking out 'time' with an object that always returns the same 'virtual' time, and 'sleep' with a function that advances this 'virtual' time.

    This approach works pretty well, save for one complication...

    Since the signature of BaseRetrying.__init__() defaults the sleep argument to tenacity.nap.sleep this default is bound at import time, making it hard to mock out the sleep function after the fact.

    Options I can think of:

    1. Reach in to the method signature and mock out the default value - seems a bit messy.
    2. Pass in a sleep function in the code under test, then mock this out in the test - I don't like this because it involves modifying production code simply for the sake of the test, and also makes this mocking strategy less portable if I want to use it in other tests (because now I need to mock out sleep at my callsite, rather than inside tenacity).
    3. Make a change to the signature of BaseRetrying.__init__() to default sleep to None, and then default this to nap.sleep inside the method: self.sleep = sleep or nap.sleep.

    Option 3 seems the neatest, because now I can just mock out tenacity.nap.sleep which is the most intuitive approach, but it does involve a change to the library which is why I wanted to solicit input first before preparing a PR.

    Thoughts?

    opened by asqui 16
  • Retry coroutines

    Retry coroutines

    Hi,

    This PR ports https://github.com/rholder/retrying/pull/64 to tenacity. Thanks for the fork. The codebase is really clean.

    It's a bit tricky to implement retry on coroutines. I did this by extracting the calling (aka attempt) and sleep logic from the retry control loop. This is done by using a generator. The control loop generate instructions (attempt, sleep or return), it's up to the wrapping code to do the actual IO, asynchronously or not.

    I'm actually not very proud of this. Supporting old python versions and sync/async is a bit hackish. I'd be glad to hear your opinion on this contribution. Until it's mergeable, i wish :)

    Regards,

    opened by bersace 12
  • Trouble building rpms on Centos 7.3

    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?

    help wanted 
    opened by jnaulty 10
  • Make before, after and stop, retry and retry_error_callback accept retry state

    Make before, after and stop, retry and retry_error_callback accept retry state

    This is a continuation to work done in #124 and #122 adding support for retry_state (previously called call_state) to before=, after=, stop=, retry= and retry_error_callback= callbacks.

    opened by immerrr 9
  • Raise last exception vs always raise RetryError

    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)...)
    
    enhancement 
    opened by bodenr 9
  • Add  wait_chain()

    Add wait_chain()

    One PR I had in retrying [1] proposed to support the notion of "stepped wait". This patch adds stepping support by introducing a new combined step function that wraps 1 or more wait fns to produced a "stepped queue" of wait fns.

    This approach solves [1] and also is reusable.

    [1] https://github.com/rholder/retrying/pull/51

    opened by bodenr 9
  • Retry being called despite successful execution of function

    Retry being called despite successful execution of function

    On using retry_unless_exception_type , retry is always called (despite a successful run) until the exception(s) is(are) encountered. However, correct behavior with this condition should be to retry only if a function has failed due to some exception other than the ones mentioned in the retry condition. e.g. This is the function definition, wherein I want to retry the function only if the exception is not among psycopg2.ProgrammingError and NameError and either the time since last retrial is less than 1 min, or the attempt count is less than 3:

    @retry(stop=(stop_after_delay(60) | stop_after_attempt(2)),
            reraise=True,
            retry=retry_unless_exception_type((psycopg2.ProgrammingError, NameError)))
    def run_query(query_variable):
        cursor.execute(query_variable)
        print cursor.rowcount
        print "Done"
    

    and here's the execution log, which shows that function was retried twice despite successful runs:

    53059
    Done
    53059
    Done
    Traceback (most recent call last):
      File "update_offer_base_item_mapping.py", line 22, in <module>
        run_query(a)
      File "/Users/grofers/Documents/cb/py2venv/lib/python2.7/site-packages/tenacity/__init__.py", line 241, in wrapped_f
        return self.call(f, *args, **kw)
      File "/Users/grofers/Documents/cb/py2venv/lib/python2.7/site-packages/tenacity/__init__.py", line 330, in call
        start_time=start_time)
      File "/Users/grofers/Documents/cb/py2venv/lib/python2.7/site-packages/tenacity/__init__.py", line 297, in iter
        raise retry_exc.reraise()
      File "/Users/grofers/Documents/cb/py2venv/lib/python2.7/site-packages/tenacity/__init__.py", line 137, in reraise
        raise self
    tenacity.RetryError: RetryError[<Future at 0x109cf4190 state=finished returned NoneType>]
    
    opened by chetnaB 8
  • Add RetryCallState class

    Add RetryCallState class

    This is to share some initial work regarding #77, where I suggested to attach all metadata related to the current retry call into a "retry call state" object from where it could be logged using any field available to other callbacks.

    Ideally, all callbacks would use that "retry call state" object, but that's probably something for the next major release that breaks compatibility. Or if it is OK performance-wise, the code could detect whether or not the callback accepts the call state.

    I have intentionally not touched statistics although they seem quite related and probably could be dropped if "retry call state" approach is accepted.

    opened by immerrr 8
  • Provide a generator decorator

    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
    
    opened by proppy 8
  • Drop support for deprecated Pythons

    Drop support for deprecated Pythons

    This PR obsoletes Python 2.7 & 3.5, removes them from package metadata and CI. Also, it adds the python_requires metadata option which instructs pip running on older Python versions to find and install the latest suitable release.

    See #291

    opened by and-semakin 7
  • Changelog is missing entry for version 8.1.0

    Changelog is missing entry for version 8.1.0

    At the time of writing, 8.1.0 is the latest version. It was released on September 21st. Github tags and PyPI agree.

    Unfortunately the changelog is still showing the prior version (8.0.1) as the latest. It does not contain any reference to 8.1.0.

    For those of us who are gratefully using this excellent package and want to stay up-to-date, having the details of the latest changes documented is a great help.

    opened by tdwright 0
  • Fix async loop with retrying code block when result is available

    Fix async loop with retrying code block when result is available

    Hi!

    I was facing an issue of not being able to receive the result payload in callbacks when using the retrying code block pattern. It's basically what's explained in https://github.com/jd/tenacity/issues/326, which has a workaround.

    However, when using an async loop it was having a strange behaviour when the block was finally successful. The minimal example is present in the test:

    async def test():
        attempts = 0
        async for attempt in tasyncio.AsyncRetrying(retry=retry_if_result(lambda x: x < 3)):
            with attempt:
                attempts += 1
            attempt.retry_state.set_result(attempts)
        return attempts
    
    result = await test()
    

    Without the fix, the last iteration of the loop receives the result instead of an AttemptManager and it crashes in the with clause.

    Cheers!

    opened by victormferrara 1
  • Before outcome always None

    Before outcome always None

    version 8.0.1 I have example of code:

    import tenacity
    import httpx
    
    
    def log(name):
        def inner(retry_state: tenacity.RetryCallState) -> None:
            print(f"{name=} - {retry_state.outcome=}")
        return inner
    
    
    @tenacity.retry(
        retry=tenacity.retry_if_exception_type(httpx.RequestError),
        stop=tenacity.stop_after_attempt(3),
        after=log("after"),
        before_sleep=log("before_sleep"),
        before=log("before"),
    )
    def httpbin():
        httpx.get(url="https://httpbin.org/delay/3", timeout=2)
    
    
    if __name__ == '__main__':
        try:
            httpbin()
        except Exception:
            pass
    

    Output:

    name='before' - retry_state.outcome=None
    name='after' - retry_state.outcome=<Future at 0x10d492590 state=finished raised ReadTimeout>
    name='before_sleep' - retry_state.outcome=<Future at 0x10d492590 state=finished raised ReadTimeout>
    name='before' - retry_state.outcome=None
    name='after' - retry_state.outcome=<Future at 0x10d492c50 state=finished raised ReadTimeout>
    name='before_sleep' - retry_state.outcome=<Future at 0x10d492c50 state=finished raised ReadTimeout>
    name='before' - retry_state.outcome=None
    name='after' - retry_state.outcome=<Future at 0x10d492b30 state=finished raised ReadTimeout>
    
    Process finished with exit code 0
    

    How can i fix this?

    opened by stepalxser 1
  • Added CLI wrapper

    Added CLI wrapper

    Tenacity can now be used as a CLI app to retry shell scripts or apps!

    Closes https://github.com/jd/tenacity/issues/279

    It's not yet complete, some of the advanced functions are left (such as exponential and random wait functions, thinking of a good way to implement those). A changelog is left too, but I wanted your reviews first on whether this is a good change, and if I am following the proper styling/coding guidelines or not.

    opened by Yureien 1
  • update CI images to cimg/python and add Python 3.10 job

    update CI images to cimg/python and add Python 3.10 job

    I just saw that you actually don't run your tests in CI against Python 3.10, so I added it and also changed the image name to the new one cimg/python, because the old one is not maintained anymore. https://circleci.com/developer/images/image/cimg/python

    opened by gruebel 6
Releases(5.1.4)
Owner
Julien Danjou
Open Source Software Engineer πŸ’», Runner πŸƒπŸ»β€β™‚οΈ Foodie 🍲 FPS player 🎯
Julien Danjou
Statistics Calculator module for all types of Stats calculations.

Statistics-Calculator This Calculator user the formulas and methods to find the statistical values listed. Statistics Calculator module for all types

2 May 29, 2022
Skip spotify ads by automatically restarting application when ad comes

SpotiByeAds No one likes interruptions! Don't you hate it when you're listening to your favorite jazz track or your EDM playlist and an ad for Old Spi

Partho 287 Dec 29, 2022
Easy, clean, reliable Python 2/3 compatibility

Overview: Easy, clean, reliable Python 2/3 compatibility python-future is the missing compatibility layer between Python 2 and Python 3. It allows you

Python Charmers 1.2k Jan 08, 2023
The code submitted for the Analytics Vidhya Jobathon - February 2022

Introduction On February 11th, 2022, Analytics Vidhya conducted a 3-day hackathon in data science. The top candidates had the chance to be selected by

11 Nov 21, 2022
Web service which feeds Navitia with real-time disruptions

Chaos Chaos is the web service which can feed Navitia with real-time disruptions. It can work together with Kirin which can feed Navitia with real-tim

KISIO Digital 7 Jan 07, 2022
Ssma is a tool that helps you collect your badges in a satr platform

satr-statistics-maker ssma is a tool that helps you collect your badges in a satr platform πŸŽ–οΈ Requirements python = 3.7 Installation first clone the

TheAwiteb 3 Jan 04, 2022
bib2xml - A tool for getting Word formatted XML from Bibtex files

bib2xml - A tool for getting Word formatted XML from Bibtex files Processes Bibtex files (.bib), produces Word Bibliography XML (.xml) output Why not

Matheus Sartor 1 May 05, 2022
msImpersonate - User account impersonation written in pure Python3

msImpersonate v1.0 msImpersonate is a Python-native user impersonation tool that is capable of impersonating local or network user accounts with valid

Joe Helle 90 Dec 16, 2022
Collection of Python scripts to perform Eikonal Tomography

Collection of Python scripts to perform Eikonal Tomography

Emanuel KΓ€stle 10 Nov 04, 2022
PIP Manager written in python Tkinter

PIP Manager About PIP Manager is designed to make Python Package handling easier by just a click of a button!! Available Features Installing packages

Will Payne 9 Dec 09, 2022
Custom SLURM wrapper scripts to make finding job histories and system resource usage more easily accessible

SLURM Wrappers Executables job-history A simple wrapper for grabbing data for completed and running jobs. nodes-busy Developed for the HPC systems at

Sara 2 Dec 13, 2021
A module comment generator for python

Module Comment Generator The comment style is as a tribute to the comment from the RA . The comment generator can parse the ast tree from the python s

飘尘 1 Oct 21, 2021
Python flexible slugify function

Python flexible slugify function

Dmitry Voronin 471 Dec 20, 2022
Provide error messages for Python exceptions, even if the original message is empty

errortext is a Python package to provide error messages for Python exceptions, even if the original message is empty.

Thomas Aglassinger 0 Dec 07, 2021
Fast Base64 encoding/decoding in Python

Fast Base64 implementation This project is a wrapper on libbase64. It aims to provide a fast base64 implementation for base64 encoding/decoding. Insta

Matthieu Darbois 96 Dec 26, 2022
This repository contains various tools useful for offensive operations (reversing, etc) regarding the PE (Portable Executable) format

PE-Tools This repository contains various tools useful for offensive operations (reversing, etc) regarding the PE (Portable Executable) format Install

stark0de 4 Oct 13, 2022
Python module for creating the circuit simulation definitions for Elmer FEM

elmer_circuitbuilder Python module for creating the circuit simulation definitions for Elmer FEM. The circuit definitions enable easy setup of coils (

5 Oct 03, 2022
Script to calculate delegator epoch returns for all pillars

znn_delegator_calculator Script to calculate estimated delegator epoch returns for all Pillars, so you can delegate to the best one. You can find me o

2 Dec 03, 2021
Collections of python projects

nppy, mostly contains projects written in Python. Some projects are very simple while some are a bit lenghty and difficult(for beginners) Requirements

ghanteyyy 75 Dec 20, 2022
Its a simple and fun to use application. You can make your own quizes and send the lik of the quiz to your friends.

Quiz Application Its a simple and fun to use application. You can make your own quizes and send the lik of the quiz to your friends. When they would a

Atharva Parkhe 1 Feb 23, 2022