JuliaPy / PythonCall.jl

Python and Julia in harmony.
https://juliapy.github.io/PythonCall.jl/stable/
MIT License
776 stars 63 forks source link

Preserve "caused by" stack trace from caught errors #430

Open PhilReinhold opened 10 months ago

PhilReinhold commented 10 months ago

Is your feature request related to a problem? Please describe.

I have julia code which catches errors and adds context. For example:

d = Dict()
try
  d["missing key"]
catch
  error("Extra context information")
end

When the above code gets executed in native julia, it shows the error stack trace and the "caused by" stack trace i.e.

ERROR: Extra context information
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:35
 [2] top-level scope
   @ REPL[2]:4

caused by: KeyError: key "missing key" not found
Stacktrace:
 [1] getindex(h::Dict{Any, Any}, key::String)
   @ Base ./dict.jl:484
 [2] top-level scope
   @ REPL[2]:2

However, when I run the same code in pythoncall , the "caused by" trace is not shown:

from juliacall import Main as jl
jl.seval("""
try
  Dict()["missing key"]
catch
  error("Extra context information")
end
""")
---------------------------------------------------------------------------
JuliaError                                Traceback (most recent call last)
Cell In[9], line 1
----> 1 jl.seval("""
      2 try
      3   Dict()["missing key"]
      4 catch
      5   error("Extra context information")
      6 end
      7 """)

File ~/.julia/packages/PythonCall/wXfah/src/jlwrap/module.jl:25, in seval(self, expr)
     23         return ValueBase.__dir__(self) + self._jl_callmethod($(pyjl_methodnum(pyjlmodule_dir)))
     24     def seval(self, expr):
---> 25         return self._jl_callmethod($(pyjl_methodnum(pyjlmodule_seval)), expr)
     26 """, @__FILE__(), "exec"), jl.__dict__)
     27 pycopy!(pyjlmoduletype, jl.ModuleValue)

JuliaError: Extra context information
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:35
 [2] top-level scope
   @ none:4
 [3] eval
   @ ./boot.jl:370 [inlined]
 [4] eval
   @ ./Base.jl:68 [inlined]
 [5] pyjlmodule_seval(self::Module, expr::Py)
   @ PythonCall ~/.julia/packages/PythonCall/wXfah/src/jlwrap/module.jl:13
 [6] _pyjl_callmethod(f::Any, self_::Ptr{PythonCall.C.PyObject}, args_::Ptr{PythonCall.C.PyObject}, nargs::Int64)
   @ PythonCall ~/.julia/packages/PythonCall/wXfah/src/jlwrap/base.jl:62
 [7] _pyjl_callmethod(o::Ptr{PythonCall.C.PyObject}, args::Ptr{PythonCall.C.PyObject})
   @ PythonCall.C ~/.julia/packages/PythonCall/wXfah/src/cpython/jlwrap.jl:47

Without this information, it is more difficult to tell what went wrong.

Describe the solution you'd like Ideally, we would have the original stack trace shown below, e.g.

JuliaError: Extra context information
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:35
 [2] top-level scope
   @ none:4
 [3] eval
   @ ./boot.jl:370 [inlined]
 [4] eval
   @ ./Base.jl:68 [inlined]
 [5] pyjlmodule_seval(self::Module, expr::Py)
   @ PythonCall ~/.julia/packages/PythonCall/wXfah/src/jlwrap/module.jl:13
 [6] _pyjl_callmethod(f::Any, self_::Ptr{PythonCall.C.PyObject}, args_::Ptr{PythonCall.C.PyObject}, nargs::Int64)
   @ PythonCall ~/.julia/packages/PythonCall/wXfah/src/jlwrap/base.jl:62
 [7] _pyjl_callmethod(o::Ptr{PythonCall.C.PyObject}, args::Ptr{PythonCall.C.PyObject})
   @ PythonCall.C ~/.julia/packages/PythonCall/wXfah/src/cpython/jlwrap.jl:47

caused by: KeyError: key "missing key" not found
Stacktrace:
 [1] getindex(h::Dict{Any, Any}, key::String)
   @ Base ./dict.jl:484
 [2] top-level scope
   @ REPL[2]:2
cjdoris commented 10 months ago

Yep good idea. I don't know where the "caused by" into comes from, do you?

PhilReinhold commented 10 months ago

It looks like it's coming from this line? https://github.com/JuliaLang/julia/blob/bac3ba5697c21cfc5058dfdb85a829e756ba9c68/base/errorshow.jl#L977C25-L977C25

It looks like you could get the exception stack via current_exceptions() https://docs.julialang.org/en/v1/manual/stacktraces/#Exception-stacks-and-[current_exceptions](@ref)