py_class!: add support for __str__, __repr__, __unicode__ and __format__
This commit is contained in:
parent
b37fa220df
commit
be0c1573aa
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)'");
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue