Correct order for exception handling in python - python-3.x

I want to check for an exception and an error in the except statements - WebDriverException and NameError. What would be the correct order of except statements?
Case 1. Check first for WebDriverException and then for NameError
try:
// do something
except WebDriverException:
pass
except NameError:
pass
Case 2. Check first for NameErrorand then for WebDriverException
try:
// do something
except NameError:
pass
except WebDriverException:
pass

Related

In pytest, how to test for sys.exit('some error message')?

I'm a beginner to python and pytest. I have a function that I'm trying to test for the exceptions part with Pytest:
def read_data(args) -> tuple[Polyline, str]:
parsed_args = input_parsing_1(args)
try:
with open(parsed_args.f) as f_input:
reader = csv.reader(f_input)
polyline = fill_poly_with_data(reader)
except FileNotFoundError as e:
sys.exit('Invalid input file \n')
else:
return (polyline, parsed_args.f)
I want to test if exceptions are risen and if the error message matches the one I put in the code above.
My attempts
#patch('project.open')
def test_read_data_error_SE(mock_open):
mock_open.side_effect = SystemExit
with pytest.raises(SystemExit):
assert read_data(['-f', ''])
#patch('project.open')
def test_read_data_error_FNFE2(mock_open):
mock_open.side_effect = FileNotFoundError
with pytest.raises(SystemExit):
with pytest.raises(FileNotFoundError):
assert read_data(['-f', 'cos'])
The above tests works fine.
I also wish to assert if sys.exit message matches 'Invalid input file \n'. I've tried:
#patch('project.open')
def test_read_data_error_SE1(mock_open):
mock_open.side_effect = SystemExit
with pytest.raises(SystemExit, match='Invalid input file'):
assert read_data(['-f', ''])
#patch('project.open')
def test_read_data_error_SE2(mock_open):
mock_open.side_effect = SystemExit
with pytest.raises(SystemExit) as e:
assert read_data(['-f', ''])
assert 'Invalid input file' in str(e.value)
but those tests fails:
===================================================== short test summary info ======================================================
FAILED test_project.py::test_read_data_error_SE1 - AssertionError: Regex pattern did not match.
FAILED test_project.py::test_read_data_error_SE2 - AssertionError: assert 'Invalid input file' in ''
I have seen some posts here on stackoverflow, like:
Verify the error code or message from SystemExit in pytest
How to properly assert that an exception gets raised in pytest?
Catch SystemExit message with Pytest
but none of them seems to answer my problem.
My questions:
It seems like my tested message 'Invalid input file' is being matched to an empty string ''? Why? How to properly catch and assert the sys.exit('some error message')?
You're seeing an empty string because you're raising SystemExit with no parameters:
#patch('project.open')
def test_read_data_error_SE1(mock_open):
mock_open.side_effect = SystemExit
with pytest.raises(SystemExit, match='Invalid input file'):
assert read_data(['-f', ''])
You've set mock_open.side_effect = SystemExit, so when you're code calls open(), it raises SystemExit with no parameters. If you want to see a message like Invalid input file, have your code explicitly exit by calling sys.exit('Invalid input file').
Your code already does that, but you're pre-empting that behavior by having the call to open raise SystemExit instead. You probably want open to raise FileNotFound instead:
#patch('project.open')
def test_read_data_error_SE1(mock_open):
mock_open.side_effect = FileNotFound
with pytest.raises(SystemExit, match='Invalid input file'):
assert read_data(['-f', ''])
This will get caught by the except in read_data, which will then call sys.exit.
Here's a complete example (I've included some stub functions here so that I can use your read_data method largely unmodified):
import csv
import sys
import types
import pytest
from unittest import mock
def fill_poly_with_data(x):
return x
def input_parsing_1(args):
return types.SimpleNamespace(f="testing")
def read_data(args) -> tuple:
parsed_args = input_parsing_1(args)
try:
with open(parsed_args.f) as f_input:
reader = csv.reader(f_input)
polyline = fill_poly_with_data(reader)
except FileNotFoundError:
sys.exit('Invalid input file \n')
else:
return (polyline, parsed_args.f)
#mock.patch('builtins.open')
def test_function_that_exits(mock_open):
mock_open.side_effect = FileNotFoundError
with pytest.raises(SystemExit, match="Invalid input file"):
read_data(['-f', 'cos'])

logging usability in python

I am implementing a simple logger in python.Find my code below, Sometimes it runs fine,at times I get ValueError at self.log('INFO','Append operation successful.')
Where am I doing wrong? I am new to python and have also referred to the doc. Unable to understand the inconsistency, also I am using python 3.9, tried using force parameter too. It is also not logging the level, like Critical, Info or Error.
import logging as logger
class data:
def __init__(self, filename,filetype,filesize,date):
self.filename=filename
self.filetype=filetype
self.filesize=filesize
self.date=date
def log(self,logtype,msg):
logger.basicConfig(filename="datalog.log",level=logger.INFO,force=True,format='%(asctime)s %(message)s')
if logtype == "INFO":
logger.info(msg)
elif logtype == "ERROR":
logger.ERROR(msg)
elif logtype == "CRITICAL":
logger.CRITICAL(msg)
else:
logger.info(msg)
def openFile(self):
try:
filename=self.filename+self.filetype
f=open(filename,'w+')
f.write("Hello, Lets learn OOPs in Python")
f.close()
self.log('INFO','Write operation successful.')
except Exception as e:
self.log('ERROR','Writing operation failed.')
def appendFile(self):
try:
f=open(self.filename+self.filetype,'a')
f.write("Appending some extra content")
f.close()
self.log('INFO','Append operation successful.')
except Exception as e:
self.log('ERROR','Append operation failed.')
sample = data('sample','.txt','1Mb','2021-08-20')
sample.appendFile()
sample.openFile()
Error:
ValueError: Unrecognised argument(s): force
During handling of the above exception, another exception occurred:
TypeError Traceback (most recent call last)
<ipython-input-1-e095529812fd> in log(self, logtype, msg)
12 logger.info(msg)
13 elif logtype == "ERROR":
---> 14 logger.ERROR(msg)
15 elif logtype == "CRITICAL":
16 logger.CRITICAL(msg)
TypeError: 'int' object is not callable
The force option to basicConfig is only available in Python 3.8+. Check your Python version.
Also, it's probably not a good idea to force reconfigure the logging system on each and every log event. Just configure it once, at the main entry point of your application.

why is it not catching index errror?

def dict_items(dict1):
try:
d2={}
for k in dict1.keys():
d2[k+1]=dict1[k]+k
dict1[k]=d2[k]
except IndexError:
print("IndexError")
except ValueError:
print("ValueError")
finally:
print("Finally done")
try:
dict_items({1:1,2:22,3:33})
print("function call done")
except:
print("Exception")
finally:
print("inside finally")
The above executes correctly and prints
Finally done
Exception
inside finally
why is not catching index error is there anything I am missing?
A bare except: isn't very useful. Catch the exception as print its type to see your error:
def dict_items(dict1):
try:
d2={}
for k in dict1.keys():
d2[k+1]=dict1[k]+k
dict1[k]=d2[k]
except IndexError:
print("IndexError")
except ValueError:
print("ValueError")
finally:
print("Finally done")
try:
dict_items({1:1,2:22,3:33})
print("function call done")
except Exception as e:
print("Exception",type(e))
finally:
print("inside finally")
Output:
Finally done
Exception <class 'KeyError'>
inside finally
Dictionaries throw KeyError not IndexError when the key doesn't exist, so the dict_items try block catches nothing and prints Finally done, then the outer try catches the KeyError in the bare except. Exception is the base class of most exceptions so you can see what type and value it has, then the final finally prints inside finally.

How do I catch only a specific error in Python3?

