I'm using assertRaises in my unit test to test the raising of specific exceptions.
assertRaises(IOError, testToRun, passedValues)
Though some of the exceptions I need to capture have specific error numbers (errno), so instead of collecting the base exception I'd like to capture the specific error number relating to that exception. Something like this, though it obviously doesn't work :)
assertRaises(IOError.errno(2), testToRun, passedValue)
To get around this when I want to capture specificly numbered exceptions I've been using:-
try:
testToRun(passedValues)
except IOError, e:
if e.errno == 2:
pass
else:
raise
I'm sure it's not perfect but it works, but was wondering if it is possible to use assertRaises to do the same thing is a lot more compact way.
Thanks.
Since 2.7 it's possible to use assertRaises with a context manager:
with self.assertRaises(SomeException) as cm:
do_something()
the_exception = cm.exception
self.assertEqual(the_exception.error_code, 3)
You could also create a new TestCase function using your current code:
def assertRaisesErrNo(self, exc, errno, f, *args, **kwargs):
try:
self.assertRaises(exc, f, *args, **kwargs)
except IOError, e:
if e.errno == errno:
pass
else:
raise
unittest.TestCase.assertRaisesErrNo = assertRaisesErrNo
Then use it like any other assert method:
class TestSomething(unittest.TestCase):
def test_somthing(self):
self.assertRaisesErrNo(IOError, 2, myfunction)
You could also turn this into a context manager fairly easily using contextlib.contextmanager
Related
I have the code block like below:
try:
method()
except ErrorType1:
todo()
return
except ErrorType2 as e:
todo()
raise e
Basically for the two error types, I need to execute todo() first, then either return or raise e. Is it possible to just write todo() once? I was thinking using finally but don't think that actually works.
You could catch both exceptions in one except clause, execute todo and then decide to do based on the exception type:
try:
method()
except (ErrorType1, ErrorType2) as e:
todo()
if isinstance(e, ErrorType1):
return
raise
Note - as pointed out by #ShadowRanger in the comments to the question - you should just use raise to re-raise the existing exception, using raise e will raise a second copy of it, resulting in the traceback including the line with raise e on it as well as the line where the original error occurred.
If you have an common set of instructions (either encapsulated as a function or series of functions) that must be executed as part of an exception handling, consider using a context manager to encapsulate the common bits. The following two results in identical outcome, albeit with different construction (one using try..finally, the other using try..except).
from contextlib import contextmanager
#contextmanager
def context1(method):
print("starting context1")
completed = False
try:
yield method()
completed = True
finally:
if completed:
commit()
else:
rollback()
print("finished")
#contextmanager
def context2(method):
print("starting context2")
try:
yield method()
except Exception:
rollback()
raise
else:
commit()
print("finished")
The latter one will not be able to deal with KeyboardInterrupt or other exceptions that subclass off BaseException, so for certain use case this is not exactly ideal, though it is included to follow suite of the question. The first one is more of a response to the fact that you never appeared to have tried using finally, but rather simply thinking it does not actually works, and thus provided to show it can be used to achieve your goal (where only todo() in the question is executed if failure, through the use of a boolean variable).
In both cases, note how the common control flow is fully encapsulated inside the context manager, and usage is fairly straightforward like so such that all the unique extra cases can be done with another try..except block around the with context block.
try:
with context1(f) as result:
pass # or use the result to do something
except Exception: ...
# all the special unique cases be handled here.
To complete demo, more code is below; the commit and rollback functions I defined the following:
def commit():
print("commit")
def rollback():
print("rollback")
Now to test it, I defined the following helpers:
from functools import partial
class ErrorType1(Exception):
pass
class ErrorType2(Exception):
pass
def raise_e(e):
raise e
subject = [
object,
partial(raise_e, ErrorType1),
partial(raise_e, ErrorType2),
]
With the tests defined as such (replace context1 with context2 for the other demonstration):
for f in subject:
try:
with context1(f) as result:
print('done - got result %r' % result)
except ErrorType2:
print("ErrorType2 will be raised")
# raise # uncomment to actually re-raise the exception
except Exception as e:
print("Exception trapped: %r raised by %r" % (e, f))
Note the output of both the above should look about like so (aside from context1 vs context2):
starting context1
done - got result <object object at 0x7f20ccd3e180>
commit
finished
starting context1
rollback
Exception trapped: ErrorType1() raised by functools.partial(<function raise_e at 0x7f20ccb30af0>, <class '__main__.ErrorType1'>)
starting context1
rollback
ErrorType2 will be raised
Scraping webpages with Python 3.8, Selenium and BeautifulSoap, I would like to remove or alter some elements. Since not all pages contain the respective elements, I have to catch exceptions:
try:
soup.find('aside', id="post").decompose()
except Exception:
pass
try:
soup.find('footer', id="footer").decompose()
except Exception:
pass
try:
soup.find(class_="myclass")["class"] = ''
except Exception:
pass
There is a lot of repetition in this code (my list of statements is even longer), so I tried to build a block:
try:
soup.find('aside', id="post").decompose()
soup.find('footer', id="footer").decompose()
soup.find(class_="myclass")["class"] = ''
except Exception:
pass
But this isn't what I want to achieve, because if first statement doesn't catch a match, then the following statements aren't evaluated at all.
What's a good, pythonic and elegant way to execute/evaluate all statements? I read, that using pass is bad practice also. Maybe try isn't the correct thing here at all and would be better off using something like isset() in PHP (but in python I don't know the eqivalent)?
Not an ideal solution, but you can decorate functions to ignore exceptions and then use decorated functions instead of originals:
from functools import wraps
def exceptions_ignored(callee):
#wraps(callee)
def _ignore(*args, **kwargs):
try:
return callee(*args, **kwargs)
except Exception:
pass
return _ignore
mydivmod = exceptions_ignored(divmod)
# or define it as
# #exceptions_ignored
# def mydivmod(n, d):
# return divmod(n, d)
mydivmod(5, 0)
I have a requirement to execute multiple Python statements and few of them might fail during execution, even after failing I want the rest of them to be executed.
Currently, I am doing:
try:
wx.StaticBox.Destroy()
wx.CheckBox.Disable()
wx.RadioButton.Enable()
except:
pass
If any one of the statements fails, except will get executed and program exits. But what I need is even though it is failed it should run all three statements.
How can I do this in Python?
Use a for loop over the methods you wish to call, eg:
for f in (wx.StaticBox.Destroy, wx.CheckBox.Disable, wx.RadioButton.Enable):
try:
f()
except Exception:
pass
Note that we're using except Exception here - that's generally much more likely what you want than a bare except.
If an exception occurs during a try block, the rest of the block is skipped. You should use three separate try clauses for your three separate statements.
Added in response to comment:
Since you apparently want to handle many statements, you could use a wrapper method to check for exceptions:
def mytry(functionname):
try:
functionname()
except Exception:
pass
Then call the method with the name of your function as input:
mytry(wx.StaticBox.Destroy)
I would recommend creating a context manager class that suppress any exception and the exceptions to be logged.
Please look at the code below. Would encourage any improvement to it.
import sys
class catch_exception:
def __init__(self, raising=True):
self.raising = raising
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
if issubclass(type, Exception):
self.raising = False
print ("Type: ", type, " Log me to error log file")
return not self.raising
def staticBox_destroy():
print("staticBox_destroy")
raise TypeError("Passing through")
def checkbox_disable():
print("checkbox_disable")
raise ValueError("Passing through")
def radioButton_enable():
print("radioButton_enable")
raise ValueError("Passing through")
if __name__ == "__main__":
with catch_exception() as cm:
staticBox_destroy()
with catch_exception() as cm:
checkbox_disable()
with catch_exception() as cm:
radioButton_enable()
I'm currently writing a wrapper class. I want to be able to log exceptions properly but allow calling methods to be aware of exceptions which occur. My class looks like this:
import logging
log = logging.getLogger('module')
class MyAPIWrapper(library.APIClass):
def __init__(self):
self.log = logging.getLogger('module.myapiwrapper')
def my_wrapper_method(self):
try:
response = self.call_api_method()
return response.someData
except APIException, e:
self.log.exception('Oh noes!')
raise e #Throw exception again so calling code knows it happened
I'm a bit dubious about catching and exception just to log it and then re-raising it so the calling code can do something about it. What's the proper pattern here?
There is nothing wrong with catching to log. However, I'd recommend:
try:
response = self.call_api_method()
except APIException, e: # or 'as e' depending on your Python version
self.log.exception('Oh noes!')
raise #Throw exception again so calling code knows it happened
else:
return response.someData
By just doing a bare raise you preserve the full traceback info. It's also more explicit to put code that will only happen if you don't have an exception in the else clause, and it's clearer what line you're catching an exception from.
It would also be fine for the calling class to do the logging if it's handling the error anyway, but that may not be convenient for your app.
Edit: The documentation for try ... except ... else ... finally is under compound statements.
That method is correct, although instead of raise e you should just use raise, which will automatically re-raise the last exception. This is also one of the rare cases where using a blanket except is considered acceptable.
Here is an example very similar to what you are doing from the Python docs on Handling Exceptions:
The last except clause may omit the exception name(s), to serve as a wildcard. Use this with extreme caution, since it is easy to mask a real programming error in this way! It can also be used to print an error message and then re-raise the exception (allowing a caller to handle the exception as well):
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except IOError as (errno, strerror):
print "I/O error({0}): {1}".format(errno, strerror)
except ValueError:
print "Could not convert data to an integer."
except:
print "Unexpected error:", sys.exc_info()[0]
raise
You can just extend the standard Exception class and add the logger into there.
Like this:
class LoggedException(Exception):
""" An exception that also logs the msg to the given logger. """
def __init__(self, logger: logging.Logger, msg: str):
logger.error(msg)
super().__init__(msg)
When handling exceptions in python, I find myself repeating code quite often. The basic pattern is something of the form:
try:
action_here()
except CommonException1:
Action_always_taken_for_CommonException1()
except CommonException2:
Action_always_taken_for_CommonException2()
except Exception:
Default_action_always_taken()
What I would like to do is to some how abstract this repetitive code out to a function or class. I know one way to do it is to call an exception handling function with the exception object, such as:
try:
action_here()
except Exception as e:
handle_exception(e)
Then in this function determine the exception based on class.
def handle_exception(e):
if type(e) == type(CommonException1()):
Action_always_taken_for_CommonException1()
elif type(e) == type(CommonException2()):
Action_always_taken_for_CommonException2())
else:
Default_action_always_taken()
This, however, feels clunky and inelegant. So my question is, what are some other alternatives to handling repetitive exception handling?
This situation is one of the main use cases for context managers and the with statement:
from __future__ import with_statement # Needed in 2.5, but not in 2.6 or later
from contextlib import contextmanager
#contextmanager
def handle_exceptions():
try:
yield # Body of the with statement effectively runs here
except CommonException1:
Action_always_taken_for_CommonException1()
except CommonException2:
Action_always_taken_for_CommonException2()
except Exception:
Default_action_always_taken()
# Used as follows
with handle_exceptions():
action_here()
If you dislike the repeated if / elseif blocks, you could put your handles in a dict, keyed by type:
handlers = { type(CommonException1()) : Action_always_taken_forCommonException1,
type(CommonException2()) : Action_always_taken_forCommonException2 }
def handle_exception(te):
if te in handlers:
handlers[te]()
else:
Default_action()
Which you could then run with:
try:
action_here()
except Exception as e:
handle_exception(type(e))
In addition: If you find yourself writing these try blocks frequently, then you could write your own context manager (see here). At the action_here() side, your code would then look like this:
with my_error_handling_context():
action_here1()
action_here2()
In this case, the handle_exception code would essentially be your context manager's __exit__ method (which will always get passed any exceptions raised during the with block).
Although a solution using a context manager (as proposed by others) is the most elegant, and would be what I would recommend too, I'd like to point out that your handle_exception function could be written more elegantly by re-raising the exception:
def handle_exception(e):
try:
raise e
except CommonException1:
Action_always_taken_for_CommonException1()
except CommonException2:
Action_always_taken_for_CommonException2()
except Exception:
Default_action_always_taken()