Package: waiter
Does Python need yet another retry / poll library? It needs at least one that isn't coupled to decorators and functions. Decorators prevent the caller from customizing delay options, and organizing the code around functions hinders any custom handling of failures.Waiter is built around iteration instead, because the foundation of retrying / polling is a slowly executing loop. The resulting interface is both easier to use and more flexible, decoupling the delay algorithms from the application logic.UsagecreationSupply a number of seconds to repeat endlessly, or any iterable of seconds.fromwaiterimportwaitwait(1)# 1, 1, 1, 1, ...wait([1]*3)# 1, 1, 1wait([0.5,0.5,60])# circuit breakerIterable delays can express any waiting strategy, and constructors for common algorithms are also provided.wait.count(1)# incremental backoff 1, 2, 3, 4, 5, ...wait(1)+1# alternate syntax 1, 2, 3, 4, 5, ...wait.fibonacci(1)# 1, 1, 2, 3, 5, ...wait.polynomial(2)# 0, 1, 4, 9, 16, ...wait.exponential(2)# exponential backoff 1, 2, 4, 8, ...backoff=wait(1)*2# alternate syntax 1, 2, 4, 8, ...backoff[:3]# limit attempt count 1, 2, 4backoff<=5# set maximum delay   1, 2, 4, 5, 5, 5, ...backoff.random(-1,1)# add random jitteriterationThen simply use thewaitobject like any iterable, yielding the amount of elapsed time. Timeouts also supported of course.fromwaiterimportwait,suppress,firstforelapsedinwait(delays):# first iteration is immediatewithsuppress(exception):# then each subsequent iteration sleeps as necessary...breakfor_inwait(delays,timeout):# standard convention for ignoring a loop variable...# won't sleep past the timeoutif...:breakresults=(...for_inwait(delays))# expressions are even easierfirst(predicate,results[,default])# filter for first true itemassertany(results)# perfect for tests toofunctionsYes, functional versions are provided, as well as being trivial to implement.wait(...).throttle(iterable)# generate items from iterablewait(...).repeat(func,*args,**kwargs)# generate successive resultswait(...).retry(exception,func,*args,**kwargs)# return first success or re-raise exceptionwait(...).poll(predicate,func,*args,**kwargs)# return first success or raise StopIterationThe decorator variants are simply partial applications of the corresponding methods. Note decorator syntax doesn't support arbitrary expressions.backoff=wait(0.1)*2@backoff.repeating@backoff.retrying(exception)@backoff.polling(predicate)But in the real world:the function may not exist or be succinctly written as a lambdathe predicate may not exist or be succinctly written as a lambdalogging may be requiredthere may be complex handling of different exceptions or resultsSo consider the block form, just as decorators don't renderwithblocks superfluous. Also notewaitobjects are re-iterable provided their original delays were.asyncWaiters also support async iteration.throttleoptionally accepts an async iterable.repeat,retry, andpolloptionally accept coroutine functions.statisticsWaiter objects have astatsattribute for aggregating statistics about the calls made. The base implementation providestotalandfailurecounts. The interface of thestatsobject itself is considered provisional for now, but can be extended by overriding theStatsclass attribute. This also allows customization of the iterable values; elapsed time is the default.Installation%pipinstallwaiterDependenciesmultimethodTests100% branch coverage.%pytest[--cov]