Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a context manager #48

Closed
xmedeko opened this issue Jan 12, 2017 · 16 comments
Closed

Provide a context manager #48

xmedeko opened this issue Jan 12, 2017 · 16 comments

Comments

@xmedeko
Copy link

xmedeko commented Jan 12, 2017

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.

@jd jd added the enhancement label Jan 12, 2017
@jd
Copy link
Owner

jd commented Jan 12, 2017

Yes, I think having a context manager would be an interesting feature. :)

@jd jd changed the title with retrying Provide a context manager Jan 12, 2017
@jd
Copy link
Owner

jd commented Mar 6, 2017

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)

@jd jd closed this as completed Mar 6, 2017
jd added a commit that referenced this issue Mar 6, 2017
This is not a with statement, but it's as easy as shown in #48.
@xmedeko
Copy link
Author

xmedeko commented Mar 6, 2017

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

@proppy
Copy link

proppy commented Apr 13, 2017

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

@jd
Copy link
Owner

jd commented Apr 14, 2017

@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? ;)

@jd jd reopened this Apr 14, 2017
@heri16
Copy link

heri16 commented Apr 22, 2017

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)

@jd
Copy link
Owner

jd commented Apr 22, 2017

@heri16 There's no need for a context manager in this case… you can just do func_result = tenacity.Retrying()(func, args)

heri16 added a commit to heri16/tenacity that referenced this issue Apr 22, 2017
See Issue jd#48

Example:
```python
with RetryingContext(open) as open:
  with open('filename.txt', 'r') as file:
    ...
```
heri16 added a commit to heri16/tenacity that referenced this issue Apr 22, 2017
Alternative implementation.

See Issue jd#48

Example:
```python
with RetryingContext(open) as open:
  with open('filename.txt', 'r') as file:
    ...
```
heri16 added a commit to heri16/tenacity that referenced this issue Apr 22, 2017
See Issue jd#48

Example:
```python
async with AsyncRetryingContext(open)('filename.txt', 'r') as file:
  ...
```
heri16 added a commit to heri16/tenacity that referenced this issue Apr 22, 2017
Alternative implementation
See Issue jd#48

Example:
```python
with AsyncRetryingContext(open) as open:
   async with open('filename.txt', 'r') as file:
     ...
```
@heri16
Copy link

heri16 commented Apr 23, 2017

@jd I see what you mean.
I relooked at redo's context-manager documentation and produced 4 pull requests to review.

@jd
Copy link
Owner

jd commented Apr 24, 2017

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 jd closed this as completed Apr 24, 2017
@proppy
Copy link

proppy commented Apr 24, 2017

@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?

@jd
Copy link
Owner

jd commented Apr 24, 2017

That looks like cutting the workflow in piece but I can see some value. If you can make it, go ahead.

@jd jd reopened this Apr 24, 2017
@bersace
Copy link
Contributor

bersace commented Mar 19, 2019

@jd does it looks like a big refactoring of tenacity code ? Do you have some insight to give us so to contribute ?

@jd
Copy link
Owner

jd commented Mar 21, 2019

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 #48 (comment) as an API.

@apurvis
Copy link

apurvis commented Mar 25, 2019

maybe helpful, maybe not, but i implemented the contexts in the ruby Retriable gem; i think we ended up in a good place: kamui/retriable#43

@bersace
Copy link
Contributor

bersace commented Mar 26, 2019

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

@jd
Copy link
Owner

jd commented May 7, 2024

We have this now.

@jd jd closed this as completed May 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants