Skip to content

Commit 8ccb2a0

Browse files
authored
feat(py-err): attach Python exception backtrace when propagating to Rust (#363)
1 parent f530811 commit 8ccb2a0

File tree

3 files changed

+50
-20
lines changed

3 files changed

+50
-20
lines changed

src/ops/py_factory.rs

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use pythonize::pythonize;
1212
use crate::{
1313
base::{schema, value},
1414
builder::plan,
15-
py,
15+
py::{self, FromPyResult},
1616
};
1717
use anyhow::{anyhow, Result};
1818

@@ -74,14 +74,17 @@ impl PyFunctionExecutor {
7474
Some(kwargs)
7575
};
7676

77-
let result = self.py_function_executor.call(
78-
py,
79-
PyTuple::new(py, args.into_iter())?,
80-
kwargs
81-
.map(|kwargs| -> Result<_> { Ok(kwargs.into_py_dict(py)?) })
82-
.transpose()?
83-
.as_ref(),
84-
)?;
77+
let result = self
78+
.py_function_executor
79+
.call(
80+
py,
81+
PyTuple::new(py, args.into_iter())?,
82+
kwargs
83+
.map(|kwargs| -> Result<_> { Ok(kwargs.into_py_dict(py)?) })
84+
.transpose()?
85+
.as_ref(),
86+
)
87+
.from_py_result(py)?;
8588
Ok(result.into_bound(py))
8689
}
8790
}
@@ -99,8 +102,9 @@ impl SimpleFunctionExecutor for Arc<PyFunctionExecutor> {
99102
result_coro,
100103
)?)
101104
})?;
102-
let result = result_fut.await?;
105+
let result = result_fut.await;
103106
Python::with_gil(|py| -> Result<_> {
107+
let result = result.from_py_result(py)?;
104108
Ok(py::value_from_py_object(
105109
&self.result_type.typ,
106110
&result.into_bound(py),
@@ -156,11 +160,14 @@ impl SimpleFunctionFactory for PyFunctionFactory {
156160
.iter()
157161
.map(|(name, _)| PyString::new(py, name).unbind())
158162
.collect::<Vec<_>>();
159-
let result = self.py_function_factory.call(
160-
py,
161-
PyTuple::new(py, args.into_iter())?,
162-
Some(&kwargs.into_py_dict(py)?),
163-
)?;
163+
let result = self
164+
.py_function_factory
165+
.call(
166+
py,
167+
PyTuple::new(py, args.into_iter())?,
168+
Some(&kwargs.into_py_dict(py)?),
169+
)
170+
.from_py_result(py)?;
164171
let (result_type, executor) = result
165172
.extract::<(crate::py::Pythonized<schema::EnrichedValueType>, Py<PyAny>)>(py)?;
166173
Ok((
@@ -181,18 +188,22 @@ impl SimpleFunctionFactory for PyFunctionFactory {
181188
.clone();
182189
let (prepare_fut, enable_cache, behavior_version) =
183190
Python::with_gil(|py| -> anyhow::Result<_> {
184-
let prepare_coro = executor.call_method(py, "prepare", (), None)?;
191+
let prepare_coro = executor
192+
.call_method(py, "prepare", (), None)
193+
.from_py_result(py)?;
185194
let prepare_fut = pyo3_async_runtimes::into_future_with_locals(
186195
&pyo3_async_runtimes::TaskLocals::new(
187196
py_exec_ctx.event_loop.bind(py).clone(),
188197
),
189198
prepare_coro.into_bound(py),
190199
)?;
191200
let enable_cache = executor
192-
.call_method(py, "enable_cache", (), None)?
201+
.call_method(py, "enable_cache", (), None)
202+
.from_py_result(py)?
193203
.extract::<bool>(py)?;
194204
let behavior_version = executor
195-
.call_method(py, "behavior_version", (), None)?
205+
.call_method(py, "behavior_version", (), None)
206+
.from_py_result(py)?
196207
.extract::<Option<u32>>(py)?;
197208
Ok((prepare_fut, enable_cache, behavior_version))
198209
})?;

src/py/convert.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use bytes::Bytes;
12
use pyo3::types::{PyList, PyTuple};
23
use pyo3::IntoPyObjectExt;
34
use pyo3::{exceptions::PyException, prelude::*};
@@ -6,8 +7,7 @@ use serde::de::DeserializeOwned;
67
use serde::Serialize;
78
use std::collections::BTreeMap;
89
use std::ops::Deref;
9-
use std::sync::Arc;
10-
use bytes::Bytes;
10+
use std::sync::Arc;
1111

1212
use super::IntoPyResult;
1313
use crate::base::{schema, value};

src/py/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::setup;
1212
use pyo3::{exceptions::PyException, prelude::*};
1313
use pyo3_async_runtimes::tokio::future_into_py;
1414
use std::collections::btree_map;
15+
use std::fmt::Write;
1516

1617
mod convert;
1718
pub use convert::*;
@@ -26,6 +27,24 @@ impl PythonExecutionContext {
2627
}
2728
}
2829

30+
pub trait FromPyResult<T> {
31+
fn from_py_result(self, py: Python<'_>) -> anyhow::Result<T>;
32+
}
33+
34+
impl<T> FromPyResult<T> for Result<T, PyErr> {
35+
fn from_py_result(self, py: Python<'_>) -> anyhow::Result<T> {
36+
match self {
37+
Ok(value) => Ok(value),
38+
Err(err) => {
39+
let mut err_str = format!("Error calling Python function: {}", err);
40+
if let Some(tb) = err.traceback(py) {
41+
write!(&mut err_str, "\n{}", tb.format()?)?;
42+
}
43+
Err(anyhow::anyhow!(err_str))
44+
}
45+
}
46+
}
47+
}
2948
pub trait IntoPyResult<T> {
3049
fn into_py_result(self) -> PyResult<T>;
3150
}

0 commit comments

Comments
 (0)