How to subclass a custom exception? #4440
-
Imagine I have the following custom #[derive(Debug, Default)]
pub struct CustomError {
msg: String,
int_vec: Vec<i32>,
}
impl CustomError {
pub fn new(msg: String, int_vec: Vec<i32>) -> Self {
Self {
msg,
int_vec,
}
}
pub fn msg(&self) -> String {
self.name.clone()
}
pub fn int_vec(&self) -> Vec<i32> {
self.int_vec.clone()
}
}
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name)
}
}
impl std::error::Error for CustomError {} Currently, I do the following: #[pyclass(module = "lib", name = "CustomError", extends = PyRuntimeError, subclass)] // keeping subclass here since I need it (?) bellow
#[derive(Debug, Default)]
pub struct LocalCustomError(external_lib::CustomError);
#[pymethods]
impl LocalCustomError {
#[new]
pub fn new(msg: Bound<'_, PyString>, int_vec: Bound<'_, PyList>) -> PyResult<Self> {
let _name = name.extract()?;
let _int_vec = int_vec.extract()?;
Ok(Self(external_lib::CustomError::new(
_name, _int_vec, _bool_prop,
)))
}
#[getter]
pub fn msg(&self, py: Python<'_>) -> PyObject {
let _ret = self.0.msg();
_ret.into_py(py)
}
#[getter]
pub fn int_vec(&self, py: Python<'_>) -> PyObject {
let _ret = self.0.int_vec();
_ret.into_py(py)
}
pub fn __str__(&self) -> String {
format!("{}", self.0)
}
}
impl From<LocalCustomError> for PyErr {
fn from(error: LocalCustomError) -> Self {
Python::with_gil(|py| {
let obj = Bound::new(py, error).unwrap();
PyErr::from_value_bound(obj.into_any())
})
}
} This works great in Python!! Given an instance of In [1]: type(err)
Out[1]: lib.CustomError
In [2]: (err.msg, err.int_vec)
Out[2]: ('some error', [1, 2])
In [3]: err.__class__.mro()
Out[3]:
[lib.CustomError,
RuntimeError,
Exception,
BaseException,
object]
In [4]: raise err
---------------------------------------------------------------------------
CustomError Traceback (most recent call last)
Cell In[10], line 1
----> 1 raise err
# some text...
CustomError: some error If I now have a second #[pyclass(module = "lib", name = "SubCustomError", extends = LocalCustomError)]
#[derive(Debug)]
pub struct LocalSubCustomError(external_lib::SubCustomError);
#[pymethods]
impl LocalSubCustomError {
#[new]
pub fn new(_py: Python<'_>) -> PyClassInitializer<Self> {
PyClassInitializer::from(LocalCustomError::default())
.add_subclass(Self(external_lib::SubCustomError))
}
pub fn __str__(&self) -> String {
format!("{}", self.0)
}
}
impl From<LocalSubCustomError> for PyErr {
fn from(error: LocalSubCustomError) -> Self {
Python::with_gil(|py| {
let obj = Bound::new(py, error).unwrap();
PyErr::from_value_bound(obj.into_any())
})
}
} which produced error: error[E0271]: type mismatch resolving `<LocalCustomError as PyClassBaseType>::Initializer == PyNativeTypeInitializer<LocalCustomError>`
--> diliopoulos/pyo3/rs/src/exceptions.rs:227:38
|
227 | let obj = Bound::new(py, error).unwrap();
| ---------- ^^^^^ expected `PyNativeTypeInitializer<LocalCustomError>`, found `PyClassInitializer<LocalCustomError>`
| |
| required by a bound introduced by this call
|
= note: expected struct `PyNativeTypeInitializer<exceptions::LocalCustomError>`
found struct `pyo3::PyClassInitializer<exceptions::LocalCustomError>`
= note: required for `pyo3::PyClassInitializer<exceptions::LocalSimpleError>` to implement `From<exceptions::LocalSimpleError>`
= note: required for `exceptions::LocalSimpleError` to implement `Into<pyo3::PyClassInitializer<exceptions::LocalSimpleError>>`
note: required by a bound in `pyo3::Bound::<'py, T>::new`
--> rust/pyo3-0.21.2/src/instance.rs:91:5 If I was using create_exception!(lib, CustomError, PyRuntimeError);
create_exception!(lib, SubCustomError, CustomError); I had a look at |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
The challenge here is that on the Python side the subclass For the impl From<LocalSubCustomError> for PyErr {
fn from(error: LocalSubCustomError) -> Self {
Python::with_gil(|py| {
let obj = Bound::new(
py,
PyClassInitializer::from(LocalCustomError::default())
.add_subclass(error)
).unwrap();
PyErr::from_value_bound(obj.into_any())
})
}
} (disclaimer: I didn't check if this compiles! 😬) |
Beta Was this translation helpful? Give feedback.
-
Thanks @davidhewitt, worked like a charm! |
Beta Was this translation helpful? Give feedback.
The challenge here is that on the Python side the subclass
SubCustomError
needs to have the Rust data forCustomError
populated. You already worked that out for your#[new]
function.For the
From
implementation, I think you can do similarly:(disclaimer: I didn't check if this compiles! 😬)