tenacity
tenacity copied to clipboard
Provide a context manager
I like the idea from https://github.com/bhearsum/redo to have retrying
contextmanager:
from tenacity import retry
from tenacity.stop import stop_after_attempt
@contextmanager
def retrying(func, *retry_args, **retry_kwargs):
yield retry(*retry_args, **retry_kwargs)(func)
def foo(max_count):
global count
count += 1
print(count)
if count < max_count:
raise ValueError("count too small")
return "success!"
count = 0
ret = None
max_attempt=5
with retrying(foo, stop=stop_after_attempt(max_attempt)) as f:
ret = f(3)
print(ret)
Do you think is may be included in the tenacity
? I have no clue if it works with async
and futures
.
Yes, I think having a context manager would be an interesting feature. :)
So there's actually no interesting thing we can do with the with
statement in Python. It'd be useful if there was some macro, but, nop.
Basically your example turns down to be just:
f = tenacity.Retrying(stop=stop_after_attempt(max_attempt)
f.call(foo, 3)
I've added __call__
so it can be reduced to:
f = tenacity.Retrying(stop=stop_after_attempt(max_attempt)
f(foo, 3)
+1 I am still novice in Python, so it seem's the redo
solution is overkill. For me, the f.call()
is well enough. IMHO it's just necessary to document such features.
So there's actually no interesting thing we can do with the with statement in Python. It'd be useful if there was some macro, but, nop.
What about something like this:
retrying = tenacity.Retrying(stop=stop_after_attempt(max_attempt))
with retrying:
foo(3)
With something like this:
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.attempt_number = 0
return True
else:
self.attempt_number += 1
return self.attempt_number <= self.max_attempt_number
@proppy sounds like a good idea. It's not much different than doing retrying(foo, 3)
but I can see the value of being more Pythonic. Wanna do a PR? ;)
I would prefer the context manager to be more Pythonic:
with tenacity.Retrying(func, ...) as func_result:
print(func_result.http_statuscode)
OR
with tenacity.Retrying(...)(func, args) as func_result:
print(func_result.http_statuscode)
@heri16 There's no need for a context manager in this case… you can just do func_result = tenacity.Retrying()(func, args)
…
@jd I see what you mean. I relooked at redo's context-manager documentation and produced 4 pull requests to review.
I took a look at your idea @proppy but unfortunately, there's no way to re-call the function in __exit__
since the code/function that is being called in the with
statement is unknown. Unless it's passed alongside that with
statement, but that adds 0 value, as the PRs from @heri16 show.
So while, it'd be a nice addition, until Python transforms itself more into a Lisp and adds some macro support or something like that, this seems impossible to implement.
@jd Sorry for the lack of update.
I was thinking retry wouldn't really need to know about the code being retried, if it provide something that yields "Attempt context-managers".
Something like this:
for attempt in tenacity.Attempts(stop=stop_after_attempt(max_attempt)):
with attempt:
if some_condition:
may_fail()
else:
may_fail_too()
Each attempt
could report back on __exit__
the success/failure to the generator, and Attempts
could either:
- raise
StopIteration
if the attempt succeeded. - yield another attempt if
max_attempt
is not reached. - re-raise the exception is
max_attempts
is reached.
What do you think?
That looks like cutting the workflow in piece but I can see some value. If you can make it, go ahead.
@jd does it looks like a big refactoring of tenacity code ? Do you have some insight to give us so to contribute ?
I don't think it's a lot of refactoring nowadays. You could probably add an __iter__
method on tenacity.Retrying
whose code would be based on the tenacity.Retrying.call
method. The latter uses a while True
internally to call self.iter
: that could be split and changed to make the approach iterative.
Then __iter__
could return a context manager that handles what @proppy described in https://github.com/jd/tenacity/issues/48#issuecomment-296572380 as an API.
maybe helpful, maybe not, but i implemented the contexts in the ruby Retriable
gem; i think we ended up in a good place: https://github.com/kamui/retriable/pull/43
@apurvis Python does not have big-lambda like Ruby's block. So the context is quite more complex in python, we need to combine a generator and a context manager.