use pyo3::types::PyTypeMethods;
use pyo3::{PyErr, Python};
use std::fmt;
pub struct PythonErrorFormatter {
error: PyErr,
}
impl PythonErrorFormatter {
pub fn new(error: PyErr) -> Self {
Self { error }
}
pub fn format(&self) -> String {
let mut output = String::new();
let error_message = Python::attach(|py| {
let type_obj = self.error.get_type(py);
let type_name = type_obj
.name()
.map(|n| n.to_string())
.unwrap_or_else(|_| "Unknown".to_string());
let value = self.error.value(py).to_string();
format!("\nError: {}\n{}", type_name, value)
});
output.push_str(&error_message);
output.push('\n');
Python::attach(|py| {
if let Some(traceback) = self.error.traceback(py) {
output.push_str("\nTraceback:\n");
let tb_str = format!(" {}", traceback);
for line in tb_str.lines() {
output.push_str(&format!(" {}\n", line));
}
}
});
output
}
}
impl fmt::Display for PythonErrorFormatter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.format())
}
}
#[cfg(test)]
mod tests {
use super::*;
use pyo3::prelude::*;
#[test]
fn test_error_formatter() {
Python::attach(|py| {
let result: PyResult<()> =
py.run(c"raise ValueError('Test error message')", None, None);
let error = result.unwrap_err();
let formatter = PythonErrorFormatter::new(error);
let output = formatter.format();
assert!(output.contains("Error: ValueError"));
assert!(output.contains("Test error message"));
});
}
}