pyo3 tries to convert arguments to the specified type in the signature, if that fails it returns TypeError without any message through:
~Rust
/// Converts PyDowncastError to Python TypeError.
impl std::convert::From
fn from(_err: PyDowncastError) -> PyErr {
exceptions::TypeError.into()
}
}
~
Since conversion happens before entering the user-defined method body, there's no way to be verbose about what happened unless some new-type is defined for which extract() is implemented manually or the method is changed to take &PyAny or PyObject to begin with - which also entails manual extraction.
Are there obvious downsides to changing PyDowncastError to wrap a String holding an error message that gets propagated into Python?
In that case, the failed conversions could return something along these lines:
~Rust
PyDowncastError(format!(Can't extract some_type from {}", ob.get_type().name())
~
Looks nice :+1:
Thanks.
I was looking at the code generation yesterday and it would be possible to be verbose there, but not in a clean way: Either the (possibly in extract() user-defined) error message is lost or the generic error message would need to be extended by the one coming out of extract().
Another option could be changing the conversion impls in types to something like:
~Rust
/// Allows extracting strings from Python objects.
/// Accepts Python str and unicode objects.
impl<'a> crate::FromPyObject<'a> for &'a str {
fn extract(ob: &'a PyAny) -> PyResult
let s: Cow<'a, str> = crate::FromPyObject::extract(ob).map_err(|_| {
TypeError::py_err(format!(
"Failed to extract string from {}",
ob.get_type().name()
))
})?;
match s {
Cow::Borrowed(r) => Ok(r),
Cow::Owned(r) => {
let r = ob.py().register_any(r);
Ok(r.as_str())
}
}
}
}
~
As a workaround within my own project, this little helper method comes in handy:
~Rust
pub(crate) fn t_from_any<'a, T>(any: &'a PyAny, to: Option<&str>) -> PyResult
where
T: FromPyObject<'a>,
{
let any_type = any.get_type().name();
let msg = to
.map(|to| format!("Expected '{}' not '{}'", to, any_type))
.unwrap_or_else(|| format!("Invalid argument: '{}'", any_type));
any.extract::
}
~
This would be a really useful feature. It would would also be good to include the name of the argument that has the wrong type.
E.g. right now you get this:
>>> connect(url=b'localhost:3000', timeout=2.0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError
What I would like to happen is something more like this:
>>> connect(url=b'localhost:3000', timeout=2.0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError("Parameter `url` has type `str` but received value of type `bytes`")
I'd like to take a look at some form of this for 0.12.
Closed via #1050
Most helpful comment
This would be a really useful feature. It would would also be good to include the name of the argument that has the wrong type.
E.g. right now you get this:
What I would like to happen is something more like this: