Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling exceptions inside context managers

I have some code where I try to reach a resource but sometimes it is unavailable, and results in an exception. I tried to implement a retry engine using context managers, but I can't handle the exception raised by the caller inside the __enter__ context for my context manager.

class retry(object):     def __init__(self, retries=0):         self.retries = retries         self.attempts = 0     def __enter__(self):         for _ in range(self.retries):             try:                 self.attempts += 1                 return self             except Exception as e:                 err = e     def __exit__(self, exc_type, exc_val, traceback):         print 'Attempts', self.attempts 

These are some examples which just raise an exception (which I expected to handle)

>>> with retry(retries=3): ...     print ok ...  Attempts 1 Traceback (most recent call last):   File "<stdin>", line 2, in <module> NameError: name 'ok' is not defined >>>  >>> with retry(retries=3): ...     open('/file') ...  Attempts 1 Traceback (most recent call last):   File "<stdin>", line 2, in <module> IOError: [Errno 2] No such file or directory: '/file' 

Is there any way to intercept these exception(s) and handle them inside the context manager?

like image 588
Mauro Baraldi Avatar asked Feb 18 '16 13:02

Mauro Baraldi


People also ask

What actions are guaranteed by a context manager?

Context managers allow you to allocate and release resources precisely when you want to. The most widely used example of context managers is the with statement. Suppose you have two related operations which you'd like to execute as a pair, with a block of code in between.

Can context managers be used outside the with statement?

Yes, the context manager will be available outside the with statement and that is not implementation or version dependent. with statements do not create a new execution scope.

What does __ enter __ do in Python?

__enter__ and [__exit__] both are methods that are invoked on entry to and exit from the body of "the with statement" (PEP 343) and implementation of both is called context manager. the with statement is intend to hiding flow control of try finally clause and make the code inscrutable.

How do context managers work in Python?

The with statement in Python is a quite useful tool for properly managing external resources in your programs. It allows you to take advantage of existing context managers to automatically handle the setup and teardown phases whenever you're dealing with external resources or with operations that require those phases.


1 Answers

Quoting __exit__,

If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.

By default, if you don't return a value explicitly from a function, Python will return None, which is a falsy value. In your case, __exit__ returns None and that is why the exeception is allowed to flow past the __exit__.

So, return a truthy value, like this

class retry(object):      def __init__(self, retries=0):         ...       def __enter__(self):         ...      def __exit__(self, exc_type, exc_val, traceback):         print 'Attempts', self.attempts         print exc_type, exc_val         return True                                   # or any truthy value  with retry(retries=3):     print ok 

the output will be

Attempts 1 <type 'exceptions.NameError'> name 'ok' is not defined 

If you want to have the retry functionality, you can implement that with a decorator, like this

def retry(retries=3):     left = {'retries': retries}      def decorator(f):         def inner(*args, **kwargs):             while left['retries']:                 try:                     return f(*args, **kwargs)                 except NameError as e:                     print e                     left['retries'] -= 1                     print "Retries Left", left['retries']             raise Exception("Retried {} times".format(retries))         return inner     return decorator   @retry(retries=3) def func():     print ok  func() 
like image 191
thefourtheye Avatar answered Sep 25 '22 23:09

thefourtheye