basic object customization interface

This commit is contained in:
Nikolay Kim 2017-05-16 16:54:27 -07:00
parent 086f24e7a1
commit 34e4d956f1
7 changed files with 209 additions and 27 deletions

View File

@ -61,6 +61,7 @@ static NUM_METHODS: Methods = Methods {
enum ImplType {
Object,
Async,
Buffer,
Context,
@ -76,6 +77,9 @@ pub fn build_py_proto(ast: &mut syn::Item) -> Tokens {
syn::ItemKind::Impl(_, _, _, ref path, ref ty, ref mut impl_items) => {
if let &Some(ref path) = path {
match process_path(path) {
ImplType::Object =>
impl_protocol("pyo3::class::async::PyObjectProtocolImpl",
path.clone(), ty, impl_items, &DEFAULT_METHODS),
ImplType::Async =>
impl_protocol("pyo3::class::async::PyAsyncProtocolImpl",
path.clone(), ty, impl_items, &DEFAULT_METHODS),
@ -112,6 +116,7 @@ pub fn build_py_proto(ast: &mut syn::Item) -> Tokens {
fn process_path(path: &syn::Path) -> ImplType {
if let Some(segment) = path.segments.last() {
match segment.ident.as_ref() {
"PyObjectProtocol" => ImplType::Object,
"PyAsyncProtocol" => ImplType::Async,
"PyBufferProtocol" => ImplType::Buffer,
"PyContextProtocol" => ImplType::Context,

View File

@ -5,14 +5,17 @@
//! more information on python async support
//! https://docs.python.org/3/reference/datamodel.html#basic-customization
use std::os::raw::c_int;
use ::{CompareOp, Py_hash_t};
use ffi;
use err::{PyErr, PyResult};
use python::{self, Python, PythonObject};
use python::{Python, PythonObject, PyDrop};
use conversion::ToPyObject;
use objects::{PyObject, PyType, PyModule};
use py_class::slots::UnitCallbackConverter;
use objects::{exc, PyObject};
use py_class::slots::{HashConverter, UnitCallbackConverter};
use function::{handle_callback, PyObjectCallbackConverter};
use class::NO_METHODS;
use class::{NO_METHODS, NO_PY_METHODS};
// __new__
// __init__
@ -24,24 +27,184 @@ use class::NO_METHODS;
/// Basic customization
pub trait PyObjectProtocol {
// fn __getattr__()
// fn __setattr__()
// fn __delattr__()
// fn __getattribute__
// fn __setattribute__
fn __getattr__(&self, py: Python, name: &PyObject) -> PyResult<PyObject>;
fn __setattr__(&self, py: Python, name: &PyObject, value: &PyObject) -> PyResult<()>;
fn __delattr__(&self, py: Python, name: &PyObject) -> PyResult<()>;
// __instancecheck__
// __subclasscheck__
// __iter__
// __next__
// __dir__
fn __str__(&self, py: Python) -> PyResult<PyString>;
fn __str__(&self, py: Python) -> PyResult<PyObject>;
fn __repr__(&self, py: Python) -> PyResult<PyString>;
fn __repr__(&self, py: Python) -> PyResult<PyObject>;
fn __hash__(&self, py: Python) -> PyResult<PyObject>;
fn __hash__(&self, py: Python) -> PyResult<u64>;
fn __bool__(&self, py: Python) -> PyResult<bool>;
fn __richcmp__(&self, other: PyObject, op: pyo3::CompareOp) -> PyResult<bool>;
fn __richcmp__(&self, py: Python, other: &PyObject, op: CompareOp) -> PyResult<PyObject>;
}
impl<T> PyObjectProtocol for T {
default fn __getattr__(&self, py: Python, _: &PyObject) -> PyResult<PyObject> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
default fn __setattr__(&self, py: Python, _: &PyObject, _: &PyObject) -> PyResult<()> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
default fn __delattr__(&self, py: Python, _: &PyObject) -> PyResult<()> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
// __instancecheck__
// __subclasscheck__
// __iter__
// __next__
// __dir__
default fn __str__(&self, py: Python) -> PyResult<PyObject> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
default fn __repr__(&self, py: Python) -> PyResult<PyObject> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
default fn __hash__(&self, py: Python) -> PyResult<u64> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
default fn __bool__(&self, py: Python) -> PyResult<bool> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
default fn __richcmp__(&self, py: Python, _: &PyObject, _: CompareOp) -> PyResult<PyObject> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
}
#[doc(hidden)]
pub trait PyObjectProtocolImpl {
fn methods() -> &'static [&'static str];
fn py_methods() -> &'static [::class::PyMethodDefType];
}
impl<T> PyObjectProtocolImpl for T {
default fn methods() -> &'static [&'static str] {
NO_METHODS
}
default fn py_methods() -> &'static [::class::PyMethodDefType] {
NO_PY_METHODS
}
}
pub fn py_object_proto_impl<T>(type_object: &mut ffi::PyTypeObject)
where T: PyObjectProtocol + PyObjectProtocolImpl + PythonObject
{
let methods = T::methods();
if methods.is_empty() {
return
}
for name in methods {
match name {
&"__str__" =>
type_object.tp_str = py_unary_func!(
PyObjectProtocol, T::__str__, PyObjectCallbackConverter),
&"__repr__" =>
type_object.tp_repr = py_unary_func!(
PyObjectProtocol, T::__repr__, PyObjectCallbackConverter),
&"__hash__" =>
type_object.tp_hash = py_unary_func!(
PyObjectProtocol, T::__hash__, HashConverter, Py_hash_t),
&"__getattr__" =>
type_object.tp_getattro = py_binary_func!(
PyObjectProtocol, T::__getattr__, PyObjectCallbackConverter),
&"__richcmp__" =>
type_object.tp_richcompare = tp_richcompare::<T>(),
_ => (),
}
}
if methods.contains(&"__setattr__") || methods.contains(&"__getattr__") {
type_object.tp_setattro = tp_setattro::<T>()
}
}
fn tp_setattro<T>() -> Option<ffi::setattrofunc>
where T: PyObjectProtocol + PythonObject
{
unsafe extern "C" fn wrap<T>(slf: *mut ffi::PyObject,
key: *mut ffi::PyObject,
value: *mut ffi::PyObject) -> c_int
where T: PyObjectProtocol + PythonObject
{
const LOCATION: &'static str = concat!(stringify!(T), ".__setitem__()");
handle_callback(
LOCATION, UnitCallbackConverter, |py|
{
let slf = PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<T>();
let key = PyObject::from_borrowed_ptr(py, key);
// if value is none, then __delitem__
let ret = if value.is_null() {
slf.__delattr__(py, &key)
} else {
let value = PyObject::from_borrowed_ptr(py, value);
let ret = slf.__setattr__(py, &key, &value);
PyDrop::release_ref(value, py);
ret
};
PyDrop::release_ref(key, py);
PyDrop::release_ref(slf, py);
ret
})
}
Some(wrap::<T>)
}
fn extract_op(py: Python, op: c_int) -> PyResult<CompareOp> {
match op {
ffi::Py_LT => Ok(CompareOp::Lt),
ffi::Py_LE => Ok(CompareOp::Le),
ffi::Py_EQ => Ok(CompareOp::Eq),
ffi::Py_NE => Ok(CompareOp::Ne),
ffi::Py_GT => Ok(CompareOp::Gt),
ffi::Py_GE => Ok(CompareOp::Ge),
_ => Err(PyErr::new_lazy_init(
py.get_type::<exc::ValueError>(),
Some("tp_richcompare called with invalid comparison operator"
.to_py_object(py).into_object())))
}
}
fn tp_richcompare<T>() -> Option<ffi::richcmpfunc>
where T: PyObjectProtocol + PythonObject
{
unsafe extern "C" fn wrap<T>(slf: *mut ffi::PyObject,
arg: *mut ffi::PyObject,
op: c_int) -> *mut ffi::PyObject
where T: PyObjectProtocol + PythonObject
{
const LOCATION: &'static str = concat!(stringify!(T), ".__richcmp__()");
handle_callback(LOCATION, PyObjectCallbackConverter, |py| {
let slf = PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<T>();
let arg = PyObject::from_borrowed_ptr(py, arg);
let ret = match extract_op(py, op) {
Ok(op) => slf.__richcmp__(py, &arg, op),
Err(_) => Ok(py.NotImplemented())
};
PyDrop::release_ref(arg, py);
PyDrop::release_ref(slf, py);
ret
})
}
Some(wrap::<T>)
}

View File

@ -29,19 +29,19 @@ pub trait PyDescrProtocol {
}
impl<P> PyDescrProtocol for P {
fn __get__(&self, py: Python, _: &PyObject, _: &PyObject) -> PyResult<PyObject> {
default fn __get__(&self, py: Python, _: &PyObject, _: &PyObject) -> PyResult<PyObject> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
fn __set__(&self, py: Python, _: &PyObject, _: &PyObject) -> PyResult<()> {
default fn __set__(&self, py: Python, _: &PyObject, _: &PyObject) -> PyResult<()> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
fn __delete__(&self, py: Python, _: &PyObject) -> PyResult<()> {
default fn __delete__(&self, py: Python, _: &PyObject) -> PyResult<()> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
fn __set_name__(&self, py: Python, _: &PyObject) -> PyResult<()> {
default fn __set_name__(&self, py: Python, _: &PyObject) -> PyResult<()> {
Err(PyErr::new::<exc::NotImplementedError, _>(py, "Not implemented"))
}
}

View File

@ -3,9 +3,11 @@
#[macro_export]
#[doc(hidden)]
macro_rules! py_unary_func {
($trait:ident, $class:ident :: $f:ident, $conv:expr) => {{
unsafe extern "C" fn wrap<T>(slf: *mut $crate::ffi::PyObject)
-> *mut $crate::ffi::PyObject
($trait:ident, $class:ident :: $f:ident, $conv:expr) => {
py_unary_func!($trait, $class::$f, $conv, *mut $crate::ffi::PyObject);
};
($trait:ident, $class:ident :: $f:ident, $conv:expr, $res_type:ty) => {{
unsafe extern "C" fn wrap<T>(slf: *mut $crate::ffi::PyObject) -> $res_type
where T: $trait + PythonObject
{
const LOCATION: &'static str = concat!(stringify!($class), ".", stringify!($f), "()");

View File

@ -3,6 +3,7 @@
#[macro_use] mod macros;
pub mod async;
pub mod basic;
pub mod buffer;
pub mod context;
pub mod descr;
@ -13,6 +14,7 @@ pub mod gc;
pub mod sequence;
pub mod typeob;
pub use self::basic::PyObjectProtocol;
pub use self::async::PyAsyncProtocol;
pub use self::buffer::PyBufferProtocol;
pub use self::context::PyContextProtocol;

View File

@ -3,13 +3,16 @@
//! Python Number Interface
//! Trait and support implementation for implementing number protocol
use std::os::raw::c_int;
use ffi;
use err::PyResult;
use python::{Python, PythonObject};
use objects::PyObject;
use function::PyObjectCallbackConverter;
use py_class::slots::BoolConverter;
use class::{NO_METHODS, NO_PY_METHODS};
use class::basic::{PyObjectProtocol, PyObjectProtocolImpl};
/// Number interface
pub trait PyNumberProtocol {
@ -244,10 +247,14 @@ impl ffi::PyNumberMethods {
/// Construct PyNumberMethods struct for PyTypeObject.tp_as_number
pub fn new<T>() -> Option<ffi::PyNumberMethods>
where T: PyNumberProtocol + PyNumberProtocolImpl + PythonObject
where T: PyNumberProtocol + PyNumberProtocolImpl
+ PyObjectProtocol + PyObjectProtocolImpl
+ PythonObject
{
let methods = T::methods();
if methods.is_empty() {
let objm = <T as PyObjectProtocolImpl>::methods();
let methods = <T as PyNumberProtocolImpl>::methods();
if methods.is_empty() && ! objm.contains(&"__bool__") {
return None
}
@ -366,7 +373,6 @@ impl ffi::PyNumberMethods {
&"__neg__" => {
meth.nb_negative = py_unary_func!(
PyNumberProtocol, T::__neg__, PyObjectCallbackConverter);
},
&"__pos__" => {
meth.nb_positive = py_unary_func!(
@ -401,6 +407,11 @@ impl ffi::PyNumberMethods {
}
}
if objm.contains(&"__bool__") {
meth.nb_bool = py_unary_func!(
PyObjectProtocol, T::__bool__, BoolConverter, c_int);
}
Some(meth)
}
}

View File

@ -90,7 +90,7 @@ pub use pyo3cls as cls;
pub use pyo3cls::*;
pub mod ffi;
pub use ffi::Py_ssize_t;
pub use ffi::{Py_ssize_t, Py_hash_t};
pub use err::{PyErr, PyResult};
pub use objects::*;
pub use python::{Python, PythonObject, PythonObjectWithCheckedDowncast, PythonObjectDowncastError, PythonObjectWithTypeObject, PyClone, PyDrop};
@ -100,7 +100,6 @@ pub use py_class::{CompareOp};
pub use objectprotocol::{ObjectProtocol};
#[allow(non_camel_case_types)]
pub type Py_hash_t = ffi::Py_hash_t;
use std::{ptr, mem};