Getting line number from the exception object - python

I have defined a custom Exception object and would like to get the line number of the exception.
class FlowException(Exception):
pass
def something():
print 2/3
print 1/2
print 2/0
try:
something()
except Exception as e:
raise FlowException("Process Exception", e)
Now, if there is a exception in something() it throws the FlowException but it does not give me the exact line number, How can I get the line number from FlowException(ie; it failed when it executed 2/0)?
Here is the output:--
raise FlowException("Process Exception", e)
__main__.FlowException: ('Process Exception', ZeroDivisionError('integer division or modulo by zero',))
[Finished in 0.4s with exit code 1]

The traceback object holds that info in the tb_lineno attribute:
import sys
# ...
except Exception as e:
trace_back = sys.exc_info()[2]
line = trace_back.tb_lineno
raise FlowException("Process Exception in line {}".format(line), e)

Tested on Python 3.6
class FlowException(Exception):
pass
def something():
raise ValueError
try:
something()
except Exception as e:
raise FlowException("Process Exception", e)
Output has line numbers:
Traceback (most recent call last):
File "/Users/diman/PycharmProjects/invites/test.py", line 8, in <module>
something()
File "/Users/diman/PycharmProjects/invites/test.py", line 5, in something
raise ValueError
ValueError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/diman/PycharmProjects/invites/test.py", line 10, in <module>
raise FlowException("Process Exception", e)
__main__.FlowException: ('Process Exception', ValueError())
For Python 2 try using internal logger ".exception()" method instead of using monstrous "sys" module.
import logging
logger = logging.getLogger()
try:
something()
except Exception as e:
logger.exception(e)
raise FlowException("Process Exception", e)

Related

Is there a way to access the "another exceptions" that happen in a python3 traceback?

Let's assume you have some simple code that you don't control (eg: it's in a module you're using):
def example():
try:
raise TypeError("type")
except TypeError:
raise Exception("device busy")
How would I go about accessing the TypeError in this traceback in order to handle it?
Traceback (most recent call last):
File "/usr/local/google/home/dthor/dev/pyle/pyle/fab/visa_instrument/exception_helpers.py", line 3, in example
raise TypeError("type")
TypeError: type
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/google/home/dthor/dev/pyle/pyle/fab/visa_instrument/exception_helpers.py", line 7, in <module>
example()
File "/usr/local/google/home/dthor/dev/pyle/pyle/fab/visa_instrument/exception_helpers.py", line 5, in example
raise Exception("device busy")
Exception: device busy
I can do the below, but i'm not happy with it because I'm doing string comparison - meaning things would break if the underlying module changes what string they raise (I don't control example()):
try:
example()
except Exception as err:
if "device busy" in str(err):
# do the thing
pass
# But raise any other exceptions
raise err
I'd much rather have:
try:
example()
except Exception as err:
if TypeError in err.other_errors: # magic that doesn't exist
# do the thing
pass
raise err
or even
try:
example()
except TypeError in exception_stack: # Magic that doesn't exist
# do the thing
pass
except Exception:
I'm investigating the traceback module and sys.exc_info(), but don't have anything concrete yet.
Followup: would things be different if the exception was chained? Eg: raise Exception from the_TypeError_exception
Check the __context__ of the exception:
>>> try:
... example()
... except Exception as e:
... print(f"exception: {e!r}")
... print(f"context: {e.__context__!r}")
...
exception: Exception('device busy')
context: TypeError('type')
If you use a chained exception, the original exception will also be accessible via the __cause__ attribute.

Avoiding "During handling of the above exception, another exception occurred" when catching and immediately raising exceptions

my_dictionary = {'a':1}
try:
my_dictionary['b']
except KeyError as e:
raise KeyError('Bad key:' + str(e))
That code, obviously, will raise a KeyError:
Traceback (most recent call last):
File "----.py", line 11, in <module>
my_dictionary['b']
KeyError: 'b'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "----.py", line 13, in <module>
raise KeyError('Bad key:' + str(e))
KeyError: "Bad key:'b'"
While I understand the need for Python to state how the except part created its own error, I'd like for that first KeyError not to be shown. A workaround I came up with is this:
my_dictionary = {'a':1}
err_msg = None
try:
my_dictionary['b']
except KeyError as e:
err_msg = str(e)
if type(err_msg) != type(None):
raise KeyError('Bad key:' + err_msg)
which shortens the error message to this:
Traceback (most recent call last):
File "----.py", line 23, in <module>
raise KeyError('Bad key:' + err_msg)
KeyError: "Bad key:'b'"
Is there a more Pythonic way of doing this?
From this answer, you need to add from None to your exception.
my_dictionary = {'a':1}
try:
my_dictionary['b']
except KeyError as e:
raise KeyError('Bad key:' + str(e)) from None
If you have a specific thing that you want to print instead of showing the stack trace, you can do that by just using print and then sys.exit(1), which is probably as pythonic as you can get. Another thing you can do is to raise KeyError('Bad key:' + str(e)) from e, which replaces "During handling of the above exception, another exception occurred:" with "The above exception was the direct cause of the following exception:". I don't think there's a way to do exactly what you asked, because the whole reason that error messages are generated like that is because you should be given all the information unless you explicitly specify some other behaviour. Uncaught exceptions generally represent some mistake in your code - they aren't supposed to happen normally, so there wouldn't be a need to make the formatting of the error messages too customizable.

How can I swallow python exception messages?

I have some code thus:
try:
subprocess.check_call(
"mysqldump {} {}".format(mysql_args, cmd), shell=True
)
except Exception as e:
raise Exception("Command failed")
The problem is that Exception logging code elsewhere in the application catches this exception and helpfully prints it out - which in this case looks like this:
Traceback (most recent call last):
File "/apps/django/myapp/tasks.py", line 336, in _safe_mysqldump
"mysqldump {} {}".format(mysql_args, cmd), shell=True
File "/usr/lib/python3.6/subprocess.py", line 291, in check_call
raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError:
Command 'mysqldump -ufoo -pMYSECRETPASSWORD myappdb t1 t2 t3...' returned non-zero exit status 6.
During handling of the above exception, another exception occurred:
etc.
The critical thing being it printed out the mysql connection string. How can I prevent it from doing this?
Use the syntax:
raise Exception("Command failed") from None
See PEP 409 Suppressing exception context):
>>> try:
... raise TypeError('a')
... except TypeError:
... raise ValueError('b') from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError: b
Versus the default behaviour which you saw:
>>> try:
... raise TypeError('a')
... except TypeError:
... raise ValueError('b')
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: a
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError: b
As an alternative specific to your use case: do not use check_call which raises an exception but use subprocess.run
instead:
if subprocess.run(<command>).returncode != 0:
raise Exception("Command failed")
or subprocess.call
if subprocess.call(<command>) != 0:
raise Exception("Command failed")
You may change the output like this:
failed = False
try:
...
except Exception:
failed = True
finally:
if failed:
raise Exception("Command failed")