Say I have a function that can produce a vast variety of errors.
I have a ValueError that I need to catch, a specific AttributeError, and then I also need to handle any other type of error.
try:
func()
except AttributeError as e:
if "specific error text" in str(e):
print("The specific AttributeError occurred")
else:
raise
except ValueError:
print("A value error occurred")
except Exception as e:
print("Another error occurred: {}".format(str(e)))
Problem: If func() bubbles an AttributeError that's not the specific one I'm looking for, in this case, it'll be re-raised and not handled how I want it to be handled (via the general Exception handler).
How do I force non-specific errors to be handled further down in the chain, without duplicating code from the Exception section into the AttributeError section?
As an option you can process AttributeError and ValueError in one try-except block and all other Exceptions on the top level like
try:
try:
func()
except AttributeError as e:
if "specific error text" in str(e):
print("The specific AttributeError occurred")
else:
raise
except ValueError:
print("A value error occurred")
except Exception as e:
print("Another error occurred: {}".format(str(e)))
this may look a bit ugly though, so we can extract inner try-except block in a separate function like
def func_with_expected_exceptions_handling():
try:
func()
except AttributeError as e:
if "specific error text" in str(e):
print("The specific AttributeError occurred")
else:
raise
except ValueError:
print("A value error occurred")
and after that
try:
func_with_expected_exceptions_handling()
except Exception as e:
print("Another error occurred: {}".format(str(e)))
this doesn't save us from an actual nested structure, but it may come in handy if this kind of func processing arises in other places.
BTW, I don't think checking for a specific error message in exception is a good idea, we need a little bit more context to see if it can be done easier.
EDIT
If I understood correctly your func looks like
def func(...):
getattr(COMMANDS, cmd.command).command(irc_c, msg, cmd)
and you want to handle error from getattr call.
I can see next options here:
Wrap getattr call in try-except and process AttributeError in-place
def func(...):
try:
commander = getattr(COMMANDS, cmd.command)
except AttributeError:
print('Command {} not found'.format(cmd.command))
else:
commander.command(irc_c, msg, cmd)
Wrap getattr call in try-except, re-raise a custom exception (or ValueError) and process it afterwards in OP try-except
class CommandNotFound(Exception): pass
def func(...):
try:
commander = getattr(COMMANDS, cmd.command)
except AttributeError:
raise CommandNotFound() # or we can use `ValueError` instead
else:
commander.command(irc_c, msg, cmd)
Use default parameter of getattr function and make some kind of logging there like
class DefaultCommand:
def command(self, irc_c, msg, cmd):
print("Command {} is not found".format(cmd.command))
and after that used like
getattr(COMMANDS, cmd.command, DefaultCommand()).command(irc_c, msg, cmd)
Basically you need to handle the specific error first. From more general to more specific, i.e Exception => AttributeError => YourError
>>> try:
... raise MyCustomAttrErr("Hey, this failed!")
... except MyCustomAttrErr as e:
... print(e)
... except AttributteError as e:
... print("Attribute error raised")
... except Exception as e:
... print("Base exception raised")
...
Hey, this failed!
Python handled the except blocks in order from top to bottom and stops in the first block that captures it.

Python Exception isn't printing entire exception

I have a dictionary that looks like this:
js = {'value': {'boot': {'delay': 0} } }
When I run the following command
print(js['value']['boot']['delay'])
I get 0 which is the behavior I expect to get.
When I run the following
print(js['value']['booooot']['delay'])
I get KeyError: 'booooot' which is also expected.
However, when I wrap all of this into a try/except and print the exception, I don't get the error as I expected.
def myfunc(js):
try:
js['value']['booooot']['delay']
except Exception as e:
print(e)
I just get the following at the console 'booooot'
Shouldn't I get KeyError: 'booooot' as I did when I ran the print outside of a try/except block?
import sys
def func(js):
try:
print(js['value']['boooot']['delay'])
except:
print("Error Information: " + str(sys.exc_info()))
js = {'value': {'boot': {'delay': 0} } }
func(js)
The command sys.exc_info() will return the exception information as a tuple, with index 0 being the error type and index 1 with the error itself.
The code above prints out
Error Information: (<class 'KeyError'>, KeyError('boooot'), <traceback object at 0x01867558>)
Thanks for asking! Hope this helps.

Resources