The Python documentation for unittest implies that the assertRaises() method can be used as a context manager. The code below shows gives a simple example of the unittest from the Python docs. The assertRaises() call in the testsample() method works fine.
Now I'd like to access the exception in when it is raised, but if I comment it out and instead uncomment the next block in which I attempt to used a context manager I get an AttributeError: __exit__ when I attempt to execute the code. This happens for both Python 2.7.2 and 3.2.2. I could catch the exception in a try...except block and access it that way but the documentation for unittest seems to imply the context manager would do this as well.
Is there something else I'm doing wrong here?
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
self.seq = [x for x in range(10)]
def testshuffle(self):
# make sure the shuffled sequence does not lose any elements
random.shuffle(self.seq)
self.seq.sort()
self.assertEqual(self.seq, [x for x in range(10)])
def testchoice(self):
element = random.choice(self.seq)
self.assert_(element in self.seq)
def testsample(self):
self.assertRaises(ValueError, random.sample, self.seq, 20)
# with self.assertRaises(ValueError, random.sample, self.seq, 20):
# print("Inside cm")
for element in random.sample(self.seq, 5):
self.assert_(element in self.seq)
if __name__ == '__main__':
unittest.main()
It seems no-one has yet suggested:
import unittest
# For python < 2.7, do import unittest2 as unittest
class Class(object):
def should_raise(self):
raise ValueError('expected arg')
class test_Class(unittest.TestCase):
def test_something(self):
DUT = Class()
with self.assertRaises(ValueError) as exception_context_manager:
DUT.should_raise()
exception = exception_context_manager.exception
self.assertEqual(exception.args, ('expected arg', ))
I usually use e_cm as short for exception_context_manager.
The source code for unittest doesn't show an exception hook for assertRaises:
class _AssertRaisesContext(object):
"""A context manager used to implement TestCase.assertRaises* methods."""
def __init__(self, expected, test_case, expected_regexp=None):
self.expected = expected
self.failureException = test_case.failureException
self.expected_regexp = expected_regexp
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
if exc_type is None:
try:
exc_name = self.expected.__name__
except AttributeError:
exc_name = str(self.expected)
raise self.failureException(
"{0} not raised".format(exc_name))
if not issubclass(exc_type, self.expected):
# let unexpected exceptions pass through
return False
self.exception = exc_value # store for later retrieval
if self.expected_regexp is None:
return True
expected_regexp = self.expected_regexp
if isinstance(expected_regexp, basestring):
expected_regexp = re.compile(expected_regexp)
if not expected_regexp.search(str(exc_value)):
raise self.failureException('"%s" does not match "%s"' %
(expected_regexp.pattern, str(exc_value)))
return True
So, as you suspected, forming your own try/except block is the way to go if you want to intercept the exception while still keeping the assertRaises test:
def testsample(self):
with self.assertRaises(ValueError):
try:
random.sample(self.seq, 20)
except ValueError as e:
# do some action with e
self.assertEqual(e.args,
('sample larger than population',))
# now let the context manager do its work
raise
According to the documentation:
If called with callableObj omitted or None, will return a context object
So that code should be:
with self.assertRaises(ValueError):
random.sample(self.seq, 20)
Given this was asked six years ago I imagine this is something which works now but didn't work then. The docs state this appeared in 2.7 but not which micro version.
import unittest
class TestIntParser(unittest.TestCase):
def test_failure(self):
failure_message = 'invalid literal for int() with base 10'
with self.assertRaises(ValueError) as cm:
int('forty two')
self.assertIn(failure_message, cm.exception.message)
if __name__ == '__main__':
unittest.main()
Related
I have a function "main_func" , from which i am calling another function and so on.
class Error(Exception):
"""Base class for other exceptions"""
pass
def main_func():
return sub_func()
def sub_func():
return sub_sub_func()
def sub_sub_func():
return sub_sub_sub_func()
def sub_sub_sub_func():
try:
x = len(10)
res = 'b'
except:
raise Error
return res
main_func()
If you see in sub_sub_sub_func() i have added a line x = len(10) which will cause exception.
What i want is, if this happens, i should directly jump to main_func() and return a flag(str) as 'fail'
I looked into defining custom exceptions , but it didn't helped me.
I want to return after i raise.
len(10) will raise a TypeError you can catch this specific exception in your main_func and do the thing that needs to happen then.
Please note that you need to create an instance of your error class when raising. Error()
class Error(Exception):
"""Base class for other exceptions"""
pass
def main_func():
try:
return sub_func()
except (Error as e):
# The raised error will be cought here.
# Do the stuff that needs to happen here.
return 'fail'
def sub_func():
return sub_sub_func()
def sub_sub_func():
return sub_sub_sub_func()
def sub_sub_sub_func():
try:
x = len(10) # Will raise a `TypeError`
res = 'b'
except:
# `TypeError` that is raised will get here
raise Error()
return res
main_func()
Note: Your custom Error hides a lot of information that can come in handy later. What happened what raised this error. Best is to put the original TypeError as an inner exception to Error.
try:
x = len(10)
except Throwable as e:
raise Error(e)
In theory in your code a potential out of memory exception will be converted to your Error without knowing what happened.
I found myself using the following pattern in my tests quite often:
def test(params):
e_list = []
for p in params:
try:
run_test(p) # Or a block of codes that can continue or break
except Exception as e:
e_list.append(e)
assert isEmpty(e_list), 'error encountered: {}'.format(e_list)
I find myself rewriting this pattern quite often, especially with long code block for the loop that has some flow controls with continue and break. I am wondering if there is a python-ic wrapping to this pattern.
I have thought about a wrapper function like this:
def assert_all_tests(test_list):
e_list = []
for t in test_list:
try:
t()
except Exception as e:
e_list.append(e)
assert isEmpty(e_list), 'error encountered: {}'.format(e_list)
def test(params):
assert_all_tests([functools.partial(run_test, p) for p in params])
But I dislike this approach because it wrapped away the loop. There is no way for callable t to do flow control of the loop with continue or break (there is no loop any more, only a list comprehension).
Another approach is to use a context class like this:
def test(params):
ErrorHandler.clearErrorList()
for p in params:
with ErrorHandler():
run_test(p) # or code block that can continue or break
ErrorHandler.assertEmptyErrorList()
where ErrorHandler would be a class with appropriate __enter__ and __exit__ and keep an error list in a class variable. But I feel that at the test function level, this is not any simpler than the original pattern: since there is no way for a ErrorHandler instance to know when a loop has began and ended, I still have to write the pre- and post- loop fixtures.
I'd like to hear idea of approaches to wrap around this pattern. Thanks.
EDIT
Thank you all for your comments.
New approach inspired by #paul-cornelius's answer
class ResultCollector(object):
def __init__(self, raise_on_error=True):
self.result_list = []
self.raise_on_error = raise_on_error
def do(self, func, *args, **kwds):
'''do can only deal with code block that can be wrapped into a function'''
try:
return func(*args, **kwds)
except Exception as e:
if not isinstance(e, AssertionError) and self.raise_on_error:
raise
self.result_list.append(e.message or e)
else:
self.result_list.append(None)
def assertClean(self):
assert not [x for x in self.result_list if x is not None], 'test results: {}'.format(self.result_list)
def __enter__(self):
self.result_list = []
return self
def __exit__(self, exc_t, exc_i, exc_tb):
if exc_t:
return None
self.assertClean()
return True
def test():
def can_be_refactored_into_func(p):
assert p%3, 'failed {}'.format(p)
def condition_for_skip(p):
return p%2
def condition_for_break(p):
return p>5
with ResultCollector() as rc:
for p in range(10):
if condition_for_skip(p):
rc.result_list.append('skipped {}'.format(p))
continue
if condition_for_break(p):
rc.result_list.append('ended {}'.format(p))
break
rc.do(can_be_refactored_into_func, p)
It works pretty well when the code into loop block can be divided up into functions like above.
How about a little class that only does the one thing you find yourself doing over and over:
class TestTracker:
def __init__(self):
self.error_list = []
def do_test(self, f, p):
try:
f(p)
except Exception as e:
self.error_list.append(e)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_value is not None:
self.error_list.append(exc_value)
return True
def test(params):
tt = TestTracker()
for p in params:
tt.do_test(run_test, p)
assert isEmpty(tt.error_list), 'error encountered: {}'.format(tt.error_list)
def test2(params):
tt = TestTracker()
for p in params:
with tt:
# a block of code with loop control statements
pass
assert isEmpty(tt.error_list), 'error encountered: {}'.format(tt.error_list)
I modified this answer to make the class a context manager. The test2 shows how that can be used with loop control statements. If no exception is raised within the context, the arguments to __exit__ will be None.
You could even mix with statements and calls to do_test.
Python can do anything!
Edits :
Add some convenience to TestTracker
class TestTracker:
def __init__(self):
self.error_list = []
def do_test(self, f, p):
try:
f(p)
except Exception as e:
self.error_list.append(e)
def __bool__(self):
return len(self.error_list) == 0
def __str__(self):
return 'error encountered: {}'.format(self.error_list)
def test(params):
tt = TestTracker()
for p in params:
tt.do_test(run_test, p)
assert tt, str(tt)
I use pytest and try to raise an exception at testing phase to test whether the exception could be handled by the method. But seems it always pass, even I delete the try...except... block.
This is the class which has an error and has been handled
class SaltConfig(GridLayout):
def check_phone_number_on_first_contact(self, button):
s = self.instanciate_ServerMsg(tt)
try:
s.send()
except HTTPError as err:
print("[HTTPError] : " + str(err.code))
return
# some code when running without error
def instanciate_ServerMsg():
return ServerMsg()
This is the helper class which generates the ServerMsg object used by the former class.
class ServerMsg(OrderedDict):
def send(self,answerCallback=None):
#send something to server via urllib.urlopen
This is my tests code:
class TestSaltConfig:
def test_check_phone_number_on_first_contact(self):
myError = HTTPError(url="http://127.0.0.1", code=500,
msg="HTTP Error Occurs", hdrs="donotknow", fp=None)
mockServerMsg = mock.Mock(spec=ServerMsg)
mockServerMsg.send.side_effect = myError
mockSalt = mock.Mock(spec=SaltConfig)
mockSalt.instanciate_ServerMsg.return_value = mockServerMsg
mockSalt.check_phone_number_on_first_contact(self, "2")
I think the above code doesn't make much sense since I'm actually testing on a mockObject, but the reason is that I don't know how to raise an exception when calling a method when the exception is already handled.
How to solve it? Thanks
Here is an example using decorator instead of Mock library to achieve your purpose.
I slightly modified your code to make it runnable in my environment.
import unittest
def exception_function(f, exception_type):
def exception_fn(*args, **kwargs):
raise exception_type
def fn(*args, **kwargs):
return exception_fn
return fn
def wrap(f, exception_type):
#exception_function(f, exception_type)
def fn(*args, **kwargs):
return f(*args, **kwargs)
return fn
class ServerMsg():
def send(self):
print("send normally")
class SaltConfig():
def check_phone_number_on_first_contact(self):
s = ServerMsg()
try:
s.send()
except ValueError:
print("raise exception")
class TestSaltConfig(unittest.TestCase):
def test_check_phone_number_on_first_contact(self):
s = SaltConfig()
original_method = ServerMsg.send
print (ServerMsg.send) #<unbound method ServerMsg.send>
s.check_phone_number_on_first_contact() #send normally
ServerMsg.send = wrap(ServerMsg.send, ValueError)
print (ServerMsg.send) #<unbound method ServerMsg.exception_fn>
s.check_phone_number_on_first_contact() #raise exception
ServerMsg.send = original_method
print (ServerMsg.send) #<unbound method ServerMsg.send>
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?
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()
To deal with an exception in an __enter__ method, the most straightforward (and less surprising) thing to do, would be to wrap the with statement itself in a try-except clause, and simply raise the exception -
But, with blocks are definetelly not designed to work like this - to be, by themselves "retriable" - and there is some misunderstanding here:
def __enter__(self):
for _ in range(self.retries):
try:
self.attempts += 1
return self
except Exception as e:
err = e
Once you return self there, the context were __enter__ runs no longer exists - if an error occurs inside the with block, it will just flow naturally to the __exit__ method. And no, the __exit__ method can not, in anyway, make the execution flow go back to the beginning of the with block.
You are probably wanting something more like this:
class Retrier(object):
max_retries = 3
def __init__(self, ...):
self.retries = 0
self.acomplished = False
def __enter__(self):
return self
def __exit__(self, exc, value, traceback):
if not exc:
self.acomplished = True
return True
self.retries += 1
if self.retries >= self.max_retries:
return False
return True
....
x = Retrier()
while not x.acomplished:
with x:
...
I think this one is easy, and other folks seem to be overthinking it. Just put the resource fetching code in __enter__, and try to return, not self, but the resource fetched. In code:
def __init__(self, retries):
...
# for demo, let's add a list to store the exceptions caught as well
self.errors = []
def __enter__(self):
for _ in range(self.retries):
try:
return resource # replace this with real code
except Exception as e:
self.attempts += 1
self.errors.append(e)
# this needs to return True to suppress propagation, as others have said
def __exit__(self, exc_type, exc_val, traceback):
print 'Attempts', self.attempts
for e in self.errors:
print e # as demo, print them out for good measure!
return True
Now try it:
>>> with retry(retries=3) as resource:
... # if resource is successfully fetched, you can access it as `resource`;
... # if fetching failed, `resource` will be None
... print 'I get', resource
I get None
Attempts 3
name 'resource' is not defined
name 'resource' is not defined
name 'resource' is not defined
I found contextmanager from contextlib useful, hope this may be helpful.
from contextlib import contextmanager
#contextmanager
def handler(*args, **kwargs):
try:
# print(*args, **kwargs)
yield
except Exception:
# Handle exception
Now, to use it,
# Add optional args or kwargs
with handler():
# Code with probable exception
print("Hi")
You don't have to implement the retry functionality manually. Take a look at the tenacity library.
Tenacity is a general-purpose retrying library, written in Python, to
simplify the task of adding retry behavior to just about anything.
You can simply add #retry decorator with parameters to your function.
Also,
Tenacity allows you to retry a code block without the need to wraps it
in an isolated function. The trick is to combine a for loop and a
context manager.
Can you suggest a way to code a drop-in replacement for the "with" statement that will work in Python 2.4?
It would be a hack, but it would allow me to port my project to Python 2.4 more nicely.
EDIT:
Removed irrelevant metaclass sketch
Just use try-finally.
Really, this may be nice as a mental exercise, but if you actually do it in code you care about you will end up with ugly, hard to maintain code.
You could (ab)use decorators to do this, I think. The following works, eg:
def execute_with_context_manager(man):
def decorator(f):
target = man.__enter__()
exc = True
try:
try:
f(target)
except:
exc = False
if not man.__exit__(*sys.exc_info()):
raise
finally:
if exc:
man.__exit__(None, None, None)
return None
return decorator
#execute_with_context_manager(open("/etc/motd"))
def inside(motd_file):
for line in motd_file:
print line,
(Well, in Python 2.4 file objects don't have __enter__ and __exit__ methods, but otherwise it works)
The idea is you're replacing the with line in:
with bar() as foo:
do_something_with(foo)
do_something_else_with(foo)
# etc...
with the decorated function "declaration" in:
#execute_with_context_manager( bar() )
def dummyname( foo ):
do_something_with(foo)
do_something_else_with(foo)
# etc...
but getting the same behaviour (the do_something_... code executed). Note the decorator changes the function declaration into an immediate invocation which is more than a little evil.
Since you need to exit the context manager both during errors and not errors, I don't think it's possible to do a generic usecase with metaclasses, or in fact at all. You are going to need try/finally blocks for that.
But maybe it's possible to do something else in your case. That depends on what you use the context manager for.
Using __del__ can help in some cases, like deallocating resource, but since you can't be sure it gets called, it can only be used of you need to release resources that will be released when the program exits. That also won't work if you are handling exceptions in the __exit__ method.
I guess the cleanest method is to wrap the whole context management in a sort of context managing call, and extract the code block into a method. Something like this (untested code, but mostly stolen from PEP 343):
def call_as_context_manager(mgr, function):
exit = mgr.__exit__
value = mgr.__enter__()
exc = True
try:
try:
function(value)
except:
exc = False
if not exit(*sys.exc_info()):
raise
finally:
if exc:
exit(None, None, None)
How about this?
def improvize_context_manager(*args, **kwargs):
assert (len(args) + len(kwargs)) == 1
if args:
context_manager = args[0]
as_ = None
else: # It's in kwargs
(as_, context_manager) = kwargs.items()[0]
def decorator(f):
exit_ = context_manager.__exit__ # Not calling it yet
enter_ = context_manager.__enter__()
exc = True
try:
try:
if as_:
f(*{as_: enter_})
else:
f()
except:
exc = False
if not exit_(*sys.exc_info()):
raise
finally:
if exc:
exit_(None, None, None)
return None
return decorator
Usage:
#improvize_context_manager(lock)
def null():
do(stuff)
Which parallels the with keyword without as.
Or:
#improvize_context_manager(my_lock=lock)
def null(my_lock):
do(stuff_with, my_lock)
Which parallels the with keyword with the as.
If you are OK with using def just to get a block, and decorators that immediately execute, you could use the function signature to get something more natural for the named case.
import sys
def with(func):
def decorated(body = func):
contexts = body.func_defaults
try:
exc = None, None, None
try:
for context in contexts:
context.__enter__()
body()
except:
exc = sys.exc_info()
raise
finally:
for context in reversed(contexts):
context.__exit__(*exc)
decorated()
class Context(object):
def __enter__(self):
print "Enter %s" % self
def __exit__(self, *args):
print "Exit %s(%s)" % (self, args)
x = Context()
#with
def _(it = x):
print "Body %s" % it
#with
def _(it = x):
print "Body before %s" % it
raise "Nothing"
print "Body after %s" % it