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
A fishing bot script written in Python!

A fishing bot script written in Python!

Anel Drocic 3 Nov 03, 2021
This repository requires you to solve a problem by writing some basic python code.

Can You Solve a Problem? A beginner friendly repository that requires you to solve familiar problems with python. This could be as simple as implement

Precious Kolawole 11 Nov 30, 2022
An extended version of the hotkeys demo code using action classes

An extended version of the hotkeys application using action classes. In adafruit's Hotkeys code, a macro is using a series of integers, assumed to be

Neradoc 5 May 01, 2022
The parser of a timetable of tennis matches for Flashscore website

FlashscoreParser The parser of a timetable of tennis matches for Flashscore website. The program collects the schedule of tennis matches for two days

Valendovsky 1 Jul 15, 2022
NFT generator for Solana!

Solseum NFT Generator for Solana! Check this guide here! Creating your randomized uniques NFTs, getting rarity information and displaying it on a webp

Solseum™ VR NFTs 145 Dec 30, 2022
Inacap - Programa para pasar las notas de inacap a una hoja de cálculo rápidamente.

Inacap Programa en python para obtener varios datos académicos desde inacap y subirlos directamente a una hoja de cálculo. Cómo funciona Primero que n

Gabriel Barrientos 0 Jul 28, 2022
Writeup of NilbinSec's participation in the Winja CTF for c0c0n 2021

Winja-CTF-c0c0n-2021-Writeup NilbinSec's participation in the Winja CTF for c0c0n 2021 This repo covers NilbinSec's participation in the Winja CTF dur

1 Nov 15, 2021
🍬️🦇️ Open source Trick or Treat! 🦇️🍬️

Open Source Halloween! What's an easy way to have fun, and celebrate an open source Halloween? Open source trick or treating, of course! The repositor

Research Software Engineers 3 Oct 18, 2021
It's a repo for Cramer's rule, which is some math crap or something idk

It's a repo for Cramer's rule, which is some math crap or something idk (just a joke, it's not crap; don't take that seriously, math teachers)

Module64 0 Aug 31, 2022
Compiler Final Project - Lisp Interpreter

Compiler Final Project - Lisp Interpreter

2 Jan 23, 2022
Chicks get hostloc points regularly

hostloc_getPoints 小鸡定时获取hostloc积分 github action大规模失效,mjj平均一人10鸡,以下可以部署到自己的小鸡上

59 Dec 28, 2022
Fastest python library for making asynchronous group requests.

FGrequests: Fastest Asynchronous Group Requests Installation Install using pip: pip install fgrequests Documentation Pretty easy to use. import fgrequ

Farid Chowdhury 14 Nov 22, 2022
High-level bindings to the Valhalla framework.

Valhalla for Python This spin-off project simply offers improved Python bindings to the fantastic Valhalla project. Installation pip install valhalla

GIS • OPS 20 Dec 13, 2022
Easy Alias's for bash

easy-alias Easy Alias's for bash Setup Your system needs to have 'echo' which every 21st century computer has You dont need any python requirments but

Hashm 2 Jan 18, 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 dot matrix rendered using braille characters.

⣿ dotmatrix A dot matrix rendered using braille characters. Description This library provides class called Matrix which represents a dot matrix that c

Tim Fischer 25 Dec 12, 2022
Files relating to polymtl university

This is a tool I developed quickly, which allows users to visualize class availability by day of the week for a given program at polymtl. The schedule

PN 3 Mar 15, 2022
Project based on pure python with OOP

Object oriented programming review Object oriented programming (OOP) is among the most used programming paradigms (if not the most common) in the indu

Facundo Abrahan Cerimeli 1 May 09, 2022
Buggy script to play with GPOs

GPOwned /!\ This is a buggy PoC I made just to play with GPOs in my lab. Don't use it in production! /!\ The script uses impacket and ldap3 to update

45 Dec 15, 2022
Slientruss3d : Python for stable truss analysis tool

slientruss3d : Python for stable truss analysis tool Desciption slientruss3d is a python package which can solve the resistances, internal forces and

3 Dec 26, 2022