py_class!: add support for __str__, __repr__, __unicode__ and __format__

This commit is contained in:
Daniel Grunwald 2016-05-05 07:31:14 +02:00
parent b37fa220df
commit be0c1573aa
5 changed files with 166 additions and 26 deletions

View File

@ -17,7 +17,7 @@
// DEALINGS IN THE SOFTWARE.
use libc;
use std::{mem, ptr, io, any};
use std::{mem, ptr, io, any, marker};
#[cfg(feature="nightly")] use std::panic;
use std::ffi::{CString, CStr};
use python::{Python, PythonObject, PyDrop};
@ -142,20 +142,37 @@ pub unsafe fn py_fn_impl(py: Python, method_def: *mut ffi::PyMethodDef) -> PyObj
err::from_owned_ptr_or_panic(py, ffi::PyCFunction_New(method_def, ptr::null_mut()))
}
pub trait CallbackConverter<T, R> {
fn convert(T, Python) -> R;
pub trait CallbackConverter<S, R> {
fn convert(S, Python) -> R;
fn error_value() -> R;
}
pub struct PyObjectCallbackConverter;
impl <T> CallbackConverter<T, *mut ffi::PyObject> for PyObjectCallbackConverter
where T: ToPyObject
impl <S> CallbackConverter<S, *mut ffi::PyObject> for PyObjectCallbackConverter
where S: ToPyObject
{
fn convert(val: T, py: Python) -> *mut ffi::PyObject {
fn convert(val: S, py: Python) -> *mut ffi::PyObject {
val.into_py_object(py).into_object().steal_ptr()
}
#[inline]
fn error_value() -> *mut ffi::PyObject {
ptr::null_mut()
}
}
pub struct PythonObjectCallbackConverter<T>(pub marker::PhantomData<T>);
impl <T, S> CallbackConverter<S, *mut ffi::PyObject> for PythonObjectCallbackConverter<T>
where T: PythonObject,
S: ToPyObject<ObjectType=T>
{
fn convert(val: S, py: Python) -> *mut ffi::PyObject {
val.into_py_object(py).into_object().steal_ptr()
}
#[inline]
fn error_value() -> *mut ffi::PyObject {
ptr::null_mut()
}

View File

@ -152,7 +152,8 @@ pub mod _detail {
pub use ::libc::{c_char, c_void, c_int};
}
pub use err::{from_owned_ptr_or_panic, result_from_owned_ptr};
pub use function::{handle_callback, py_fn_impl, AbortOnDrop, PyObjectCallbackConverter};
pub use function::{handle_callback, py_fn_impl, AbortOnDrop,
PyObjectCallbackConverter, PythonObjectCallbackConverter};
}
/// Expands to an `extern "C"` function that allows Python to load

View File

@ -545,20 +545,31 @@ def error(special_name, msg):
def unimplemented(special_name):
return error('%s is not supported by py_class! yet.' % special_name)(special_name)
@special_method
def normal_method(special_name):
pass
@special_method
def special_class_method(special_name, *args, **kwargs):
generate_class_method(special_name=special_name, *args, **kwargs)
@special_method
def unary_operator(special_name, slot,
res_type='*mut $crate::_detail::ffi::PyObject',
res_conv='$crate::_detail::PyObjectCallbackConverter'):
res_type='PyObject',
res_conv=None,
res_ffi_type='*mut $crate::_detail::ffi::PyObject'
):
if res_conv is None:
if res_type == 'PyObject':
res_conv = '$crate::_detail::PyObjectCallbackConverter'
else:
res_conv = '$crate::_detail::PythonObjectCallbackConverter::<$crate::%s>(::std::marker::PhantomData)' % res_type
generate_case(
pattern='def %s(&$slf:ident) -> $res_type:ty { $($body:tt)* }' % special_name,
new_impl='py_class_impl_item! { $class, $py, %s(&$slf,) $res_type; { $($body)* } [] }'
% special_name,
new_slots=[(slot, 'py_class_unary_slot!($class::%s, %s, %s)'
% (special_name, res_type, res_conv))]
% (special_name, res_ffi_type, res_conv))]
)
# Generate fall-back matcher that produces an error
# when using the wrong method signature
@ -571,11 +582,11 @@ special_names = {
value_macro='py_class_wrap_newfunc',
value_args='$class::__new__'),
'__del__': error('__del__ is not supported by py_class!; Use a data member with a Drop impl instead.'),
'__repr__': unimplemented(),
'__str__': unimplemented(),
'__unicode__': unimplemented(),
'__bytes__': unimplemented(),
'__format__': unimplemented(),
'__repr__': unary_operator('tp_repr', res_type="PyString"),
'__str__': unary_operator('tp_str', res_type="PyString"),
'__unicode__': unary_operator('tp_unicode', res_type="PyUnicode"),
'__bytes__': unary_operator('tp_bytes', res_type="PyBytes"),
'__format__': normal_method(),
# Comparison Operators
'__lt__': unimplemented(),
'__le__': unimplemented(),
@ -608,7 +619,7 @@ special_names = {
# Emulating container types
'__len__': unary_operator('sq_length',
res_type='$crate::_detail::ffi::Py_ssize_t',
res_ffi_type='$crate::_detail::ffi::Py_ssize_t',
res_conv='$crate::py_class::slots::LenResultConverter'),
'__length_hint__': unimplemented(),
'__getitem__': unimplemented(),

View File

@ -340,11 +340,34 @@ macro_rules! py_class_impl {
} => {
py_error! { "__bool__ is not supported by py_class! yet." }
};
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* }
$members:tt;
def __bytes__(&$slf:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $tp_slot_name : $tp_slot_value, )*
tp_bytes: py_class_unary_slot!($class::__bytes__, *mut $crate::_detail::ffi::PyObject, $crate::_detail::PythonObjectCallbackConverter::<$crate::PyBytes>(::std::marker::PhantomData)),
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __bytes__(&$slf,) $res_type; { $($body)* } [] }
}
$members; $($tail)*
}};
// def __bytes__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __bytes__ $($tail:tt)*
} => {
py_error! { "__bytes__ is not supported by py_class! yet." }
py_error! { "Invalid signature for unary operator __bytes__" }
};
// def __call__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
@ -448,12 +471,6 @@ macro_rules! py_class_impl {
} => {
py_error! { "__floordiv__ is not supported by py_class! yet." }
};
// def __format__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __format__ $($tail:tt)*
} => {
py_error! { "__format__ is not supported by py_class! yet." }
};
// def __ge__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __ge__ $($tail:tt)*
@ -866,11 +883,34 @@ macro_rules! py_class_impl {
} => {
py_error! { "__rdivmod__ is not supported by py_class! yet." }
};
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* }
$members:tt;
def __repr__(&$slf:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $tp_slot_name : $tp_slot_value, )*
tp_repr: py_class_unary_slot!($class::__repr__, *mut $crate::_detail::ffi::PyObject, $crate::_detail::PythonObjectCallbackConverter::<$crate::PyString>(::std::marker::PhantomData)),
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __repr__(&$slf,) $res_type; { $($body)* } [] }
}
$members; $($tail)*
}};
// def __repr__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __repr__ $($tail:tt)*
} => {
py_error! { "__repr__ is not supported by py_class! yet." }
py_error! { "Invalid signature for unary operator __repr__" }
};
// def __reversed__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
@ -974,11 +1014,34 @@ macro_rules! py_class_impl {
} => {
py_error! { "__setitem__ is not supported by py_class! yet." }
};
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* }
$members:tt;
def __str__(&$slf:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $tp_slot_name : $tp_slot_value, )*
tp_str: py_class_unary_slot!($class::__str__, *mut $crate::_detail::ffi::PyObject, $crate::_detail::PythonObjectCallbackConverter::<$crate::PyString>(::std::marker::PhantomData)),
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __str__(&$slf,) $res_type; { $($body)* } [] }
}
$members; $($tail)*
}};
// def __str__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __str__ $($tail:tt)*
} => {
py_error! { "__str__ is not supported by py_class! yet." }
py_error! { "Invalid signature for unary operator __str__" }
};
// def __sub__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
@ -998,11 +1061,34 @@ macro_rules! py_class_impl {
} => {
py_error! { "__truediv__ is not supported by py_class! yet." }
};
{ $class:ident $py:ident $info:tt
/* slots: */ {
/* type_slots */ [ $( $tp_slot_name:ident : $tp_slot_value:expr, )* ]
$as_number:tt $as_sequence:tt
}
{ $( $imp:item )* }
$members:tt;
def __unicode__(&$slf:ident) -> $res_type:ty { $($body:tt)* } $($tail:tt)*
} => { py_class_impl! {
$class $py $info
/* slots: */ {
/* type_slots */ [
$( $tp_slot_name : $tp_slot_value, )*
tp_unicode: py_class_unary_slot!($class::__unicode__, *mut $crate::_detail::ffi::PyObject, $crate::_detail::PythonObjectCallbackConverter::<$crate::PyUnicode>(::std::marker::PhantomData)),
]
$as_number $as_sequence
}
/* impl: */ {
$($imp)*
py_class_impl_item! { $class, $py, __unicode__(&$slf,) $res_type; { $($body)* } [] }
}
$members; $($tail)*
}};
// def __unicode__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;
def __unicode__ $($tail:tt)*
} => {
py_error! { "__unicode__ is not supported by py_class! yet." }
py_error! { "Invalid signature for unary operator __unicode__" }
};
// def __xor__()
{ $class:ident $py:ident $info:tt $slots:tt $impls:tt $members:tt;

View File

@ -330,3 +330,28 @@ fn iterator() {
py_assert!(py, inst, "list(inst) == [5, 6, 7]");
}
py_class!(class StringMethods |py| {
def __str__(&self) -> PyResult<&'static str> {
Ok("str")
}
def __repr__(&self) -> PyResult<&'static str> {
Ok("repr")
}
def __format__(&self, formatspec: &str) -> PyResult<String> {
Ok(format!("format({})", formatspec))
}
});
#[test]
fn string_methods() {
let gil = Python::acquire_gil();
let py = gil.python();
let obj = StringMethods::create_instance(py).unwrap();
py_assert!(py, obj, "str(obj) == 'str'");
py_assert!(py, obj, "repr(obj) == 'repr'");
py_assert!(py, obj, "'{0:x}'.format(obj) == 'format(x)'");
}