Open jsnps opened 3 years ago
There is currently no way to get the python exception from a JepException. I have considered adding the python exception to the JepException as a PyObject but it never seemed like that would be particularly useful and it could cause some confusion for cases where the JepException is thrown outside the scope of the Interpreter which created the PyObject. If you have a need for that info though it is something I would consider merging if you can make a pull request.
You may be able to create your own java Exception class that contains a PyObject. I don't think you could throw that exception directly form python but you could have a java function throw it. Your exception containing the PyObject would then be the cause of the JepException. See the code below for an example. The traceback wasn't working but the idea of storing the exception as a PyObject seems workable. You may need a separate field in the PythonExceptionWrapper for the traceback to make sure it gets across. You could store any state information you want in the PythonExceptionWrapper for use later in python.
class Test {
public static class PythonExceptionWrapper extends RuntimeException {
public final PyObject cause;
public PythonExceptionWrapper(PyObject cause) {
this.cause = cause;
}
}
public static void throwPython(PyObject object) {
throw new PythonExceptionWrapper(object);
}
public static void main(String[] args) {
try (Jep jep = new SharedInterpreter()) {
try {
jep.set("Test", Test.class);
jep.eval("try:\n" +
" exec(open(\"faultyScript.py\").read())\n" +
"except Exception as e:\n" +
" Test.throwPython(e)");
} catch (JepException e) {
PyObject pyExc = ((PythonExceptionWrapper) e.getCause()).cause;
jep.set("ex", pyExc);
jep.eval("import traceback");
jep.eval("traceback.print_exception(ex, ex.args, ex.__traceback__)");
}
}
}
}
Ok, I see. Isn't it also that the stored PyObject would become invalid / unusable as soon as it leaves the interpreter scope (i.e. the exception leaves the try with resource block)? In this case, I agree that it would be a cumbersome feature. I will think about it again and check if it is really needed.
With regards to exceptions I have another two small questions and I don't want to flood you with too many new issues ;)
public static class TypedJepException extends JepException {
private static final long serialVersionUID = -4282626424199367439L;
public TypedJepException(String message, long type) {
super(message, type);
}
}
public static void throwPythonException(String message, long type) throws JepException {
throw new TypedJepException(message, type);
}
@Test
public void testThrowFromJava() throws JepException {
try (Jep jep = new SharedInterpreter()) {
try {
jep.set("test", RawJepInterpreterTest.class);
jep.eval("test.throwPythonException('some name error', id(NameError))");
} catch (JepException e) {
assertEquals("<class 'NameError'>: some name error", e.getMessage());
}
}
}
Also imaginable could be some factory methods (this is how jython did it):
public static PyException TypeError(String msg) { return new PyException(Py.TypeError, msg); }
public static PyException AttributeError(String msg) { return new PyException(Py.AttributeError, msg); }
public static PyException AssertionError(String msg) { return new PyException(Py.AssertionError, msg); }
public static PyException IndexError(String msg) { return new PyException(Py.IndexError, msg); }
public static PyException RuntimeError(String msg) { return new PyException(Py.RuntimeError, msg); }
public static PyException ValueError(String msg) { return new PyException(Py.ValueError, msg); }
[...]
//and then
throw Py.ValueError("Sorry this is an invalid value :(")
Same for checking python exception on the java side - e.g.:
jepException.matches(PyExceptionType.ValueError)
I ended up doing something like this:
/* package */ static synchronized void initializeExceptionTypes(Jep jep) throws JepException {
if (exceptionTypes == null) {
Set<PyExceptionType> ignore = Sets.newHashSet();
ignore.add(PyExceptionType.StandardError);
Map<Long, PyExceptionType> types = Maps.newHashMap();
for (PyExceptionType type : PyExceptionType.values()) {
if (ignore.contains(type)) {
continue;
}
types.put(jep.getValue(String.format("id(%s)", type.name()), Long.class), type);
}
exceptionTypes = types;
}
}
Once initialized, it's quite convenient to map in both directions (the ignore set here is for the StandardError, which has been removed from Python 3 - still need that type for the Jython (2.7) backend) In this case the long pointer is actually better for me, because it is thread independant (and today PyObjects are not).
For now I just hardcoded it in C, but once I made it configurable, I will create a pull request.
Hey,
Is it possible to pass some caught JepException back into python to handle the original python exception in python code? I've read that Jep is able to throw back some JepException into python, I guess this is the case for reentrant calls (e.g. java -> python -> java -> python). Background is, that I want to print the python stack trace when runnning a script and something goes wrong. So something like this:
The issue here is that the exception is a PyObject, a simple python wrapper around the java exception object.
So far I only see some alternative like this:
Although, for this approach I would need to massage the stacktrace a bit. Any other ideas?
sys.last_value only seems to be set in an interactive interpreter, sys.exc_info() also doesn't seem to be set, which is kind of excepted, because the exception is caught on the java side.
Second alternative, add a showException flag to runScript and do this in C?