Skip to content

Commit 734094c

Browse files
authored
feat(native): Jinja - print tracebacks for Python errors (#7435)
1 parent e236079 commit 734094c

File tree

15 files changed

+143
-26
lines changed

15 files changed

+143
-26
lines changed

packages/cubejs-backend-native/src/python/entry.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::cross::*;
22
use crate::python::cube_config::CubeConfigPy;
3+
use crate::python::neon_py::*;
34
use crate::python::python_model::CubePythonModel;
45
use crate::python::runtime::py_runtime_init;
56
use neon::prelude::*;
@@ -47,7 +48,7 @@ fn python_load_config(mut cx: FunctionContext) -> JsResult<JsPromise> {
4748

4849
deferred.settle_with(&channel, move |mut cx| match conf_res {
4950
Ok(c) => c.to_object(&mut cx),
50-
Err(err) => cx.throw_error(format!("Python error: {}", err)),
51+
Err(py_err) => cx.throw_from_python_error(py_err),
5152
});
5253

5354
Ok(promise)
@@ -154,7 +155,7 @@ fn python_load_model(mut cx: FunctionContext) -> JsResult<JsPromise> {
154155

155156
deferred.settle_with(&channel, move |mut cx| match conf_res {
156157
Ok(c) => c.to_object(&mut cx),
157-
Err(err) => cx.throw_error(format!("Python error: {}", err)),
158+
Err(py_err) => cx.throw_from_python_error(py_err),
158159
});
159160

160161
Ok(promise)

packages/cubejs-backend-native/src/python/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub(crate) mod cube_config;
22
mod entry;
33
#[cfg(target_os = "linux")]
44
pub(crate) mod linux_dylib;
5+
pub mod neon_py;
56
pub(crate) mod python_model;
67
pub(crate) mod runtime;
78
pub mod utils;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use neon::prelude::*;
2+
use pyo3::prelude::*;
3+
4+
pub(crate) fn format_python_error(py_err: PyErr) -> String {
5+
let err = format!("Python error: {}", py_err);
6+
7+
let bt = Python::with_gil(move |py| -> PyResult<Option<String>> {
8+
if let Some(trace_back) = py_err.traceback(py) {
9+
Ok(Some(trace_back.format()?))
10+
} else {
11+
Ok(None)
12+
}
13+
});
14+
15+
match bt {
16+
Ok(Some(trace_back)) => format!("{}\r\n{}", err, trace_back),
17+
Err(bt_err) => {
18+
log::trace!("Unable to extract backtrace with error: {}", bt_err);
19+
20+
err
21+
}
22+
_ => err,
23+
}
24+
}
25+
26+
pub(crate) trait NeonPythonContext<'a>: Context<'a> {
27+
fn throw_from_python_error<T>(&mut self, py_err: PyErr) -> NeonResult<T> {
28+
self.throw_error(format_python_error(py_err))
29+
}
30+
}
31+
32+
impl<'a> NeonPythonContext<'a> for FunctionContext<'a> {}
33+
34+
impl<'a> NeonPythonContext<'a> for TaskContext<'a> {}

packages/cubejs-backend-native/src/python/runtime.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::cross::{CLRepr, CLReprPython};
2+
use crate::python::neon_py::*;
23
use crate::tokio_runtime_node;
34
use cubesql::CubeError;
45
use log::{error, trace};
@@ -122,13 +123,13 @@ impl PyRuntime {
122123
deferred.settle_with(
123124
js_channel,
124125
move |mut cx| -> NeonResult<Handle<JsError>> {
125-
cx.throw_error(format!("Python error: {}", err))
126+
cx.throw_from_python_error(err)
126127
},
127128
);
128129
}
129130
PyScheduledCallback::Channel(chan) => {
130131
let send_res =
131-
chan.send(Err(CubeError::internal(format!("Python error: {}", err))));
132+
chan.send(Err(CubeError::internal(format_python_error(err))));
132133
if send_res.is_err() {
133134
return Err(CubeError::internal(
134135
"Unable to send result back to consumer".to_string(),
@@ -167,10 +168,9 @@ impl PyRuntime {
167168
PyScheduledCallback::Channel(chan) => {
168169
let _ = match res {
169170
Ok(r) => chan.send(Ok(r)),
170-
Err(err) => chan.send(Err(CubeError::internal(format!(
171-
"Python error: {}",
172-
err
173-
)))),
171+
Err(err) => {
172+
chan.send(Err(CubeError::internal(format_python_error(err))))
173+
}
174174
};
175175
}
176176
}

packages/cubejs-backend-native/src/template/entry.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::cross::*;
22
use crate::template::mj_value::*;
3-
use crate::template::neon::*;
3+
use crate::template::neon_mj::*;
44
use crate::utils::bind_method;
55

66
use log::trace;

packages/cubejs-backend-native/src/template/mj_value/python.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::cross::*;
2+
use crate::python::neon_py::format_python_error;
23
use crate::python::runtime::py_runtime;
34
use crate::python::{python_obj_call_sync, python_obj_method_call_sync};
45
use crate::template::mj_value::to_minijinja_value;
@@ -135,7 +136,7 @@ impl Object for JinjaPythonObject {
135136
Ok(r) => Ok(to_minijinja_value(r)),
136137
Err(err) => Err(mj::Error::new(
137138
minijinja::ErrorKind::InvalidOperation,
138-
format!("Error while calling method: {}", err),
139+
format!("Error while calling method: {}", format_python_error(err)),
139140
)),
140141
}
141142
}
@@ -153,7 +154,7 @@ impl Object for JinjaPythonObject {
153154
Ok(r) => Ok(to_minijinja_value(r)),
154155
Err(err) => Err(mj::Error::new(
155156
minijinja::ErrorKind::InvalidOperation,
156-
format!("Error while calling method: {}", err),
157+
format!("Error while calling method: {}", format_python_error(err)),
157158
)),
158159
}
159160
}
@@ -242,12 +243,22 @@ impl Object for JinjaPythonFunction {
242243
arguments.push(from_minijinja_value(arg)?);
243244
}
244245

245-
let py_runtime = py_runtime()
246-
.map_err(|err| mj::Error::new(mj::ErrorKind::EvalBlock, format!("Error: {}", err)))?;
246+
let py_runtime = py_runtime().map_err(|err| {
247+
mj::Error::new(
248+
mj::ErrorKind::EvalBlock,
249+
format!("Python runtime error: {}", err),
250+
)
251+
})?;
252+
247253
let call_future = py_runtime.call_async(self.inner.clone(), arguments);
248254

249-
let tokio = tokio_runtime()
250-
.map_err(|err| mj::Error::new(mj::ErrorKind::EvalBlock, format!("Error: {}", err)))?;
255+
let tokio = tokio_runtime().map_err(|err| {
256+
mj::Error::new(
257+
mj::ErrorKind::EvalBlock,
258+
format!("Tokio runtime error: {}", err),
259+
)
260+
})?;
261+
251262
match tokio.block_on(call_future) {
252263
Ok(r) => Ok(to_minijinja_value(r)),
253264
Err(err) => Err(mj::Error::new(

packages/cubejs-backend-native/src/template/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
mod engine_python;
33
mod entry;
44
mod mj_value;
5-
mod neon;
5+
mod neon_mj;
66
mod workers;
77

88
pub use entry::template_register_module;

packages/cubejs-backend-native/src/template/neon.rs renamed to packages/cubejs-backend-native/src/template/neon_mj.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,32 @@ pub(crate) trait NeonMiniJinjaContext<'a>: Context<'a> {
3737
content += &format!("{:>4} | {}\r\n", idx + 1, line);
3838
}
3939

40-
format!("{}\r\n{}\r\n{}", "-".repeat(79), content, "-".repeat(79))
40+
let header = if let Some(ref filename) = err.name() {
41+
format!("{:-^79}", format!(" {} ", filename))
42+
} else {
43+
"-".repeat(79)
44+
};
45+
46+
format!("\r\n{}\r\n{}{}", header, content, "-".repeat(79))
47+
} else if let Some(ref filename) = err.name() {
48+
format!(" in {}:{}", filename, err.line().unwrap_or(0))
4149
} else {
4250
"".to_string()
4351
};
4452

53+
let formatted_err = if let Some(ref detail) = err.detail() {
54+
format!("{}: {}", err.kind(), detail)
55+
} else {
56+
format!("{}", err.kind())
57+
};
58+
4559
if let Some(next_err) = err.source() {
4660
self.throw_error(format!(
47-
"{} caused by: {:#}\r\n{}",
48-
err, next_err, codeblock
61+
"{} caused by: {:#}{}",
62+
formatted_err, next_err, codeblock
4963
))
5064
} else {
51-
self.throw_error(format!("{}\r\n{}", err, codeblock))
65+
self.throw_error(format!("{}{}", formatted_err, codeblock))
5266
}
5367
}
5468
}

packages/cubejs-backend-native/src/template/workers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::template::neon::*;
1+
use crate::template::neon_mj::*;
22
use cubesql::CubeError;
33

44
use log::trace;

packages/cubejs-backend-native/test/__snapshots__/jinja.test.ts.snap

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,26 @@ dump:
246246
dict_as_obj: \\"{\\\\n \\\\\\"a_attr\\\\\\": String(\\\\n \\\\\\"value for attribute a\\\\\\",\\\\n Normal,\\\\n ),\\\\n}\\""
247247
`;
248248

249-
exports[`Jinja (new api) render template_error.jinja: template_error.jinja 1`] = `
250-
[Error: syntax error: unknown statement unexpected_block_name (in template_error.jinja:10)
251-
-------------------------------------------------------------------------------
249+
exports[`Jinja (new api) render template_error_python.jinja: template_error_python.jinja 1`] = `
250+
[Error: could not render block: Call error: Internal: Python error: Exception: Random Exception
251+
Traceback (most recent call last):
252+
File "jinja-instance.py", line 110, in throw_exception
253+
254+
------------------------- template_error_python.jinja -------------------------
255+
3 | 3
256+
4 | 4
257+
5 | 5
258+
6 > {%- set variable = throw_exception() %}
259+
i ^^^^^^^^^^^^^^^^^^^^^^^^ could not render block
260+
7 | 7
261+
8 | 8
262+
9 | 9
263+
-------------------------------------------------------------------------------]
264+
`;
265+
266+
exports[`Jinja (new api) render template_error_syntax.jinja: template_error_syntax.jinja 1`] = `
267+
[Error: syntax error: unknown statement unexpected_block_name
268+
------------------------- template_error_syntax.jinja -------------------------
252269
7 | 7
253270
8 | {%- for country in countries %}
254271
9 | 9
@@ -257,7 +274,6 @@ exports[`Jinja (new api) render template_error.jinja: template_error.jinja 1`] =
257274
11 | 11
258275
12 | 12
259276
13 | 13
260-
261277
-------------------------------------------------------------------------------]
262278
`;
263279

0 commit comments

Comments
 (0)