diff --git a/src/conversion.rs b/src/conversion.rs index d9f5ba3a..771fb3ea 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -71,14 +71,45 @@ pub trait ToPyObject<'p> { } /// 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 = ::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] - fn from_py_object(s : &PyObject<'p>) -> PyResult<'p, T> { - Ok(try!(s.clone().cast_into())) + fn prepare_extract(obj: &'source PyObject<'python>) -> PyResult<'python, Self::Prepared> { + Ok(obj) + } + + #[inline] + fn extract(&&ref obj: &'prepared Self::Prepared) -> PyResult<'python, T> { + Ok(try!(obj.clone().cast_into())) } } diff --git a/src/lib.rs b/src/lib.rs index f603011e..985d65e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,7 @@ pub use err::{PyErr, PyResult}; pub use objects::*; pub use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonObjectWithTypeObject}; pub use pythonrun::{GILGuard, GILProtected, prepare_freethreaded_python}; -pub use conversion::{FromPyObject, ToPyObject}; +pub use conversion::{ExtractPyObject, ToPyObject}; pub use objectprotocol::{ObjectProtocol}; pub use rustobject::{PyRustType, PyRustObject}; pub use rustobject::typebuilder::PyRustTypeBuilder; diff --git a/src/objects/boolobject.rs b/src/objects/boolobject.rs index 008064b0..8c43e712 100644 --- a/src/objects/boolobject.rs +++ b/src/objects/boolobject.rs @@ -2,7 +2,7 @@ use ffi; use python::{Python, ToPythonPointer}; use err::PyResult; use super::PyObject; -use conversion::{FromPyObject, ToPyObject}; +use conversion::{ExtractPyObject, ToPyObject}; /// Represents a Python `bool`. pub struct PyBool<'p>(PyObject<'p>); @@ -44,9 +44,7 @@ impl <'p> ToPyObject<'p> for bool { /// Converts a Python `bool` to a rust `bool`. /// /// Fails with `TypeError` if the input is not a Python `bool`. -impl <'p> FromPyObject<'p> for bool { - fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, bool> { - Ok(try!(s.clone().cast_into::()).is_true()) - } -} +extract!(obj to bool => { + Ok(try!(obj.cast_as::()).is_true()) +}); diff --git a/src/objects/list.rs b/src/objects/list.rs index 78fe2671..07e3182c 100644 --- a/src/objects/list.rs +++ b/src/objects/list.rs @@ -20,7 +20,7 @@ use python::{Python, PythonObject, ToPythonPointer}; use err::{self, PyResult}; use super::object::PyObject; use ffi::{self, Py_ssize_t}; -use conversion::{ToPyObject, FromPyObject}; +use conversion::{ToPyObject, ExtractPyObject}; /// Represents a Python `list`. 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 where T: FromPyObject<'p> { - fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, Vec> { - let list = try!(s.cast_as::()); +impl <'python, 'source, 'prepared, T> ExtractPyObject<'python, 'source, 'prepared> + for Vec where T: for<'s, 'p> ExtractPyObject<'python, 's, 'p> { + + 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> { + let list = try!(obj.cast_as::()); let mut v = Vec::with_capacity(list.len()); for i in 0 .. list.len() { v.push(try!(list.get_item(i).extract::())); diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 0160c1df..e2c6d2da 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -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 typeobject; mod module; diff --git a/src/objects/num.rs b/src/objects/num.rs index 7d2cad9d..c3e624fc 100644 --- a/src/objects/num.rs +++ b/src/objects/num.rs @@ -24,7 +24,7 @@ use err::{self, PyResult, PyErr}; use super::object::PyObject; use super::exc; use ffi; -use conversion::{ToPyObject, FromPyObject}; +use conversion::{ToPyObject, ExtractPyObject}; /// Represents a Python `int` object. /// @@ -118,35 +118,17 @@ macro_rules! int_fits_c_long( } } - #[cfg(feature="python27-sys")] - 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::PyInt_AsLong(s.as_ptr()) }; - if val == -1 && PyErr::occurred(py) { - return Err(PyErr::fetch(py)); - } - match num::traits::cast::(val) { - Some(v) => Ok(v), - None => Err(overflow_error(py)) - } + extract!(obj to $rust_type => { + let py = obj.python(); + let val = unsafe { ffi::PyLong_AsLong(obj.as_ptr()) }; + if val == -1 && PyErr::occurred(py) { + return Err(PyErr::fetch(py)); } - } - - #[cfg(feature="python3-sys")] - 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::(val) { - Some(v) => Ok(v), - None => Err(overflow_error(py)) - } + match num::traits::cast::(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 { - fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> { - let py = s.python(); - let val = try!(s.extract::<$larger_type>()); - match num::traits::cast::<$larger_type, $rust_type>(val) { - Some(v) => Ok(v), - None => Err(overflow_error(py)) - } + extract!(obj to $rust_type => { + let py = obj.python(); + let val = try!(obj.extract::<$larger_type>()); + match num::traits::cast::<$larger_type, $rust_type>(val) { + Some(v) => Ok(v), + 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")] - fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> { - let py = s.python(); - let ptr = s.as_ptr(); + fn extract(&obj: &'prepared &'source PyObject<'python>) -> PyResult<'python, $rust_type> { + let py = obj.python(); + let ptr = obj.as_ptr(); unsafe { 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 { match num::traits::cast::(ffi::PyInt_AS_LONG(ptr)) { Some(v) => Ok(v), @@ -237,12 +226,12 @@ macro_rules! int_convert_u64_or_i64 ( } #[cfg(feature="python3-sys")] - fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, $rust_type> { - let py = s.python(); - let ptr = s.as_ptr(); + fn extract(&obj: &'prepared &'source PyObject<'python>) -> PyResult<'python, $rust_type> { + let py = obj.python(); + let ptr = obj.as_ptr(); unsafe { 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 { 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()) ) @@ -291,17 +280,15 @@ impl <'p> ToPyObject<'p> for f64 { } } -impl <'p> FromPyObject<'p> for f64 { - fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, f64> { - let py = s.python(); - let v = unsafe { ffi::PyFloat_AsDouble(s.as_ptr()) }; - if v == -1.0 && PyErr::occurred(py) { - Err(PyErr::fetch(py)) - } else { - Ok(v) - } +extract!(obj to f64 => { + let py = obj.python(); + let v = unsafe { ffi::PyFloat_AsDouble(obj.as_ptr()) }; + if v == -1.0 && PyErr::occurred(py) { + Err(PyErr::fetch(py)) + } else { + Ok(v) } -} +}); fn overflow_error(py: Python) -> PyErr { PyErr::new_lazy_init(py.get_type::(), None) @@ -315,11 +302,9 @@ impl <'p> ToPyObject<'p> for f32 { } } -impl <'p> FromPyObject<'p> for f32 { - fn from_py_object(s: &PyObject<'p>) -> PyResult<'p, f32> { - Ok(try!(s.extract::()) as f32) - } -} +extract!(obj to f32 => { + Ok(try!(obj.extract::()) as f32) +}); #[cfg(test)] mod test { diff --git a/src/objects/object.rs b/src/objects/object.rs index 8d228f0c..7d83b730 100644 --- a/src/objects/object.rs +++ b/src/objects/object.rs @@ -243,8 +243,10 @@ impl <'p> PyObject<'p> { /// Extracts some type from the Python object. /// This is a wrapper function around `FromPyObject::from_py_object()`. #[inline] - pub fn extract(&self) -> Result> where T: ::conversion::FromPyObject<'p> { - ::conversion::FromPyObject::from_py_object(self) + pub fn extract<'s, T>(&'s self) -> Result> + where T: for<'prep> ::conversion::ExtractPyObject<'p, 's, 'prep> { + let prepared = try!(::prepare_extract(self)); + ::extract(&prepared) } } diff --git a/src/objects/string.rs b/src/objects/string.rs index a13d6145..5ac5d799 100644 --- a/src/objects/string.rs +++ b/src/objects/string.rs @@ -25,7 +25,7 @@ use ffi; use python::{Python, PythonObject, ToPythonPointer}; use super::{exc, PyObject}; use err::{self, PyResult, PyErr}; -use conversion::{FromPyObject, ToPyObject}; +use conversion::{ExtractPyObject, ToPyObject}; /// Represents a Python byte string. /// 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. /// Accepts Python `str` and `unicode` objects. /// In Python 2.7, `str` is expected to be UTF-8 encoded. -impl <'p> FromPyObject<'p> for String { - fn from_py_object(o: &PyObject<'p>) -> PyResult<'p, String> { - PyString::extract(o).map(|s| s.into_owned()) +extract!(obj to Cow<'source, str> => { + PyString::extract(obj) +}); + +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] -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::().unwrap()); +#[cfg(test)] +mod test { + use python::{Python, PythonObject}; + use conversion::{ToPyObject, ExtractPyObject}; + + #[test] + 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::().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()); + } } + diff --git a/src/objects/tuple.rs b/src/objects/tuple.rs index 70b4450d..64bdb6ef 100644 --- a/src/objects/tuple.rs +++ b/src/objects/tuple.rs @@ -21,7 +21,7 @@ use err::{self, PyResult, PyErr}; use super::object::PyObject; use super::exc; use ffi::{self, Py_ssize_t}; -use conversion::{ToPyObject, FromPyObject}; +use conversion::{ToPyObject, ExtractPyObject}; /// Represents a Python tuple object. 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. /// Otherwise, returns an error. -impl <'p> FromPyObject<'p> for NoArgs { - fn from_py_object(s : &PyObject<'p>) -> PyResult<'p, NoArgs> { - let t = try!(s.cast_as::()); - if t.len() == 0 { - Ok(NoArgs) - } else { - Err(wrong_tuple_length(t, 0)) - } +extract!(obj to NoArgs => { + let t = try!(obj.cast_as::()); + if t.len() == 0 { + Ok(NoArgs) + } else { + Err(wrong_tuple_length(t, 0)) } -} +});