pyo3/src/conversion.rs

187 lines
6.9 KiB
Rust
Raw Normal View History

2015-01-05 16:05:53 +00:00
use libc::c_char;
use std;
use ffi;
2015-01-05 15:34:12 +00:00
use python::{Python, PythonObject, PythonObjectWithCheckedDowncast};
2015-01-05 16:02:30 +00:00
use objects::PyObject;
2015-01-05 15:34:12 +00:00
use err::{self, PyErr, PyResult};
2015-01-05 16:05:53 +00:00
use pyptr::{PyPtr, PythonPointer};
/// ToPyObject is implemented for types that can be converted into a python object.
/// The goal is to allow methods that take a python object to take anything that
/// can be converted into a python object.
/// For example, compare calling the following method signatures:
/// fn m1(o: &PyObject) {}
/// fn m2<O>(o: &O) where O : ToPyObject {}
///
/// let o: &PyObject = ...;
/// m1(o);
/// m2(o);
///
/// let p: PyPtr<PyObject> = ...;
/// m1(*p)
/// m2(p)
///
/// let i: i32 = ...;
/// m1(*try!(i.to_py_object(py)))
/// m2(i)
pub trait ToPyObject<'p, 's> for Sized? {
2015-01-05 15:34:12 +00:00
type ResultType : PythonObject<'p> = PyObject<'p>;
// The returned pointer type is flexible:
// it can be either &PyObject or PyPtr<PyObject>, depending on whether
// the conversion is allocating a new object.
// This lets us avoid a useless IncRef/DecRef pair
2015-01-05 15:34:12 +00:00
type PointerType : PythonPointer + std::ops::Deref // <Target = Self::ResultType>
= PyPtr<'p, Self::ResultType>;
2015-01-05 16:05:53 +00:00
fn to_py_object(&'s self, py: Python<'p>) -> PyResult<'p, Self::PointerType>;
// Note that there are 6 cases with methods taking ToPyObject:
// 1) input is &PyObject, FFI function steals pointer
// -> ToPyObject is no-op, PythonPointer::steal_ptr() calls Py_IncRef()
// 2) input is &PyObject, FFI function borrows pointer
// -> ToPyObject is no-op, PythonPointer::as_ptr() also is no-op
// 3) input is &PyPtr<PyObject>, FFI function steals pointer
// -> ToPyObject borrows content, PythonPointer::steal_ptr() calls Py_IncRef()
// Not optimal, we'd prefer to take the input PyPtr by value.
// 4) input is &PyPtr<PyObject>, FFI function borrows pointer
// -> ToPyObject borrows content, PythonPointer::as_ptr() is no-op
// 5) input is &str, int, etc., FFI function steals pointer
// -> ToPyObject allocates new object, PythonPointer::steal_ptr() grabs existing owned pointer
// 6) input is &str, int, etc., FFI function borrows pointer
// -> ToPyObject allocates new object, PythonPointer::as_ptr() is no-op,
// PyPtr::drop calls Py_DecRef()
2015-01-05 16:05:53 +00:00
// So the only non-optimal case (3) is the one stealing from a PyPtr<PyObject>,
// which is unavoidable as long as to_py_object takes &self.
// Note that changing ToPyObject to take self by value would cause the PyPtr to become
// unusable in case (4) as well. Users would have to add a .clone() call if the PyPtr
// is still needed after the call, making case (4) non-optimal.
// We could potentially fix this by using separate ToPyObject and IntoPyObject traits
// for the borrowing and stealing cases.
// Note that the 'PointerType' associated type is essential to avoid unnecessarily
// touching the reference count in cases (2) and (4).
2015-01-05 15:34:12 +00:00
// Btw: I'm not sure if this type crazyness is worth it.
// If rust had automatic re-borrowing to avoid the '&*p' dance when using a PyPtr as &PyObject,
// we should probably just force the user to manually call .to_py()
2015-01-05 16:05:53 +00:00
}
/// FromPyObject is implemented by various types that can be extracted from a python object.
pub trait FromPyObject<'p, 's> {
fn from_py_object(s: &'s PyObject<'p>) -> PyResult<'p, Self>;
2015-01-05 16:05:53 +00:00
}
// PyObject, PyModule etc.
// We support FromPyObject and ToPyObject for borrowed python references.
2015-01-05 16:05:53 +00:00
// This allows using existing python objects in code that generically expects a value
// convertible to a python object.
impl <'p, 's, T> ToPyObject<'p, 's> for T where T : PythonObject<'p> {
2015-01-05 15:34:12 +00:00
type ResultType = T;
type PointerType = &'s T;
#[inline]
2015-01-05 15:34:12 +00:00
fn to_py_object(&'s self, py: Python<'p>) -> PyResult<'p, &'s T> {
Ok(self)
2015-01-05 16:05:53 +00:00
}
}
2015-01-05 15:34:12 +00:00
impl <'p, 's, T> FromPyObject<'p, 's> for &'s T where T: PythonObjectWithCheckedDowncast<'p> {
2015-01-05 16:05:53 +00:00
#[inline]
2015-01-04 19:11:18 +00:00
fn from_py_object(s: &'s PyObject<'p>) -> PyResult<'p, &'s T> {
2015-01-05 16:05:53 +00:00
s.downcast()
}
}
// PyPtr<T>
// We support FromPyObject and ToPyObject for owned python references.
2015-01-05 16:05:53 +00:00
// This allows using existing python objects in code that generically expects a value
// convertible to a python object, without having to re-borrow the &PyObject.
2015-01-05 16:05:53 +00:00
impl <'p, 's, T> ToPyObject<'p, 's> for PyPtr<'p, T> where T: PythonObject<'p> {
2015-01-05 15:34:12 +00:00
type ResultType = T;
type PointerType = &'s T;
#[inline]
2015-01-05 15:34:12 +00:00
fn to_py_object(&'s self, py: Python<'p>) -> PyResult<'p, &'s T> {
Ok(&**self)
2015-01-05 16:05:53 +00:00
}
}
2015-01-05 15:34:12 +00:00
impl <'p, 's, T> FromPyObject<'p, 's> for PyPtr<'p, T> where T: PythonObjectWithCheckedDowncast<'p> {
2015-01-05 16:05:53 +00:00
#[inline]
2015-01-04 19:11:18 +00:00
fn from_py_object(s : &'s PyObject<'p>) -> PyResult<'p, PyPtr<'p, T>> {
2015-01-05 16:05:53 +00:00
PyPtr::new(s).downcast_into()
}
}
// bool
2015-01-04 19:11:18 +00:00
impl <'p, 's> ToPyObject<'p, 's> for bool {
2015-01-05 15:34:12 +00:00
type ResultType = PyObject<'p>;
type PointerType = &'p PyObject<'p>;
2015-01-04 19:11:18 +00:00
#[inline]
fn to_py_object(&'s self, py: Python<'p>) -> PyResult<'p, &'p PyObject<'p>> {
Ok(if *self { py.True() } else { py.False() })
}
2015-01-05 16:05:53 +00:00
}
impl <'p, 'a> FromPyObject<'p, 'a> for bool {
fn from_py_object(s: &'a PyObject<'p>) -> PyResult<'p, bool> {
let py = s.python();
if s == py.True() {
Ok(true)
} else if s == py.False() {
Ok(false)
} else {
unimplemented!()
}
}
}
// Strings.
// When converting strings to/from python, we need to copy the string data.
// This means we can implement ToPyObject for str, but FromPyObject only for String.
impl <'p, 's> ToPyObject<'p, 's> for str {
2015-01-05 15:34:12 +00:00
type ResultType = PyObject<'p>;
type PointerType = PyPtr<'p, PyObject<'p>>;
fn to_py_object(&'s self, py : Python<'p>) -> PyResult<'p, PyPtr<'p, PyObject<'p>>> {
2015-01-05 16:05:53 +00:00
let ptr : *const c_char = self.as_ptr() as *const _;
let len : ffi::Py_ssize_t = std::num::from_uint(self.len()).unwrap();
unsafe {
use std::ascii::AsciiExt;
let obj = if self.is_ascii() {
ffi::PyString_FromStringAndSize(ptr, len)
} else {
ffi::PyUnicode_FromStringAndSize(ptr, len)
};
err::result_from_owned_ptr(py, obj)
}
}
}
impl <'p, 'a> FromPyObject<'p, 'a> for String {
fn from_py_object(s : &'a PyObject<'p>) -> PyResult<'p, String> {
string_as_slice(s).map(|buf| String::from_utf8_lossy(buf).to_string())
}
}
pub fn string_as_slice<'a, 'p>(s : &'a PyObject<'p>) -> PyResult<'p, &'a [u8]> {
unsafe {
let mut buffer : *mut c_char = std::mem::uninitialized();
let mut length : ffi::Py_ssize_t = std::mem::uninitialized();
if ffi::PyString_AsStringAndSize(s.as_ptr(), &mut buffer, &mut length) == 1 {
Err(PyErr::fetch(s.python()))
} else {
let buffer = buffer as *const u8;
Ok(std::slice::from_raw_buf(std::mem::copy_lifetime(s, &buffer), length as uint))
}
}
}