Pyo3: More verbose conversion/downcasting errors.

Created on 26 Oct 2019  路  5Comments  路  Source: PyO3/pyo3

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 for PyErr {
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())
~

enhancement

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:

>>> 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`")

All 5 comments

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::().map_err(|_| type_err(msg))
}
~

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

thedrow picture thedrow  路  5Comments

ametisf picture ametisf  路  3Comments

konstin picture konstin  路  5Comments

mvaled picture mvaled  路  3Comments

acminor picture acminor  路  3Comments