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/test_utils.py@38

    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
JLC2KICAD_lib is a python script that generate a component library for KiCad from the JLCPCB/easyEDA library.

JLC2KiCad_lib is a python script that generate a component library (schematic, footprint and 3D model) for KiCad from the JLCPCB/easyEDA library. This script requires Python 3.6 or higher.

Nicolas Toussaint 73 Dec 26, 2022
K2HASH Python library - NoSQL Key Value Store(KVS) library

k2hash_python Overview k2hash_python is an official python driver for k2hash. Install Firstly you must install the k2hash shared library: curl -o- htt

Yahoo! JAPAN 3 Oct 19, 2022
Msgpack serialization/deserialization library for Python, written in Rust using PyO3 and rust-msgpack. Reboot of orjson. msgpack.org[Python]

ormsgpack ormsgpack is a fast msgpack library for Python. It is a fork/reboot of orjson It serializes faster than msgpack-python and deserializes a bi

Aviram Hassan 139 Dec 30, 2022
PyPIContents is an application that generates a Module Index from the Python Package Index (PyPI) and also from various versions of the Python Standard Library.

PyPIContents is an application that generates a Module Index from the Python Package Index (PyPI) and also from various versions of the Python Standar

Collage Labs 10 Nov 19, 2022
πŸ”© Like builtins, but boltons. 250+ constructs, recipes, and snippets which extend (and rely on nothing but) the Python standard library. Nothing like Michael Bolton.

Boltons boltons should be builtins. Boltons is a set of over 230 BSD-licensed, pure-Python utilities in the same spirit as β€” and yet conspicuously mis

Mahmoud Hashemi 6k Jan 6, 2023
A Python library to simulate a Zoom H6 recorder remote control

H6 A Python library to emulate a Zoom H6 recorder remote control Introduction This library allows you to control your Zoom H6 recorder from your compu

Matias Godoy 68 Nov 2, 2022
Python library for creating PEG parsers

PyParsing -- A Python Parsing Module Introduction The pyparsing module is an alternative approach to creating and executing simple grammars, vs. the t

Pyparsing 1.7k Jan 3, 2023
Python library to natively send files to Trash (or Recycle bin) on all platforms.

Send2Trash -- Send files to trash on all platforms Send2Trash is a small package that sends files to the Trash (or Recycle Bin) natively and on all pl

Andrew Senetar 224 Jan 4, 2023
Python screenshot library, replacement for the Pillow ImageGrab module on Linux.

tldr: Use Pillow The pyscreenshot module is obsolete in most cases. It was created because PIL ImageGrab module worked on Windows only, but now Linux

null 455 Dec 24, 2022
A functional standard library for Python.

Toolz A set of utility functions for iterators, functions, and dictionaries. See the PyToolz documentation at https://toolz.readthedocs.io LICENSE New

null 4.1k Jan 4, 2023
πŸ”© Like builtins, but boltons. 250+ constructs, recipes, and snippets which extend (and rely on nothing but) the Python standard library. Nothing like Michael Bolton.

Boltons boltons should be builtins. Boltons is a set of over 230 BSD-licensed, pure-Python utilities in the same spirit as β€” and yet conspicuously mis

Mahmoud Hashemi 5.4k Feb 20, 2021
Ikaros is a free financial library built in pure python that can be used to get information for single stocks, generate signals and build prortfolios

Ikaros is a free financial library built in pure python that can be used to get information for single stocks, generate signals and build prortfolios

Salma Saidane 64 Sep 28, 2022
Paprika is a python library that reduces boilerplate. Heavily inspired by Project Lombok.

Image courtesy of Anna Quaglia (Photographer) Paprika Paprika is a python library that reduces boilerplate. It is heavily inspired by Project Lombok.

Rayan Hatout 55 Dec 26, 2022
A Python library that helps data scientists to infer causation rather than observing correlation.

A Python library that helps data scientists to infer causation rather than observing correlation.

QuantumBlack Labs 1.7k Jan 4, 2023
Python library and cli util for https://www.zerochan.net/

Zerochan Library for Zerochan.net with pics parsing and downloader included! Features CLI utility for pics downloading from zerochan.net Library for c

kiriharu 10 Oct 11, 2022
An Advanced Wordlist Library Written In Python For Acm114

RBAPG ->RBAPG is the abbreviation of "Rule Based Attack Password Generator". ->This module is a wordlist generator module. ->You can generate randomly

Aziz Kaplan 11 Aug 28, 2022
Datamol is a python library to work with molecules.

Datamol is a python library to work with molecules. It's a layer built on top of RDKit and aims to be as light as possible.

datamol 276 Dec 19, 2022
AndroidEnv is a Python library that exposes an Android device as a Reinforcement Learning (RL) environment.

AndroidEnv is a Python library that exposes an Android device as a Reinforcement Learning (RL) environment.

DeepMind 814 Dec 26, 2022