Python: How to catch inner exception of exception chain?

Consider the simple example:
def f():
try:
raise TypeError
except TypeError:
raise ValueError
f()
I want to catch TypeError object when ValueError is thrown after f() execution. Is it possible to do it?
If I execute function f() then python3 print to stderr all raised exceptions of exception chain (PEP-3134) like
Traceback (most recent call last):
File "...", line 6, in f
raise TypeError
TypeError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "...", line 11, in <module>
f()
File "...", line 8, in f
raise ValueError
ValueError
So I would get the list of all exceptions of exception chain or check if exception of some type (TypeError in the above example) exists in exception chain.
Python 3 has a beautiful syntactic enhancement on exceptions handling. Instead of plainly raising ValueError, you should raise it from a caught exception, i.e.:
try:
raise TypeError('Something awful has happened')
except TypeError as e:
raise ValueError('There was a bad value') from e
Notice the difference between the tracebacks. This one uses raise from version:
Traceback (most recent call last):
File "/home/user/tmp.py", line 2, in <module>
raise TypeError('Something awful has happened')
TypeError: Something awful has happened
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/user/tmp.py", line 4, in <module>
raise ValueError('There was a bad value') from e
ValueError: There was a bad value
Though the result may seem similar, in fact it is rather different! raise from saves the context of the original exception and allows one to trace all the exceptions chain back - which is impossible with simple raise.
To get the original exception, you simply have to refer to new exception's __context__ attribute, i.e.
try:
try:
raise TypeError('Something awful has happened')
except TypeError as e:
raise ValueError('There was a bad value') from e
except ValueError as e:
print(e.__context__)
>>> Something awful has happened
Hopefully that is the solution you were looking for.
For more details, see PEP 3134 -- Exception Chaining and Embedded Tracebacks

What does raise in Python raise?

Consider the following code:
try:
raise Exception("a")
except:
try:
raise Exception("b")
finally:
raise
This will raise Exception: a. I expected it to raise Exception: b (need I explain why?). Why does the final raise raise the original exception rather than (what I thought) was the last exception raised?
Raise is re-raising the last exception you caught, not the last exception you raised
(reposted from comments for clarity)
On python2.6
I guess, you are expecting the finally block to be tied with the "try" block where you raise the exception "B". The finally block is attached to the first "try" block.
If you added an except block in the inner try block, then the finally block will raise exception B.
try:
raise Exception("a")
except:
try:
raise Exception("b")
except:
pass
finally:
raise
Output:
Traceback (most recent call last):
File "test.py", line 5, in <module>
raise Exception("b")
Exception: b
Another variation that explains whats happening here
try:
raise Exception("a")
except:
try:
raise Exception("b")
except:
raise
Output:
Traceback (most recent call last):
File "test.py", line 7, in <module>
raise Exception("b")
Exception: b
If you see here, replacing the finally block with except does raise the exception B.

Categories