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)
Related
I want to use a decorator (composer) that recevices as parameter n number of decorators, this decorators will be used to decorate a function. Also I want to pass some parameters from two origins, a parameter named "SKIP" in the composer and another parameter named "parameter" sent by the parameter_sender decorator. Here's what I tried:
def compose(*decorators, SKIP=None):
def something(func):
#wraps(func)
def func_wrap(parameter = None, **kwargs):
try:
if SKIP:
print("I'm here")
return func(parameter = parameter,**kwargs)
else:
for decorator in reversed(decorators):
func = decorator(func, parameter = parameter,**kwargs) # --------- This line is providing the error ------------------
return func
raise exception
except Exception as e:
print(e)
raise exception
return func_wrap
return something
And here is an example of where do I want to use it. In this example I want to SKIP the composing of all the decorators if the variable SKIP is true.
#application.route("/function/<id_something>", methods=['GET'])
#parameter_sender
#compose(decorator_1,decorator_2, SKIP=True)
def function (id_something, **kwargs):
try:
#TODO:
return jsonify("ok")
except Exception as e:
print(e)
But i've got an error that says this:
>>I'm here
>>local variable 'func' referenced before assignment
Even when the if statement is working. PD: It works without the line indicated in the composer.
The following code should do the thing.
You were trying to set a value for a variable from outer scope. In my example I used separate temp variable composition.
def compose(*decorators, SKIP=None):
def something(func):
#wraps(func)
def func_wrap(*args, **kwargs):
try:
if SKIP:
print("I'm here")
return func(*args, **kwargs)
else:
composition = func
for decorator in reversed(decorators):
composition = decorator(composition)
return composition(*args, **kwargs)
except Exception as e:
print(e)
raise
return func_wrap
return something
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.
I want to execute several functions, gather their exceptions (if there are any), and raise a compound exception, calling as many of the functions as possible without breaking after one exception. For example, say I have
def f():
do_one()
do_two()
do_three()
The do_i functions don't depend on each other's status. The most obvious way to do what I want is this:
def f():
errors = []
for do_i in [do_one, do_two, do_three]:
try:
do_i()
except Exception as e:
errors.append(e)
if errors:
raise Exception(';'.join(errors))
or slightly better:
def catch_error(arr, f, *args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
arr.append(e)
return None
def f():
errors = []
for do_i in [do_one, do_two, do_three]:
catch_error(errors, do_i)
if errors:
raise Exception(';'.join(errors))
but this is still ugly. Is there a Pythonic way to do this that I'm missing, maybe with clever use of a with statement?
Edit: In a dream world Python would have this:
errors = []
awesome_block(errors):
do_one()
do_two()
do_three()
return 'yes!' if not errors else ';'.join(map(str, errors))
You could rewrite your function into a contextmanager, which does simplify your code a bit. I've maintained your convention of passing a list, though this yields the internal list, so you can use it later.
from contextlib import contextmanager
#contextmanager
def catch_errors(error_list=None):
error_list = error_list if error_list is not None else []
try:
yield error_list
except Exception as e:
error_list.append(e)
error_list = []
with catch_errors(error_list):
raise Exception("First exception")
with catch_errors(error_list):
raise ValueError("Second exception")
if error_list:
raise Exception(";".join(map(repr, error_list)))
I think repr is more useful than str here. #contextmanager allows usage in a with statement while you only have to write the function as a generator.
If you don't pass a list to the generator, then you need to keep track of the returned list.
with catch_errors() as errors1:
raise Exception("First exception")
print errors1 # Exception("First exception",)
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()
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