py.test assert may raise, and if it raises it will be __ - python

Is there pytest functionality similar to pytest.raises that passes iff the block raises the specified exception, or doesn't raise at all? Something like:
def test_encode_err(ulist):
with pytest.maybe_raises_but_only(UnicodeEncodeError): # <== ?
assert encode_list(ulist, 'ascii') == map(lambda x:x.encode('ascii'), ulist)
This question came up in the following situation..
The function to test:
def encode_list(lst, enc):
"Encode all unicode values in ``lst`` using ``enc``."
return [(x.encode(enc) if isinstance(x, unicode) else x) for x in lst]
A couple of simple tests (fixtures below):
def test_encode_err(ulist):
with pytest.raises(UnicodeEncodeError):
assert encode_list(ulist, 'ascii')
def test_encode_u8(ulist, u8list):
assert encode_list(ulist, 'u8') == u8list
The fixtures:
#pytest.fixture(
scope='module',
params=[
u'blåbærsyltetøy',
u'', # <==== problem
]
)
def ustr(request):
print 'testing with:', `request.param`
return request.param
#pytest.fixture
def u8str(ustr):
return ustr.encode('u8')
#pytest.fixture
def ulist(ustr):
return [ustr, ustr]
#pytest.fixture
def u8list(u8str):
return [u8str, u8str]
the indicated <== problem is only a problem for test_encode_err() (and not test_encode_u8()), and happens since u''.encode('ascii') doesn't raise a UnicodeEncodeError (no unicode strings that doesn't contain characters above code point 127 will raise).
Is there a py.test function that covers this use case?

If you don't care when the exception is thrown just write the code as normal but put a try...except block round it to ignore the error.
def test_encode_err(ulist):
try:
assert encode_list(ulist, 'ascii') == map(lambda x:x.encode('ascii'), ulist)
except UnicodeDecodeError:
pass
Really though consider whether you should be writing a test at all if you don't know whether the code will throw an exception. Try pinning down the data a bit more and having two tests, one which raises the exception and one which doesn't.

I consider the provided response really incomplete. I like to parametrize tests for functions that could accepts different values.
Consider the following function that only accepts empty strings, in which case returns True. If you pass other type raises a TypeError and if the passed string is not empty a ValueError.
def my_func_that_only_accepts_empty_strings(value):
if isinstance(value, str):
if value:
raise ValueError(value)
return True
raise TypeError(value)
You can conveniently write parametric tests for all cases in a single test in different ways:
import contextlib
import pytest
parametrization = pytest.mark.parametrize(
('value', 'expected_result'),
(
('foo', ValueError),
('', True),
(1, TypeError),
(True, TypeError),
)
)
#parametrization
def test_branching(value, expected_result):
if hasattr(expected_result, '__traceback__'):
with pytest.raises(expected_result):
my_func_that_only_accepts_empty_strings(value)
else:
assert my_func_that_only_accepts_empty_strings(
value,
) == expected_result
#parametrization
def test_without_branching(value, expected_result):
ctx = (
pytest.raises if hasattr(expected_result, '__traceback__')
else contextlib.nullcontext
)
with ctx(expected_result):
assert my_func_that_only_accepts_empty_strings(
value,
) == expected_result
Note that when an exception raises inside pytest.raises context, the contexts exits so the later assert ... == expected_result is not executed when the exception is catch. If other exception raises, it is propagated to your test so the comparison is not executed either. This allows you to write more assertions after the execution of the function for successfull calls.
But this can be improved in a convenient maybe_raises fixture, that is what you're looking for at first:
#contextlib.contextmanager
def _maybe_raises(maybe_exception_class, *args, **kwargs):
if hasattr(maybe_exception_class, '__traceback__'):
with pytest.raises(maybe_exception_class, *args, **kwargs):
yield
else:
yield
#pytest.fixture()
def maybe_raises():
return _maybe_raises
And the test can be rewritten as:
#parametrization
def test_with_fixture(value, expected_result, maybe_raises):
with maybe_raises(expected_result):
assert my_func_that_only_accepts_empty_strings(
value,
) == expected_result
Really nice, right? Of course you need to know how the magic works to write the test properly, always knowing that the context will exits when the exception is catched.
I think that pytest does not includes this because could be a really confusing pattern that could lead to unexpected false negatives and bad tests writing. Rather than that, pytest documentation encourauges you to pass expectation contexts as parameters but for me this solution looks really ugly.
EDIT: just packaged this fixture, see pytest-maybe-raises.

Related

Test assert function order with mocks (pytest -> assert_has_calls)

I'm trying to test the order of the sub-functions inside of the main function:
def get_data():
pass
def process_data(data):
pass
def notify_admin(action):
pass
def save_data(data):
pass
def main_func():
notify_admin('start')
data = get_data()
processed_data = process_data(data)
save_data(processed_data)
notify_admin('finish')
I'm using pytest, so far I've come up with this:
import pytest
from unittest.mock import patch, Mock, call
from main_func import main_func
#patch('main_func.notify_admin')
#patch('main_func.get_data')
#patch('main_func.process_data')
#patch('main_func.save_data')
def test_main_func(mock_4, mock_3, mock_2, mock_1):
execution_order = [mock_1, mock_2, mock_3, mock_4]
order_mock = Mock()
for order, mock in enumerate(execution_order):
order_mock.attach_mock(mock, f'f_{order}')
main_func()
order_mock.assert_has_calls([
call.f_1(),
call.f_2(),
call.f_3(),
call.f_4(),
call.f_1(),
])
This is an error, which I'm not sure how to resolve:
E AssertionError: Calls not found.
E Expected: [call.f_1(), call.f_2(), call.f_3(), call.f_4(), call.f_1()]
E Actual: [call.f_1('start'),
E call.f_2(),
E call.f_3(<MagicMock name='mock.f_3()' id='2049968460848'>),
E call.f_4(<MagicMock name='mock.f_2()' id='2049968489424'>),
E call.f_1('finish')]
Could you please suggest ways to resolve it or maybe implement it in a different way?
I've read documentation of assert_has_calls but I'm still not sure how to use it for this particular case.
If you want to check the call order without the argument list, you can use the method_calls attribute of the mock, which contains a list of calls in the order they are made, and only check their name:
...
main_func()
assert len(order_mock.method_calls) == 4
assert order_mock.method_calls[0][0] == "f_1"
assert order_mock.method_calls[1][0] == "f_2"
assert order_mock.method_calls[2][0] == "f_3"
assert order_mock.method_calls[3][0] == "f_4"
Each method call is a tuple of name, positional arguments and keyword arguments, so if you want to check only the name you can just use the first index.
Note that the output of your test does not seem to match this, but this is a matter of your actual application logic.
If you are using has_calls, you have to provide each argument, which is also possible. This time taking the actual result of your test, something like this should work:
...
main_func()
order_mock.assert_has_calls([
call.f_1('start'),
call.f_2(),
call.f_3(mock1),
call.f_4(mock2),
call.f_1('finish')
])

How to assert that an iterable is not empty on Unittest?

After submitting queries to a service, I get a dictionary or a list back and I want to make sure it's not empty. I using Python 2.7.
I am surprised of not having any assertEmpty method for the unittest.TestCase class instance.
The existing alternatives just don't look right:
self.assertTrue(bool(d))
self.assertNotEqual(d,{})
self.assertGreater(len(d),0)
Is this kind of a missing method in the Python unittest framework? If yes, what would be the most pythonic way to assert that an iterable is not empty?
Empty lists/dicts evaluate to False, so self.assertTrue(d) gets the job done.
Depends exactly what you are looking for.
If you want to make sure the object is an iterable and it is not empty:
# TypeError: object of type 'NoneType' has no len()
# if my_iterable is None
self.assertTrue(len(my_iterable))
If it is OK for the object being tested to be None:
self.assertTrue(my_maybe_iterable)
"Falsy" values in Python
A falsy (sometimes written falsey) value is a value that is considered false when encountered in a Boolean context.
According to the official doc, the following built-in types evaluate to false:
constants defined to be false: None and False.
zero of any numeric type: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
empty sequences and collections: '', (), [], {}, set(), range(0)
Therefore, it's possible to check for
non-emptiness with assertTrue() and for
emptiness with assertFalse().
(The official doc has a full list of all available assert methods.)
Clean Code
All those assertTrue() and assertFalse() calls are kind of misleading as we wanted to check for emptiness and one needs to know which types evaluate to false to properly understand what's happening in the test.
So, for the sake of clean code and for better readability, we can simply define our own assertEmpty() and assertNotEmpty() methods like so:
def assertEmpty(self, obj):
self.assertFalse(obj)
def assertNotEmpty(self, obj):
self.assertTrue(obj)
Maybe:
self.assertRaises(StopIteration, next(iterable_object))
All the credit for this goes to winklerrr, I am just extending his idea: have importable mixins for when you need assertEmpty or assertNotEmpty:
class AssertEmptyMixin( object ):
def assertEmpty(self, obj):
self.assertFalse(obj)
class AssertNotEmptyMixin( object ):
def assertNotEmpty(self, obj):
self.assertTrue(obj)
Caveat, mixins should go on the left:
class MyThoroughTests( AssertNotEmptyMixin, TestCase ):
def test_my_code( self ):
...
self.assertNotEmpty( something )
Based on #winklerr's answer and #Merk's comment, I extended the idea for checking whether the given object is a Container in the first place.
from typing import Container
def assertContainerEmpty(self, obj: Container) -> None:
"""Asserts whether the given object is an empty container."""
self.assertIsInstance(obj, Container)
self.assertFalse(obj)
def assertContainerNotEmpty(self, obj: Container) -> None:
"""Asserts whether the given object is a non-empty container."""
self.assertIsInstance(obj, Container)
self.assertTrue(obj)
This means that assertEmpty and assertNotEmpty will always fail if the given object is e.g. a float, or an instance of an user-defined class - no matter if it would properly evaluate to True/False.
A slightly different answer to those already proposed... If specific named assertions are absolutely required, you could subclass TestCase and add methods for new assertions there.
from pathlib import Path
from typing import Container
from unittest import TestCase
class BaseTestCase(TestCase):
def assertIsFile(self, path: str, msg: str=None) -> None:
default_msg = 'File does not exist: {0}'.format(path)
msg = msg if msg is not None else default_msg
if not Path(path).resolve().is_file():
raise AssertionError(msg)
def assertIsEmpty(self, obj: Container, msg: str=None) -> None:
default_msg = '{0} is not empty.'.format(obj)
msg = msg if msg is not None else default_msg
self.assertIsInstance(obj, Container, '{0} is not a container.'.format(obj))
if len(obj) > 0:
raise AssertionError(msg)
def assertIsNotEmpty(self, obj: Container, msg: str=None) -> None:
default_msg = '{0} is empty.'.format(obj)
msg = msg if msg is not None else default_msg
self.assertIsInstance(obj, Container, '{0} is not a container.'.format(obj))
if obj is None or len(obj) == 0:
raise AssertionError(msg)
And then subclass the new BaseTestCase class to use the new assertion methods.
class TestApplicationLoadBalancer(_BaseTestCase):
def setUp(self) -> None:
# These assertions will fail.
self.assertIsFile('does-not-exist.txt')
self.assertIsEmpty(['asdf'])
self.assertIsNotEmpty([])
Just like the built-in unittest assertions, you can pass an error message to these if desired.
class TestApplicationLoadBalancer(_BaseTestCase):
def setUp(self) -> None:
# These assertions will fail.
self.assertIsFile('does-not-exist.txt', 'Foo')
self.assertIsEmpty(['asdf'], 'Bar')
self.assertIsNotEmpty([], 'Baz')

General decorator to wrap try except in python?

