In the module warnings (https://docs.python.org/3.5/library/warnings.html) there is the ability to raise a warning that appears to come from somewhere earlier in the stack:
warnings.warn('This is a test', stacklevel=2)
Is there an equivalent for raising errors? I know I can raise an error with an alternative traceback, but I can't create that traceback within the module since it needs to come from earlier. I imagine something like:
tb = magic_create_traceback_right_here()
raise ValueError('This is a test').with_traceback(tb.tb_next)
The reason is that I am developing a module that has a function module.check_raise
that I want to raise an error that appears to originate from where the function is called. If I raise an error within the module.check_raise
function, it appears to originate from within module.check_raise
, which is undesired.
Also, I've tried tricks like raising a dummy exception, catching it, and passing the traceback along, but somehow the tb_next becomes None
. I'm out of ideas.
Edit:
I would like the output of this minimal example (called tb2.py):
import check_raise
check_raise.raise_if_string_is_true('True')
to be only this:
Traceback (most recent call last):
File "tb2.py", line 10, in <module>
check_raise.raise_if_string_is_true(string)
RuntimeError: An exception was raised.
I can't believe I am posting this
By doing this you are going against the zen.
But if you insist here is your magical code.
check_raise.py
This is pure python, does not interfere with
sys.excepthook
and even in a try block it is not caught withexcept Exception:
although it is caught withexcept:
test.py
will give you the (horribly uninformative and extremely forged) traceback message you desire.
EDIT: The previous version did not provide quotes or explanations.
I suggest referring to PEP 3134 which states in the Motivation:
When an
Exception
is raised with a__cause__
attribute the traceback message takes the form of:To my understanding this is exactly what you are trying to accomplish; clearly indicate that the reason for the error is not your module but somewhere else. If you are instead trying to omit information to the traceback like your edit suggests then the rest of this answer won't do you any good.
Just a note on syntax:
so the bare minimum example would be something like this:
which gives an error message like this:
However the first line of the cause is the statement in the
try
block ofcheck_raise
:so before raising
err
it may (or may not) be desirable to remove the outer most traceback frame fromoriginal_error
:This way the only line in the traceback that appears to come from
check_raise
is the very lastraise
statement which cannot be omitted with pure python code although depending on how informative the message is you can make it very clear that your module was not the cause of the problem:The advantage to raising exception like this is that the original Traceback message is not lost when the new error is raised, which means that a very complex series of exceptions can be raised and python will still display all the relevant information correctly:
gives me the following error message:
Yes it is long but it is significantly more informative then:
not to mention that the original error is still usable even if the program doesn't end:
If I understand correctly, you would like the output of this minimal example:
to be only this:
In fact, it's a lot more output; there is exception chaining, which could be dealt with by handling the
RuntimeError
immediately, removing its__context__
, and re-raising it, and there is another line of traceback for theRuntimeError
itself:As far as I can tell, it is not possible for pure Python code to substitute the traceback of an exception after it was raised; the interpreter has control of adding to it but it only exposes the current traceback whenever the exception is handled. There is no API (not even when using tracing functions) for passing your own traceback to the interpreter, and traceback objects are immutable (this is what's tackled by that Jinja hack involving C-level stuff).
So further assuming that you're interested in the shortened traceback not for further programmatic use but only for user-friendly output, your best bet will be an
excepthook
that controls how the traceback is printed to the console. For determining where to stop printing, a special local variable could be used (this is a bit more robust than limiting the traceback to its length minus 1 or such). This example requires Python 3.5 (fortraceback.walk_tb
):This is the output now: