Skip to content

Commit 202af3b

Browse files
committed
Improve panics.
1 parent 5e0199a commit 202af3b

File tree

3 files changed

+60
-33
lines changed

3 files changed

+60
-33
lines changed

macros/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,13 @@ fn python_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
4646
]),
4747
punct('.'), ident("expect"), parens([string("python")]),
4848
punct(';'),
49-
]))
49+
])),
50+
punct(','),
51+
punct('|'), ident("e"), punct('|'),
52+
punct(':'), punct(':'), ident("std"),
53+
punct(':'), punct(':'), ident("panic"),
54+
punct(':'), punct(':'), ident("panic_any"),
55+
parens([ident("e")]),
5056
]),
5157
]))
5258
}

src/context.rs

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,16 @@ impl Context {
4747
///
4848
/// This function panics if it fails to create the context.
4949
#[allow(clippy::new_without_default)]
50+
#[track_caller]
5051
pub fn new() -> Self {
5152
Python::with_gil(Self::new_with_gil)
5253
}
5354

55+
#[track_caller]
5456
pub(crate) fn new_with_gil(py: Python) -> Self {
5557
match Self::try_new(py) {
5658
Ok(x) => x,
57-
Err(error) => {
58-
error.print(py);
59-
panic!("failed to create Python context");
60-
}
59+
Err(err) => panic!("{}", panic_string(py, &err)),
6160
}
6261
}
6362

@@ -80,10 +79,7 @@ impl Context {
8079
Err(_) | Ok(None) => panic!("Python context does not contain a variable named `{}`", name),
8180
Ok(Some(value)) => match FromPyObject::extract_bound(&value) {
8281
Ok(value) => value,
83-
Err(e) => {
84-
e.print(py);
85-
panic!("Unable to convert `{}` to `{}`", name, std::any::type_name::<T>());
86-
}
82+
Err(e) => panic!("Unable to convert `{}` to `{}`: {e}", name, std::any::type_name::<T>()),
8783
},
8884
})
8985
}
@@ -92,11 +88,9 @@ impl Context {
9288
///
9389
/// This function panics if the conversion fails.
9490
pub fn set<T: for<'p> IntoPyObject<'p>>(&self, name: &str, value: T) {
95-
Python::with_gil(|py| match self.globals().bind(py).set_item(name, value) {
96-
Ok(()) => (),
97-
Err(e) => {
98-
e.print(py);
99-
panic!("Unable to set `{}` from a `{}`", name, std::any::type_name::<T>());
91+
Python::with_gil(|py| {
92+
if let Err(e) = self.globals().bind(py).set_item(name, value) {
93+
panic!("Unable to set `{}` from a `{}`: {e}", name, std::any::type_name::<T>());
10094
}
10195
})
10296
}
@@ -127,10 +121,9 @@ impl Context {
127121
pub fn add_wrapped(&self, wrapper: &impl Fn(Python) -> PyResult<Bound<'_, PyCFunction>>) {
128122
Python::with_gil(|py| {
129123
let obj = wrapper(py).unwrap();
130-
let name = obj.getattr("__name__").expect("Missing __name__");
131-
if let Err(e) = self.globals().bind(py).set_item(name, obj) {
132-
e.print(py);
133-
panic!("Unable to add wrapped function");
124+
let name = obj.getattr("__name__").expect("wrapped item should have a __name__");
125+
if let Err(err) = self.globals().bind(py).set_item(name, obj) {
126+
panic!("{}", panic_string(py, &err));
134127
}
135128
})
136129
}
@@ -153,14 +146,28 @@ impl Context {
153146
Python::with_gil(|py| self.run_with_gil(py, code));
154147
}
155148

156-
pub(crate) fn run_with_gil<F: FnOnce(&Bound<PyDict>)>(&self, py: Python<'_>, code: PythonBlock<F>) {
157-
(code.set_variables)(self.globals().bind(py));
158-
match run_python_code(py, self, code.bytecode) {
159-
Ok(_) => (),
160-
Err(e) => {
161-
e.print(py);
162-
panic!("{}", "python!{...} failed to execute");
163-
}
149+
pub(crate) fn run_with_gil<F: FnOnce(&Bound<PyDict>)>(&self, py: Python<'_>, block: PythonBlock<F>) {
150+
(block.set_variables)(self.globals().bind(py));
151+
if let Err(err) = run_python_code(py, self, block.bytecode) {
152+
(block.panic)(panic_string(py, &err));
164153
}
165154
}
166155
}
156+
157+
fn panic_string(py: Python, err: &PyErr) -> String {
158+
match py_err_to_string(py, &err) {
159+
Ok(msg) => msg,
160+
Err(_) => err.to_string(),
161+
}
162+
}
163+
164+
/// Print the error while capturing stderr into a String.
165+
fn py_err_to_string(py: Python, err: &PyErr) -> Result<String, PyErr> {
166+
let sys = py.import("sys")?;
167+
let stderr = py.import("io")?.getattr("StringIO")?.call0()?;
168+
let original_stderr = sys.dict().get_item("stderr")?;
169+
sys.dict().set_item("stderr", &stderr)?;
170+
err.print(py);
171+
sys.dict().set_item("stderr", original_stderr)?;
172+
stderr.call_method0("getvalue")?.extract()
173+
}

src/lib.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,33 +139,46 @@ pub use inline_python_macros::python;
139139

140140
#[doc(hidden)]
141141
pub trait FromInlinePython<F: FnOnce(&Bound<PyDict>)> {
142-
fn from_python_macro(bytecode: &'static [u8], set_variables: F) -> Self;
142+
fn from_python_macro(bytecode: &'static [u8], set_variables: F, panic: fn(String) -> !) -> Self;
143143
}
144144

145145
/// Converting a `python!{}` block to `()` will run the Python code.
146146
///
147147
/// This happens when `python!{}` is used as a statement by itself.
148148
impl<F: FnOnce(&Bound<PyDict>)> FromInlinePython<F> for () {
149-
fn from_python_macro(bytecode: &'static [u8], set_variables: F) {
150-
let _: Context = FromInlinePython::from_python_macro(bytecode, set_variables);
149+
#[track_caller]
150+
fn from_python_macro(bytecode: &'static [u8], set_variables: F, panic: fn(String) -> !) {
151+
let _: Context = FromInlinePython::from_python_macro(bytecode, set_variables, panic);
151152
}
152153
}
153154

154155
/// Assigning a `python!{}` block to a `Context` will run the Python code and capture the resulting context.
155156
impl<F: FnOnce(&Bound<PyDict>)> FromInlinePython<F> for Context {
156-
fn from_python_macro(bytecode: &'static [u8], set_variables: F) -> Self {
157+
#[track_caller]
158+
fn from_python_macro(bytecode: &'static [u8], set_variables: F, panic: fn(String) -> !) -> Self {
157159
Python::with_gil(|py| {
158160
let context = Context::new_with_gil(py);
159-
context.run_with_gil(py, PythonBlock { bytecode, set_variables });
161+
context.run_with_gil(
162+
py,
163+
PythonBlock {
164+
bytecode,
165+
set_variables,
166+
panic,
167+
},
168+
);
160169
context
161170
})
162171
}
163172
}
164173

165174
/// Using a `python!{}` block as a `PythonBlock` object will not do anything yet.
166175
impl<F: FnOnce(&Bound<PyDict>)> FromInlinePython<F> for PythonBlock<F> {
167-
fn from_python_macro(bytecode: &'static [u8], set_variables: F) -> Self {
168-
Self { bytecode, set_variables }
176+
fn from_python_macro(bytecode: &'static [u8], set_variables: F, panic: fn(String) -> !) -> Self {
177+
Self {
178+
bytecode,
179+
set_variables,
180+
panic,
181+
}
169182
}
170183
}
171184

@@ -174,4 +187,5 @@ impl<F: FnOnce(&Bound<PyDict>)> FromInlinePython<F> for PythonBlock<F> {
174187
pub struct PythonBlock<F> {
175188
bytecode: &'static [u8],
176189
set_variables: F,
190+
panic: fn(String) -> !,
177191
}

0 commit comments

Comments
 (0)