I'd interacting with a lot of deeply nested json I didn't write, and would like to make my python script more 'forgiving' to invalid input. I find myself writing involved try-except blocks, and would rather just wrap the dubious function up.
I understand it's a bad policy to swallow exceptions, but I'd rather prefer they to be printed and analysed later, than to actually stop execution. It's more valuable, in my use-case to continue executing over the loop than to get all keys.
Here's what I'm doing now:
try:
item['a'] = myobject.get('key').METHOD_THAT_DOESNT_EXIST()
except:
item['a'] = ''
try:
item['b'] = OBJECT_THAT_DOESNT_EXIST.get('key2')
except:
item['b'] = ''
try:
item['c'] = func1(ARGUMENT_THAT_DOESNT_EXIST)
except:
item['c'] = ''
...
try:
item['z'] = FUNCTION_THAT_DOESNT_EXIST(myobject.method())
except:
item['z'] = ''
Here's what I'd like, (1):
item['a'] = f(myobject.get('key').get('subkey'))
item['b'] = f(myobject.get('key2'))
item['c'] = f(func1(myobject)
...
or (2):
#f
def get_stuff():
item={}
item['a'] = myobject.get('key').get('subkey')
item['b'] = myobject.get('key2')
item['c'] = func1(myobject)
...
return(item)
...where I can wrap either the single data item (1), or a master function (2), in some function that turns execution-halting exceptions into empty fields, printed to stdout. The former would be sort of an item-wise skip - where that key isn't available, it logs blank and moves on - the latter is a row-skip, where if any of the fields don't work, the entire record is skipped.
My understanding is that some kind of wrapper should be able to fix this. Here's what I tried, with a wrapper:
def f(func):
def silenceit():
try:
func(*args,**kwargs)
except:
print('Error')
return(silenceit)
Here's why it doesn't work. Call a function that doesn't exist, it doesn't try-catch it away:
>>> f(meow())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'meow' is not defined
Before I even add a blank return value, I'd like to get it to try-catch correctly. If the function had worked, this would have printed "Error", right?
Is a wrapper function the correct approach here?
UPDATE
I've had a lot of really useful, helpful answers below, and thank you for them---but I've edited the examples I used above to illustrate that I'm trying to catch more than nested key errors, that I'm looking specifically for a function that wraps a try-catch for...
When a method doesn't exist.
When an object doesn't exist, and is getting a method called on it.
When an object that does not exist is being called as an argument to a function.
Any combination of any of these things.
Bonus, when a function doesn't exist.
There are lots of good answers here, but I didn't see any that address the question of whether you can accomplish this via decorators.
The short answer is "no," at least not without structural changes to your code. Decorators operate at the function level, not on individual statements. Therefore, in order to use decorators, you would need to move each of the statements to be decorated into its own function.
But note that you can't just put the assignment itself inside the decorated function. You need to return the rhs expression (the value to be assigned) from the decorated function, then do the assignment outside.
To put this in terms of your example code, one might write code with the following pattern:
#return_on_failure('')
def computeA():
item['a'] = myobject.get('key').METHOD_THAT_DOESNT_EXIST()
item["a"] = computeA()
return_on_failure could be something like:
def return_on_failure(value):
def decorate(f):
def applicator(*args, **kwargs):
try:
return f(*args,**kwargs)
except:
print('Error')
return value
return applicator
return decorate
You could use a defaultdict and the context manager approach as outlined in Raymond Hettinger's PyCon 2013 presentation
from collections import defaultdict
from contextlib import contextmanager
#contextmanager
def ignored(*exceptions):
try:
yield
except exceptions:
pass
item = defaultdict(str)
obj = dict()
with ignored(Exception):
item['a'] = obj.get(2).get(3)
print item['a']
obj[2] = dict()
obj[2][3] = 4
with ignored(Exception):
item['a'] = obj.get(2).get(3)
print item['a']
It's very easy to achieve using configurable decorator.
def get_decorator(errors=(Exception, ), default_value=''):
def decorator(func):
def new_func(*args, **kwargs):
try:
return func(*args, **kwargs)
except errors, e:
print "Got error! ", repr(e)
return default_value
return new_func
return decorator
f = get_decorator((KeyError, NameError), default_value='default')
a = {}
#f
def example1(a):
return a['b']
#f
def example2(a):
return doesnt_exist()
print example1(a)
print example2(a)
Just pass to get_decorator tuples with error types which you want to silence and default value to return.
Output will be
Got error! KeyError('b',)
default
Got error! NameError("global name 'doesnt_exist' is not defined",)
default
Edit: Thanks to martineau i changed default value of errors to tuples with basic Exception to prevents errors.
It depends on what exceptions you expect.
If your only use case is get(), you could do
item['b'] = myobject.get('key2', '')
For the other cases, your decorator approach might be useful, but not in the way you do it.
I'll try to show you:
def f(func):
def silenceit(*args, **kwargs): # takes all kinds of arguments
try:
return func(*args, **kwargs) # returns func's result
except Exeption, e:
print('Error:', e)
return e # not the best way, maybe we'd better return None
# or a wrapper object containing e.
return silenceit # on the correct level
Nevertheless, f(some_undefined_function())won't work, because
a) f() isn't yet active at the execution time and
b) it is used wrong. The right way would be to wrap the function and then call it: f(function_to_wrap)().
A "layer of lambda" would help here:
wrapped_f = f(lambda: my_function())
wraps a lambda function which in turn calls a non-existing function. Calling wrapped_f() leads to calling the wrapper which calls the lambda which tries to call my_function(). If this doesn't exist, the lambda raises an exception which is caught by the wrapper.
This works because the name my_function is not executed at the time the lambda is defined, but when it is executed. And this execution is protected and wrapped by the function f() then. So the exception occurs inside the lambda and is propagated to the wrapping function provided by the decorator, which handles it gracefully.
This move towards inside the lambda function doesn't work if you try to replace the lambda function with a wrapper like
g = lambda function: lambda *a, **k: function(*a, **k)
followed by a
f(g(my_function))(arguments)
because here the name resolution is "back at the surface": my_function cannot be resolved and this happens before g() or even f() are called. So it doesn't work.
And if you try to do something like
g(print)(x.get('fail'))
it cannot work as well if you have no x, because g() protects print, not x.
If you want to protect x here, you'll have to do
value = f(lambda: x.get('fail'))
because the wrapper provided by f() calls that lambda function which raises an exception which is then silenced.
Extending #iruvar answer - starting with Python 3.4 there is an existing context manager for this in Python standard lib: https://docs.python.org/3/library/contextlib.html#contextlib.suppress
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove('somefile.tmp')
with suppress(FileNotFoundError):
os.remove('someotherfile.tmp')
in your case you first evaluate the value of the meow call (which doesn't exist) and then wrap it in the decorator. this doesn't work that way.
first the exception is raised before it was wrapped, then the wrapper is wrongly indented (silenceit should not return itself). You might want to do something like:
def hardfail():
return meow() # meow doesn't exist
def f(func):
def wrapper():
try:
func()
except:
print 'error'
return wrapper
softfail =f(hardfail)
output:
>>> softfail()
error
>>> hardfail()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in hardfail
NameError: global name 'meow' is not defined
anyway in your case I don't understand why you don't use a simple method such as
def get_subkey(obj, key, subkey):
try:
return obj.get(key).get(subkey, '')
except AttributeError:
return ''
and in the code:
item['a'] = get_subkey(myobject, 'key', 'subkey')
Edited:
In case you want something that will work at any depth. You can do something like:
def get_from_object(obj, *keys):
try:
value = obj
for k in keys:
value = value.get(k)
return value
except AttributeError:
return ''
That you'd call:
>>> d = {1:{2:{3:{4:5}}}}
>>> get_from_object(d, 1, 2, 3, 4)
5
>>> get_from_object(d, 1, 2, 7)
''
>>> get_from_object(d, 1, 2, 3, 4, 5, 6, 7)
''
>>> get_from_object(d, 1, 2, 3)
{4: 5}
And using your code
item['a'] = get_from_object(obj, 2, 3)
By the way, on a personal point of view I also like #cravoori solution using contextmanager. But this would mean having three lines of code each time:
item['a'] = ''
with ignored(AttributeError):
item['a'] = obj.get(2).get(3)
Why not just use cycle?
for dst_key, src_key in (('a', 'key'), ('b', 'key2')):
try:
item[dst_key] = myobject.get(src_key).get('subkey')
except Exception: # or KeyError?
item[dst_key] = ''
Or if you wish write a little helper:
def get_value(obj, key):
try:
return obj.get(key).get('subkey')
except Exception:
return ''
Also you can combine both solutions if you have a few places where you need to get value and helper function would be more reasonable.
Not sure that you actually need a decorator for your problem.
Since you're dealing with lots of broken code, it may be excusable to use eval in this case.
def my_eval(code):
try:
return eval(code)
except: # Can catch more specific exceptions here.
return ''
Then wrap all your potentially broken statements:
item['a'] = my_eval("""myobject.get('key').get('subkey')""")
item['b'] = my_eval("""myobject.get('key2')""")
item['c'] = my_eval("""func1(myobject)""")
How about something like this:
def exception_handler(func):
def inner_function(*args, **kwargs):
try:
func(*args, **kwargs)
except TypeError:
print(f"{func.__name__} error")
return inner_function
then
#exception_handler
def doSomethingExceptional():
a=2/0
all credits go to:https://medium.com/swlh/handling-exceptions-in-python-a-cleaner-way-using-decorators-fae22aa0abec
Try Except Decorator for sync and async functions
Note: logger.error can be replaced with print
Latest version can be found here.

Using a context manager with Python assertRaises

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()

Double return in python class method

When I call the following method, two returns are executed and I can't figure out why.
def Build(self, name = None):
if self.buildData:
try:
installData = self.buildData.Build(name)
return BuildResult(True, installData)
except:
pass
else:
Log("Application has no <build> data")
return BuildResult(False, None)
What happens is this:
The method is called with a valid string, say "abc"
self.buildData.Build(name) is called and BuildResult(True, installData) constructor is run
The last return-statement is also executed
Well, I guess that BuildResult() fails, exception is passed and you go into second return. You can easy test it just by adding some debug print in except - never make exceptions silent :)
When return BuildResult(True, installData) is executed BuildResult(True, installData) is evaluated first. My guess is, this function raises an exception which is caught and passed. After that return BuildResult(False, None) is executed.
You should see what exception is raised and handle it properly.
I would rewrite the function as follows:
def Build(self, name = None):
if self.buildData:
try:
installData = self.buildData.Build(name)
except: # TODO: catch only expected exception
pass
return BuildResult(True, installData)
else:
Log("Application has no <build> data")
return BuildResult(False, None)

Categories

Resources