Skip to content

Commit 38ac1b3

Browse files
authored
Merge pull request #6 from rjaduthie/add_traceback
Add clear backtrace on PyO3 error during main.py initialisation
2 parents a215502 + f074493 commit 38ac1b3

File tree

2 files changed

+91
-5
lines changed

2 files changed

+91
-5
lines changed

src/error.rs

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// git clone https://github.com/marcomq/tauri-plugin-python
55

66
#[cfg(feature = "pyo3")]
7-
use pyo3::PyErr;
7+
use pyo3::{prelude::*, PyErr};
88
use serde::{ser::Serializer, Serialize};
99

1010
pub type Result<T> = std::result::Result<T, Error>;
@@ -63,9 +63,95 @@ impl From<rustpython_vm::PyRef<rustpython_vm::builtins::PyBaseException>> for Er
6363
#[cfg(feature = "pyo3")]
6464
impl From<PyErr> for Error {
6565
fn from(error: PyErr) -> Self {
66-
let msg = error.to_string();
67-
println!("error: {}", &msg);
68-
Error::String(msg)
66+
let error_msg = match pyo3::Python::with_gil(|py| -> Result<Vec<String>> {
67+
let traceback_module = py.import("traceback")?;
68+
let traceback_object = error
69+
.traceback(py)
70+
.ok_or(pyo3::exceptions::PyWarning::new_err("No traceback found."))?;
71+
let extract_traceback = traceback_module.getattr("extract_tb")?;
72+
73+
// Get the formatted traceback lines
74+
let result = extract_traceback.call1((traceback_object,)).and_then(|r| {
75+
match r.extract::<Vec<PyObject>>() {
76+
Ok(v) => {
77+
let mut formatted_lines = Vec::new();
78+
for arg in v.iter() {
79+
let frame = arg.bind(py);
80+
81+
// Extract filename
82+
let filename = match frame.getattr("filename") {
83+
Ok(f) => match f.extract::<String>() {
84+
Ok(s) if s == "<string>".to_string() => {
85+
// Special handling for <string>
86+
frame.setattr("filename", "main.py")?;
87+
let lineno = frame.getattr("lineno")?.extract::<usize>()?;
88+
frame.setattr("lineno", lineno - 2)?;
89+
"main.py".to_string()
90+
}
91+
Ok(s) => s,
92+
Err(_) => "<unknown>".to_string(),
93+
},
94+
Err(_) => "<unknown>".to_string(),
95+
};
96+
97+
// Extract line number
98+
let lineno = match frame.getattr("lineno") {
99+
Ok(l) => match l.extract::<usize>() {
100+
Ok(n) => n,
101+
Err(_) => 0,
102+
},
103+
Err(_) => 0,
104+
};
105+
106+
// Extract function name
107+
let name = match frame.getattr("name") {
108+
Ok(n) => match n.extract::<String>() {
109+
Ok(s) => s,
110+
Err(_) => "<unknown>".to_string(),
111+
},
112+
Err(_) => "<unknown>".to_string(),
113+
};
114+
115+
// Extract line content (if available)
116+
let line = match frame.getattr("line") {
117+
Ok(l) => match l.extract::<Option<String>>() {
118+
Ok(Some(s)) => format!("\t{}", s),
119+
_ => "".to_string(),
120+
},
121+
Err(_) => "".to_string(),
122+
};
123+
124+
// Format the line like requested
125+
let formatted_line = format!(
126+
"File \"{}\", line {}, in {}\n{}",
127+
filename, lineno, name, line
128+
);
129+
130+
formatted_lines.push(formatted_line);
131+
}
132+
133+
Ok(formatted_lines)
134+
}
135+
Err(_) => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
136+
"Failed to extract traceback",
137+
)),
138+
}
139+
})?;
140+
141+
// Add traceback header
142+
let mut full_traceback = vec!["Traceback (most recent call last):".to_string()];
143+
full_traceback.extend(result);
144+
145+
// Add error type and message
146+
full_traceback.push(error.to_string());
147+
148+
Ok(full_traceback)
149+
}) {
150+
Ok(formatted) => formatted.join("\n"),
151+
Err(_) => error.to_string(), // Fall back to simple error message
152+
};
153+
154+
Error::String(error_msg)
69155
}
70156
}
71157

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ sys.path = sys.path + [{}]
107107
code
108108
);
109109
py_lib::run_python_internal(path_import, "main.py".into())
110-
.unwrap_or_else(|e| panic!("Error '{e}' initializing main.py"));
110+
.unwrap_or_else(|e| panic!("Error initializing main.py:\n\n{e}\n"));
111111
}
112112

113113
/// Initializes the plugin.

0 commit comments

Comments
 (0)