-
Notifications
You must be signed in to change notification settings - Fork 758
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a proc macro for exceptions #295
Comments
I'd like to try my hand at this. Would that be fine? If so, I'm not super familiar with the details here, so would this issue be a good place to ask questions? |
Thanks, please don't hesitate to ask us! |
Yes, just ask here! |
I'm nominating this for |
Nearly everything else in |
For those watching this, in #1591 we made it possible to write This doesn't yet replace the
I'm wondering if we can eliminate the differences by adding a |
...thinking about this one in particular and interaction with |
Saw this had a However, the How should I solve that issue? (Moving important bits like that felt like it deserved some feedback first) |
You shouldn't have to do that - nothing in the macro crate has any idea about pyo3's types. Do you have experience writing proc macros? |
Ah, the thing I was missing that while you write the macro in that crate, you re-export and test it within the other depending crate! I don't have much proc macro experience, you're right! Now, I've reached the point where I tried to follow the recommendation of implementing I've been reaching for But that whole strategy of making a wrapping struct might not be the direction folks were wanting to go in and I'm missing some other (possibly very obvious!) technique I could explore. I'm happy to hear ideas! |
Hmm, perhaps my above comment was slightly too fleeting a thought without enough consideration. Maybe an alternative approach would be to have a |
Any update on this? If I'm being honest I'm not the most positive on what's going on with the above stuff, but I'd like this functionality in my own code to enhance the usability of my exceptions. Anything I can do to help push things along? |
@hwittenborn no further progress here other than the above thread. Really what this issue needs is someone who's able to invest the time to experiment with design to come up with a proposal of what we still need. Implementation can be worked out along the way, I can mentor and review PRs, though I wasn't expecting to find time to dedicate myself to this one at the moment.
It would be interesting to hear what you feel you need which |
I want to be able to return exceptions with custom fields, such as the line number an error occured on. Currently I have this: #[derive(Debug)]
#[pyclass(extends=PyException)]
pub struct ParserError {
/// A message describing the parsing error.
pub msg: String,
/// The line number the error occured on. This will always be the [`Some`] variant unless there
/// was an issue with the file as a whole, in which case the [`None`] variant will be returned.
pub line_num: Option<usize>,
} And then in another part of my code where I'd like to raise that exception, which I'm not sure how to make work with #[new]
fn new(content: String) -> PyResult<Self> {
match RustSrcInfo::new(&content) {
Ok(srcinfo) => Ok(SrcInfo { srcinfo }),
Err(err) => {
let msg: String;
if let Some(line_num) = err.line_num {
msg = format!("[Line {}] {}", line_num, err.msg);
} else {
msg = err.msg;
}
let py_err = ParserError { msg: err.msg, line_num: err.line_num};
Err(py_err)
}
}
} error[E0308]: mismatched types
--> src/python.rs:42:21
|
42 | Err(py_err)
| --- ^^^^^^ expected struct `PyErr`, found struct `ParserError`
| |
| arguments to this enum variant are incorrect
| I can send a more general example if needed. |
Okay, I've learned a good deal about how the Python runtime manages exceptions and some of what needs to change in PyO3 to accommodate the goal of having instance attributes (instance members, whatever) on Python exceptions defined in Rust-land. There's a few plausible next steps one could follow and I know less about PyO3's state, so I figured I should write down what I know about the Python side in case others pick up the torch. The current and the desired implementationsSo, right now, PyO3 uses the cpython-provided PyErr_NewExceptionWithDoc to create exception types in If one wants a more custom exception type, you're meant to first create a This is basically what you're supposed to do with normal Python classes with the extra sauce of I assume everything in this If How I learned the Python part of this problemHow to create exception types using the C API is difficult to search up because the answer is basically "make them like other Python classes but take care with the inheritance" while the search results you get are ones that explicitly mention exceptions like the docs for the underpowered We can figure out what to do by looking at how the cpython runtime defines its exceptions. Specifically, the built-in exceptions (like RuntimeError or NameError) are defined using C macros that create global
Meanwhile, Instead, the strategy of using Next actionsThere's a couple possible next actions.
Footnotes
|
(Meta point: I'm pretty sure this ticket should have the "Good First Issue" label dropped. It's been open for a few years and it doesn't seem to have a trivial solution) |
I think the approach outlined in #295 (comment) would be nicer, i.e. we use the existing machinery around |
I managed to get the basics of this working by loading a python string: fn raise_MemoryError() -> PyErr {
Python::with_gil(|py| {
let icicle = py.import("icicle").unwrap();
let exception = icicle.getattr("MemoryError").unwrap();
let inst = exception.call0().unwrap();
PyErr::from_value(inst)
})
}
#[pymodule]
fn icicle(py: Python<'_>, m: &PyModule) -> PyResult<()> {
PyModule::from_code(py, r#"
class MemoryError(Exception):
pass
"#, "icicle_exceptions.py", "icicle")?;
Ok(())
} Obviously this is extremely ugly, but I couldn't figure out how to do this the 'proper' way... |
Slightly modified it to also accept a message + code: fn raise_MemoryError(message: String, e: MemError) -> PyErr {
Python::with_gil(|py| {
let icicle = py.import("icicle").unwrap();
let exception = icicle.getattr("MemoryError").unwrap();
let xx = MemoryErrorCode::from(e);
let args = (message, xx, );
let inst = exception.call1(args).unwrap();
PyErr::from_value(inst)
})
}
#[pymodule]
fn icicle(py: Python<'_>, m: &PyModule) -> PyResult<()> {
PyModule::from_code(py, r#"
class MemoryError(Exception):
def __init__(self, message, code):
super().__init__(message)
self.code = code
def __str__(self):
return f"{super().__str__()}: {self.code}"
"#, "icicle_exceptions.py", "icicle")?;
Ok(())
} Super ugly, but this ended up working exactly as I needed: try:
vm.mem_protect(addr, 0x2000, icicle.MemoryProtection.ExecuteRead)
except icicle.MemoryError as x:
message = x.args[0]
print(message, x.code) |
If I'm reading everything correctly, extending from PyException won't work if you use abi3-py38 feature: I'll try the ugly approach @mrexodia used and see if that works. |
As a general FYI I slightly changed my approach and instead put the exception (+ type hints) in Then to raise the exception (with argument): This is all a bit hacky because of the double module name, but it's working with |
@mrexodia Thanks a bunch for the comment -- we also just took the same approach. Others can look here for our implementation: https://github.com/BoundaryML/baml/pull/1038/files |
I would be great to have a custom derive for exceptions, similar but more powerful than create_exception. This would e.g. allow reimplementing
JSONDecodeError
:The steps required are roughly:
#[proc_macro_attribute]
function should go inpyo3cls/src/lib.rs
, while the actual implementation should be inpyo3-derive-backend
.If you want to work on implementing this or have any questions, feel free ask here or on gitter!
The text was updated successfully, but these errors were encountered: