Replace FromPyObject with the more powerful ExtractPyObject.

This is a [breaking-change] and makes the trait more difficult to implement.
The usage through PyObject::extract() is unchanged.

This change allows extracting a `&str` through a temporary `Cow<str>`
without having to copy the string data from python to rust
(at least in cases where the python string is UTF-8 encoded).

This is preparation in hope I'll be able to make py_fn!()
automatically extract the function arguments.
This commit is contained in:
Daniel Grunwald 2015-07-18 22:39:55 +02:00
parent 1ad27b977e
commit 45d03bf8bb
9 changed files with 181 additions and 100 deletions

View File

@ -71,14 +71,45 @@ pub trait ToPyObject<'p> {
} }
/// FromPyObject is implemented by various types that can be extracted from a Python object. /// FromPyObject is implemented by various types that can be extracted from a Python object.
pub trait FromPyObject<'p> { ///
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, Self>; /// Usage:
/// ```let obj: PyObject = ...;
/// let prepared = <TargetType as FromPyObject>::prepare_extract(&obj);
/// let extracted = try!(extract(&prepared));```
///
/// Note: depending on the implementation, the lifetime of the extracted result may
/// depend on the lifetime of the `obj` or the `prepared` variable.
///
/// For example, when extracting `&str` from a python byte string, the resulting string slice will
/// point to the existing string data (lifetime: `'source`).
/// On the other hand, when extracting `&str` from a python unicode string, the preparation step
/// will convert the string to UTF-8, and the resulting string slice will have lifetime `'prepared`.
/// Since only which of these cases applies depends on the runtime type of the python object,
/// both the `obj` and `prepared` variables must outlive the resulting string slice.
///
/// In cases where the result does not depend on the `'prepared` lifetime,
/// the inherent method `PyObject::extract()` can be used.
pub trait ExtractPyObject<'python, 'source, 'prepared> {
type Prepared;
fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared>;
fn extract(prepared: &'prepared Self::Prepared) -> PyResult<'python, Self>;
} }
impl <'p, T> FromPyObject<'p> for T where T: PythonObjectWithCheckedDowncast<'p> { impl <'python, 'source, 'prepared, T> ExtractPyObject<'python, 'source, 'prepared>
for T where T: PythonObjectWithCheckedDowncast<'python> {
type Prepared = &'source PyObject<'python>;
#[inline] #[inline]
fn from_py_object(s : &PyObject<'p>) -> PyResult<'p, T> { fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared> {
Ok(try!(s.clone().cast_into())) Ok(obj)
}
#[inline]
fn extract(&&ref obj: &'prepared Self::Prepared) -> PyResult<'python, T> {
Ok(try!(obj.clone().cast_into()))
} }
} }

View File

@ -88,7 +88,7 @@ pub use err::{PyErr, PyResult};
pub use objects::*; pub use objects::*;
pub use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonObjectWithTypeObject}; pub use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonObjectWithTypeObject};
pub use pythonrun::{GILGuard, GILProtected, prepare_freethreaded_python}; pub use pythonrun::{GILGuard, GILProtected, prepare_freethreaded_python};
pub use conversion::{FromPyObject, ToPyObject}; pub use conversion::{ExtractPyObject, ToPyObject};
pub use objectprotocol::{ObjectProtocol}; pub use objectprotocol::{ObjectProtocol};
pub use rustobject::{PyRustType, PyRustObject}; pub use rustobject::{PyRustType, PyRustObject};
pub use rustobject::typebuilder::PyRustTypeBuilder; pub use rustobject::typebuilder::PyRustTypeBuilder;

View File

@ -2,7 +2,7 @@ use ffi;
use python::{Python, ToPythonPointer}; use python::{Python, ToPythonPointer};
use err::PyResult; use err::PyResult;
use super::PyObject; use super::PyObject;
use conversion::{FromPyObject, ToPyObject}; use conversion::{ExtractPyObject, ToPyObject};
/// Represents a Python `bool`. /// Represents a Python `bool`.
pub struct PyBool<'p>(PyObject<'p>); pub struct PyBool<'p>(PyObject<'p>);
@ -44,9 +44,7 @@ impl <'p> ToPyObject<'p> for bool {
/// Converts a Python `bool` to a rust `bool`. /// Converts a Python `bool` to a rust `bool`.
/// ///
/// Fails with `TypeError` if the input is not a Python `bool`. /// Fails with `TypeError` if the input is not a Python `bool`.
impl <'p> FromPyObject<'p> for bool { extract!(obj to bool => {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, bool> { Ok(try!(obj.cast_as::<PyBool>()).is_true())
Ok(try!(s.clone().cast_into::<PyBool>()).is_true()) });
}
}

View File

@ -20,7 +20,7 @@ use python::{Python, PythonObject, ToPythonPointer};
use err::{self, PyResult}; use err::{self, PyResult};
use super::object::PyObject; use super::object::PyObject;
use ffi::{self, Py_ssize_t}; use ffi::{self, Py_ssize_t};
use conversion::{ToPyObject, FromPyObject}; use conversion::{ToPyObject, ExtractPyObject};
/// Represents a Python `list`. /// Represents a Python `list`.
pub struct PyList<'p>(PyObject<'p>); pub struct PyList<'p>(PyObject<'p>);
@ -136,9 +136,19 @@ impl <'p, T> ToPyObject<'p> for [T] where T: ToPyObject<'p> {
} }
} }
impl <'p, T> FromPyObject<'p> for Vec<T> where T: FromPyObject<'p> { impl <'python, 'source, 'prepared, T> ExtractPyObject<'python, 'source, 'prepared>
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, Vec<T>> { for Vec<T> where T: for<'s, 'p> ExtractPyObject<'python, 's, 'p> {
let list = try!(s.cast_as::<PyList>());
type Prepared = &'source PyObject<'python>;
#[inline]
fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared> {
Ok(obj)
}
#[inline]
fn extract(&&ref obj: &'prepared Self::Prepared) -> PyResult<'python, Vec<T>> {
let list = try!(obj.cast_as::<PyList>());
let mut v = Vec::with_capacity(list.len()); let mut v = Vec::with_capacity(list.len());
for i in 0 .. list.len() { for i in 0 .. list.len() {
v.push(try!(list.get_item(i).extract::<T>())); v.push(try!(list.get_item(i).extract::<T>()));

View File

@ -151,6 +151,27 @@ macro_rules! pyobject_newtype(
); );
); );
macro_rules! extract(
($obj:ident to $t:ty => $body: block) => {
impl <'python, 'source, 'prepared>
::conversion::ExtractPyObject<'python, 'source, 'prepared>
for $t
{
type Prepared = &'source PyObject<'python>;
#[inline]
fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared> {
Ok(obj)
}
fn extract(&$obj: &'prepared &'source PyObject<'python>) -> PyResult<'python, Self> {
$body
}
}
}
);
mod object; mod object;
mod typeobject; mod typeobject;
mod module; mod module;

View File

@ -24,7 +24,7 @@ use err::{self, PyResult, PyErr};
use super::object::PyObject; use super::object::PyObject;
use super::exc; use super::exc;
use ffi; use ffi;
use conversion::{ToPyObject, FromPyObject}; use conversion::{ToPyObject, ExtractPyObject};
/// Represents a Python `int` object. /// Represents a Python `int` object.
/// ///
@ -118,35 +118,17 @@ macro_rules! int_fits_c_long(
} }
} }
#[cfg(feature="python27-sys")] extract!(obj to $rust_type => {
impl <'p> FromPyObject<'p> for $rust_type { let py = obj.python();
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> { let val = unsafe { ffi::PyLong_AsLong(obj.as_ptr()) };
let py = s.python(); if val == -1 && PyErr::occurred(py) {
let val = unsafe { ffi::PyInt_AsLong(s.as_ptr()) }; return Err(PyErr::fetch(py));
if val == -1 && PyErr::occurred(py) {
return Err(PyErr::fetch(py));
}
match num::traits::cast::<c_long, $rust_type>(val) {
Some(v) => Ok(v),
None => Err(overflow_error(py))
}
} }
} match num::traits::cast::<c_long, $rust_type>(val) {
Some(v) => Ok(v),
#[cfg(feature="python3-sys")] None => Err(overflow_error(py))
impl <'p> FromPyObject<'p> for $rust_type {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> {
let py = s.python();
let val = unsafe { ffi::PyLong_AsLong(s.as_ptr()) };
if val == -1 && PyErr::occurred(py) {
return Err(PyErr::fetch(py));
}
match num::traits::cast::<c_long, $rust_type>(val) {
Some(v) => Ok(v),
None => Err(overflow_error(py))
}
} }
} });
) )
); );
@ -162,16 +144,14 @@ macro_rules! int_fits_larger_int(
} }
} }
impl <'p> FromPyObject<'p> for $rust_type { extract!(obj to $rust_type => {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> { let py = obj.python();
let py = s.python(); let val = try!(obj.extract::<$larger_type>());
let val = try!(s.extract::<$larger_type>()); match num::traits::cast::<$larger_type, $rust_type>(val) {
match num::traits::cast::<$larger_type, $rust_type>(val) { Some(v) => Ok(v),
Some(v) => Ok(v), None => Err(overflow_error(py))
None => Err(overflow_error(py))
}
} }
} });
) )
); );
@ -215,15 +195,24 @@ macro_rules! int_convert_u64_or_i64 (
} }
} }
impl <'p> FromPyObject<'p> for $rust_type { impl <'python, 'source, 'prepared>
ExtractPyObject<'python, 'source, 'prepared> for $rust_type
{
type Prepared = &'source PyObject<'python>;
#[inline]
fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared> {
Ok(obj)
}
#[cfg(feature="python27-sys")] #[cfg(feature="python27-sys")]
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> { fn extract(&obj: &'prepared &'source PyObject<'python>) -> PyResult<'python, $rust_type> {
let py = s.python(); let py = obj.python();
let ptr = s.as_ptr(); let ptr = obj.as_ptr();
unsafe { unsafe {
if ffi::PyLong_Check(ptr) != 0 { if ffi::PyLong_Check(ptr) != 0 {
err_if_invalid_value(s, !0, || $pylong_as_ull_or_ull(s.as_ptr()) ) err_if_invalid_value(&obj, !0, || $pylong_as_ull_or_ull(obj.as_ptr()) )
} else if ffi::PyInt_Check(ptr) != 0 { } else if ffi::PyInt_Check(ptr) != 0 {
match num::traits::cast::<c_long, $rust_type>(ffi::PyInt_AS_LONG(ptr)) { match num::traits::cast::<c_long, $rust_type>(ffi::PyInt_AS_LONG(ptr)) {
Some(v) => Ok(v), Some(v) => Ok(v),
@ -237,12 +226,12 @@ macro_rules! int_convert_u64_or_i64 (
} }
#[cfg(feature="python3-sys")] #[cfg(feature="python3-sys")]
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> { fn extract(&obj: &'prepared &'source PyObject<'python>) -> PyResult<'python, $rust_type> {
let py = s.python(); let py = obj.python();
let ptr = s.as_ptr(); let ptr = obj.as_ptr();
unsafe { unsafe {
if ffi::PyLong_Check(ptr) != 0 { if ffi::PyLong_Check(ptr) != 0 {
err_if_invalid_value(s, !0, || $pylong_as_ull_or_ull(s.as_ptr()) ) err_if_invalid_value(&obj, !0, || $pylong_as_ull_or_ull(obj.as_ptr()) )
} else { } else {
let num = try!(err::result_from_owned_ptr(py, ffi::PyNumber_Long(ptr))); let num = try!(err::result_from_owned_ptr(py, ffi::PyNumber_Long(ptr)));
err_if_invalid_value(&num, !0, || $pylong_as_ull_or_ull(num.as_ptr()) ) err_if_invalid_value(&num, !0, || $pylong_as_ull_or_ull(num.as_ptr()) )
@ -291,17 +280,15 @@ impl <'p> ToPyObject<'p> for f64 {
} }
} }
impl <'p> FromPyObject<'p> for f64 { extract!(obj to f64 => {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, f64> { let py = obj.python();
let py = s.python(); let v = unsafe { ffi::PyFloat_AsDouble(obj.as_ptr()) };
let v = unsafe { ffi::PyFloat_AsDouble(s.as_ptr()) }; if v == -1.0 && PyErr::occurred(py) {
if v == -1.0 && PyErr::occurred(py) { Err(PyErr::fetch(py))
Err(PyErr::fetch(py)) } else {
} else { Ok(v)
Ok(v)
}
} }
} });
fn overflow_error(py: Python) -> PyErr { fn overflow_error(py: Python) -> PyErr {
PyErr::new_lazy_init(py.get_type::<exc::OverflowError>(), None) PyErr::new_lazy_init(py.get_type::<exc::OverflowError>(), None)
@ -315,11 +302,9 @@ impl <'p> ToPyObject<'p> for f32 {
} }
} }
impl <'p> FromPyObject<'p> for f32 { extract!(obj to f32 => {
fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, f32> { Ok(try!(obj.extract::<f64>()) as f32)
Ok(try!(s.extract::<f64>()) as f32) });
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View File

@ -243,8 +243,10 @@ impl <'p> PyObject<'p> {
/// Extracts some type from the Python object. /// Extracts some type from the Python object.
/// This is a wrapper function around `FromPyObject::from_py_object()`. /// This is a wrapper function around `FromPyObject::from_py_object()`.
#[inline] #[inline]
pub fn extract<T>(&self) -> Result<T, PyErr<'p>> where T: ::conversion::FromPyObject<'p> { pub fn extract<'s, T>(&'s self) -> Result<T, PyErr<'p>>
::conversion::FromPyObject::from_py_object(self) where T: for<'prep> ::conversion::ExtractPyObject<'p, 's, 'prep> {
let prepared = try!(<T as ::conversion::ExtractPyObject>::prepare_extract(self));
<T as ::conversion::ExtractPyObject>::extract(&prepared)
} }
} }

View File

@ -25,7 +25,7 @@ use ffi;
use python::{Python, PythonObject, ToPythonPointer}; use python::{Python, PythonObject, ToPythonPointer};
use super::{exc, PyObject}; use super::{exc, PyObject};
use err::{self, PyResult, PyErr}; use err::{self, PyResult, PyErr};
use conversion::{FromPyObject, ToPyObject}; use conversion::{ExtractPyObject, ToPyObject};
/// Represents a Python byte string. /// Represents a Python byte string.
/// Corresponds to `str` in Python 2, and `bytes` in Python 3. /// Corresponds to `str` in Python 2, and `bytes` in Python 3.
@ -281,21 +281,57 @@ impl <'p> ToPyObject<'p> for String {
} }
} }
/// Allows extracting strings from Python objects.
/// Accepts Python `str` and `unicode` objects.
/// In Python 2.7, `str` is expected to be UTF-8 encoded.
extract!(obj to String => {
PyString::extract(obj).map(|s| s.into_owned())
});
/// Allows extracting strings from Python objects. /// Allows extracting strings from Python objects.
/// Accepts Python `str` and `unicode` objects. /// Accepts Python `str` and `unicode` objects.
/// In Python 2.7, `str` is expected to be UTF-8 encoded. /// In Python 2.7, `str` is expected to be UTF-8 encoded.
impl <'p> FromPyObject<'p> for String { extract!(obj to Cow<'source, str> => {
fn from_py_object(o: &PyObject<'p>) -> PyResult<'p, String> { PyString::extract(obj)
PyString::extract(o).map(|s| s.into_owned()) });
impl <'python, 'source, 'prepared> ExtractPyObject<'python, 'source, 'prepared> for &'prepared str {
type Prepared = Cow<'source, str>;
#[inline]
fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared> {
PyString::extract(obj)
}
#[inline]
fn extract(cow: &'prepared Cow<'source, str>) -> PyResult<'python, Self> {
Ok(cow)
} }
} }
#[test] #[cfg(test)]
fn test_non_bmp() { mod test {
let gil = Python::acquire_gil(); use python::{Python, PythonObject};
let py = gil.python(); use conversion::{ToPyObject, ExtractPyObject};
let s = "\u{1F30F}";
let py_string = s.to_py_object(py).into_object(); #[test]
assert_eq!(s, py_string.extract::<String>().unwrap()); fn test_non_bmp() {
let gil = Python::acquire_gil();
let py = gil.python();
let s = "\u{1F30F}";
let py_string = s.to_py_object(py).into_object();
assert_eq!(s, py_string.extract::<String>().unwrap());
}
#[test]
fn test_extract_str() {
let gil = Python::acquire_gil();
let py = gil.python();
let s = "Hello Python";
let py_string = s.to_py_object(py).into_object();
let prepared = <&str>::prepare_extract(&py_string).unwrap();
assert_eq!(s, <&str>::extract(&prepared).unwrap());
}
} }

View File

@ -21,7 +21,7 @@ use err::{self, PyResult, PyErr};
use super::object::PyObject; use super::object::PyObject;
use super::exc; use super::exc;
use ffi::{self, Py_ssize_t}; use ffi::{self, Py_ssize_t};
use conversion::{ToPyObject, FromPyObject}; use conversion::{ToPyObject, ExtractPyObject};
/// Represents a Python tuple object. /// Represents a Python tuple object.
pub struct PyTuple<'p>(PyObject<'p>); pub struct PyTuple<'p>(PyObject<'p>);
@ -219,14 +219,12 @@ impl <'p> ToPyObject<'p> for NoArgs {
/// Returns `Ok(NoArgs)` if the input is an empty Python tuple. /// Returns `Ok(NoArgs)` if the input is an empty Python tuple.
/// Otherwise, returns an error. /// Otherwise, returns an error.
impl <'p> FromPyObject<'p> for NoArgs { extract!(obj to NoArgs => {
fn from_py_object(s : &PyObject<'p>) -> PyResult<'p, NoArgs> { let t = try!(obj.cast_as::<PyTuple>());
let t = try!(s.cast_as::<PyTuple>()); if t.len() == 0 {
if t.len() == 0 { Ok(NoArgs)
Ok(NoArgs) } else {
} else { Err(wrong_tuple_length(t, 0))
Err(wrong_tuple_length(t, 0))
}
} }
} });