[pypy-commit] pypy default: merge buffer-interface2 which improves cpyext support for the new buffer interface

mattip pypy.commits at gmail.com
Sun Oct 16 10:39:18 EDT 2016


Author: Matti Picus <matti.picus at gmail.com>
Branch: 
Changeset: r87832:a9164735893a
Date: 2016-10-16 17:32 +0300
http://bitbucket.org/pypy/pypy/changeset/a9164735893a/

Log:	merge buffer-interface2 which improves cpyext support for the new
	buffer interface

diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -207,6 +207,9 @@
 
     def buffer_w(self, space, flags):
         w_impl = space.lookup(self, '__buffer__')
+        if w_impl is None:
+            # cpyext types that may have only old buffer interface
+            w_impl = space.lookup(self, '__wbuffer__')
         if w_impl is not None:
             w_result = space.get_and_call_function(w_impl, self, 
                                         space.newint(flags))
@@ -215,7 +218,10 @@
         raise BufferInterfaceNotFound
 
     def readbuf_w(self, space):
-        w_impl = space.lookup(self, '__buffer__')
+        # cpyext types that may have old buffer protocol
+        w_impl = space.lookup(self, '__rbuffer__')
+        if w_impl is None:
+            w_impl = space.lookup(self, '__buffer__')
         if w_impl is not None:
             w_result = space.get_and_call_function(w_impl, self,
                                         space.newint(space.BUF_FULL_RO))
@@ -224,7 +230,10 @@
         raise BufferInterfaceNotFound
 
     def writebuf_w(self, space):
-        w_impl = space.lookup(self, '__buffer__')
+        # cpyext types that may have old buffer protocol
+        w_impl = space.lookup(self, '__wbuffer__')
+        if w_impl is None:
+            w_impl = space.lookup(self, '__buffer__')
         if w_impl is not None:
             w_result = space.get_and_call_function(w_impl, self,
                                         space.newint(space.BUF_FULL))
diff --git a/pypy/module/cpyext/include/object.h b/pypy/module/cpyext/include/object.h
--- a/pypy/module/cpyext/include/object.h
+++ b/pypy/module/cpyext/include/object.h
@@ -144,7 +144,7 @@
 
 /* Py3k buffer interface, adapted for PyPy */
 #define Py_MAX_NDIMS 32
-#define Py_MAX_FMT 5
+#define Py_MAX_FMT 128
 typedef struct bufferinfo {
     void *buf;
     PyObject *obj;        /* owned reference */
diff --git a/pypy/module/cpyext/memoryobject.py b/pypy/module/cpyext/memoryobject.py
--- a/pypy/module/cpyext/memoryobject.py
+++ b/pypy/module/cpyext/memoryobject.py
@@ -4,6 +4,7 @@
 from rpython.rtyper.lltypesystem import lltype, rffi
 from rpython.rlib.rarithmetic import widen
 from pypy.objspace.std.memoryobject import W_MemoryView
+from pypy.module.cpyext.import_ import PyImport_Import
 
 PyMemoryView_Check, PyMemoryView_CheckExact = build_type_checkers("MemoryView", "w_memoryview")
 
@@ -33,33 +34,43 @@
         view.c_buf = rffi.cast(rffi.VOIDP, buf.get_raw_address())
     except ValueError:
         raise BufferError("could not create buffer from object")
+    ret = fill_Py_buffer(space, buf, view)
     view.c_obj = make_ref(space, w_obj)
-    return fill_Py_buffer(space, buf, view)
+    return ret
 
-def fill_Py_buffer(space, buf, view):    
+def fill_Py_buffer(space, buf, view):
     # c_buf, c_obj have been filled in
     ndim = buf.getndim()
     view.c_len = buf.getlength()
     view.c_itemsize = buf.getitemsize()
     rffi.setintfield(view, 'c_ndim', ndim)
     view.c_format = rffi.cast(rffi.CCHARP, view.c__format)
-    view.c_shape = rffi.cast(Py_ssize_tP, view.c__shape)
-    view.c_strides = rffi.cast(Py_ssize_tP, view.c__strides)
     fmt = buf.getformat()
     n = Py_MAX_FMT - 1 # NULL terminated buffer
     if len(fmt) > n:
-        ### WARN?
-        pass
+        w_message = space.newbytes("PyPy specific Py_MAX_FMT is %d which is too "
+                           "small for buffer format, %d needed" % (
+                           Py_MAX_FMT, len(fmt)))
+        w_stacklevel = space.newint(1)
+        w_module = PyImport_Import(space, space.newbytes("warnings"))
+        w_warn = space.getattr(w_module, space.newbytes("warn"))
+        space.call_function(w_warn, w_message, space.w_None, w_stacklevel)
     else:
         n = len(fmt)
     for i in range(n):
         view.c_format[i] = fmt[i]
-    view.c_format[n] = '\x00'        
-    shape = buf.getshape()
-    strides = buf.getstrides()
-    for i in range(ndim):
-        view.c_shape[i] = shape[i]
-        view.c_strides[i] = strides[i]
+    view.c_format[n] = '\x00'
+    if ndim > 0:
+        view.c_shape = rffi.cast(Py_ssize_tP, view.c__shape)
+        view.c_strides = rffi.cast(Py_ssize_tP, view.c__strides)
+        shape = buf.getshape()
+        strides = buf.getstrides()
+        for i in range(ndim):
+            view.c_shape[i] = shape[i]
+            view.c_strides[i] = strides[i]
+    else:
+        view.c_shape = lltype.nullptr(Py_ssize_tP.TO)
+        view.c_strides = lltype.nullptr(Py_ssize_tP.TO)
     view.c_suboffsets = lltype.nullptr(Py_ssize_tP.TO)
     view.c_internal = lltype.nullptr(rffi.VOIDP.TO)
     return 0
@@ -102,12 +113,12 @@
 
 @cpython_api([lltype.Ptr(Py_buffer), lltype.Char], rffi.INT_real, error=CANNOT_FAIL)
 def PyBuffer_IsContiguous(space, view, fort):
-    """Return 1 if the memory defined by the view is C-style (fortran is
-    'C') or Fortran-style (fortran is 'F') contiguous or either one
-    (fortran is 'A').  Return 0 otherwise."""
+    """Return 1 if the memory defined by the view is C-style (fort is
+    'C') or Fortran-style (fort is 'F') contiguous or either one
+    (fort is 'A').  Return 0 otherwise."""
     # traverse the strides, checking for consistent stride increases from
     # right-to-left (c) or left-to-right (fortran). Copied from cpython
-    if not view.c_suboffsets:
+    if view.c_suboffsets:
         return 0
     if (fort == 'C'):
         return _IsCContiguous(view)
@@ -139,6 +150,7 @@
     if ndim >= Py_MAX_NDIMS:
         # XXX warn?
         return view
+    fill_Py_buffer(space, w_obj.buf, view)
     try:
         view.c_buf = rffi.cast(rffi.VOIDP, w_obj.buf.get_raw_address())
         view.c_obj = make_ref(space, w_obj)
@@ -147,8 +159,8 @@
     except ValueError:
         w_s = w_obj.descr_tobytes(space)
         view.c_obj = make_ref(space, w_s)
+        view.c_buf = rffi.cast(rffi.VOIDP, rffi.str2charp(space.str_w(w_s), track_allocation=False))
         rffi.setintfield(view, 'c_readonly', 1)
         isstr = True
-    fill_Py_buffer(space, w_obj.buf, view)     
     return view
 
diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py
--- a/pypy/module/cpyext/pyerrors.py
+++ b/pypy/module/cpyext/pyerrors.py
@@ -21,7 +21,7 @@
 @cpython_api([PyObject, CONST_STRING], lltype.Void)
 def PyErr_SetString(space, w_type, message_ptr):
     message = rffi.charp2str(message_ptr)
-    PyErr_SetObject(space, w_type, space.wrap(message))
+    PyErr_SetObject(space, w_type, space.newbytes(message))
 
 @cpython_api([PyObject], lltype.Void, error=CANNOT_FAIL)
 def PyErr_SetNone(space, w_type):
@@ -150,12 +150,12 @@
     Return value: always NULL."""
     # XXX Doesn't actually do anything with PyErr_CheckSignals.
     if llfilename:
-        w_filename = rffi.charp2str(llfilename)
-        filename = space.wrap(w_filename)
+        filename = rffi.charp2str(llfilename)
+        w_filename = space.newbytes(filename)
     else:
-        filename = space.w_None
+        w_filename = space.w_None
 
-    PyErr_SetFromErrnoWithFilenameObject(space, w_type, filename)
+    PyErr_SetFromErrnoWithFilenameObject(space, w_type, w_filename)
 
 @cpython_api([PyObject, PyObject], PyObject)
 @jit.dont_look_inside       # direct use of _get_errno()
@@ -170,13 +170,13 @@
     msg = os.strerror(errno)
     if w_value:
         w_error = space.call_function(w_type,
-                                      space.wrap(errno),
-                                      space.wrap(msg),
+                                      space.newint(errno),
+                                      space.newbytes(msg),
                                       w_value)
     else:
         w_error = space.call_function(w_type,
-                                      space.wrap(errno),
-                                      space.wrap(msg))
+                                      space.newint(errno),
+                                      space.newbytes(msg))
     raise OperationError(w_type, w_error)
 
 @cpython_api([], rffi.INT_real, error=-1)
@@ -252,11 +252,11 @@
     documentation.  There is no C API for warning control."""
     if w_category is None:
         w_category = space.w_None
-    w_message = space.wrap(rffi.charp2str(message_ptr))
-    w_stacklevel = space.wrap(rffi.cast(lltype.Signed, stacklevel))
+    w_message = space.newbytes(rffi.charp2str(message_ptr))
+    w_stacklevel = space.newint(rffi.cast(lltype.Signed, stacklevel))
 
-    w_module = PyImport_Import(space, space.wrap("warnings"))
-    w_warn = space.getattr(w_module, space.wrap("warn"))
+    w_module = PyImport_Import(space, space.newbytes("warnings"))
+    w_warn = space.getattr(w_module, space.newbytes("warn"))
     space.call_function(w_warn, w_message, w_category, w_stacklevel)
     return 0
 
@@ -317,10 +317,10 @@
 
 @cpython_api([PyObject, PyObject], rffi.INT_real, error=-1)
 def PyTraceBack_Print(space, w_tb, w_file):
-    space.call_method(w_file, "write", space.wrap(
+    space.call_method(w_file, "write", space.newbytes(
         'Traceback (most recent call last):\n'))
     w_traceback = space.call_method(space.builtin, '__import__',
-                                    space.wrap("traceback"))
+                                    space.newbytes("traceback"))
     space.call_method(w_traceback, "print_tb", w_tb, space.w_None, w_file)
     return 0
 
diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py
--- a/pypy/module/cpyext/slotdefs.py
+++ b/pypy/module/cpyext/slotdefs.py
@@ -15,6 +15,7 @@
     readbufferproc, getbufferproc, ssizessizeobjargproc)
 from pypy.module.cpyext.pyobject import from_ref, make_ref, Py_DecRef
 from pypy.module.cpyext.pyerrors import PyErr_Occurred
+from pypy.module.cpyext.memoryobject import fill_Py_buffer
 from pypy.module.cpyext.state import State
 from pypy.interpreter.error import OperationError, oefmt
 from pypy.interpreter.argument import Arguments
@@ -349,6 +350,10 @@
     def getndim(self):
         return self.ndim
 
+    def setitem(self, index, char):
+        # absolutely no safety checks, what could go wrong?
+        self.ptr[index] = char
+
 def wrap_getreadbuffer(space, w_self, w_args, func):
     func_target = rffi.cast(readbufferproc, func)
     with lltype.scoped_alloc(rffi.VOIDPP.TO, 1) as ptr:
@@ -358,6 +363,15 @@
             space.fromcache(State).check_and_raise_exception(always=True)
         return space.newbuffer(CPyBuffer(ptr[0], size, w_self))
 
+def wrap_getwritebuffer(space, w_self, w_args, func):
+    func_target = rffi.cast(readbufferproc, func)
+    with lltype.scoped_alloc(rffi.VOIDPP.TO, 1) as ptr:
+        index = rffi.cast(Py_ssize_t, 0)
+        size = generic_cpy_call(space, func_target, w_self, index, ptr)
+        if size < 0:
+            space.fromcache(State).check_and_raise_exception(always=True)
+        return space.newbuffer(CPyBuffer(ptr[0], size, w_self, readonly=False))
+
 def wrap_getbuffer(space, w_self, w_args, func):
     func_target = rffi.cast(getbufferproc, func)
     with lltype.scoped_alloc(Py_buffer) as pybuf:
@@ -608,13 +622,27 @@
         @cpython_api([PyObject, Py_bufferP, rffi.INT_real], 
                 rffi.INT_real, header=None, error=-1)
         @func_renamer("cpyext_%s_%s" % (name.replace('.', '_'), typedef.name))
-        def buff_w(space, w_self, pybuf, flags):
-            # XXX this is wrong, needs a test
-            raise oefmt(space.w_NotImplemented, 
-                "calling bf_getbuffer on a builtin type not supported yet")
-            #args = Arguments(space, [w_self],
-            #                 w_stararg=w_args, w_starstararg=w_kwds)
-            #return space.call_args(space.get(buff_fn, w_self), args)
+        def buff_w(space, w_self, view, flags):
+            args = Arguments(space, [space.newint(flags)])
+            w_obj = space.call_args(space.get(buff_fn, w_self), args)
+            if view:
+                #like PyObject_GetBuffer
+                flags = widen(flags)
+                buf = space.buffer_w(w_obj, flags)
+                try:
+                    view.c_buf = rffi.cast(rffi.VOIDP, buf.get_raw_address())
+                    view.c_obj = make_ref(space, w_obj)
+                except ValueError:
+                    w_s = space.newbytes(buf.as_str())
+                    view.c_obj = make_ref(space, w_s)
+                    view.c_buf = rffi.cast(rffi.VOIDP, rffi.str2charp(
+                                    space.str_w(w_s), track_allocation=False))
+                    rffi.setintfield(view, 'c_readonly', 1)
+                ret = fill_Py_buffer(space, buf, view)
+                return ret
+            return 0
+        # XXX remove this when it no longer crashes a translated PyPy
+        return
         api_func = buff_w.api_func
     else:
         # missing: tp_as_number.nb_nonzero, tp_as_number.nb_coerce
@@ -924,13 +952,13 @@
 slotdefs = eval(slotdefs_str)
 # PyPy addition
 slotdefs += (
-    # XXX that might not be what we want!
     TPSLOT("__buffer__", "tp_as_buffer.c_bf_getbuffer", None, "wrap_getbuffer", ""),
 )
 
 if not PY3:
     slotdefs += (
-        TPSLOT("__buffer__", "tp_as_buffer.c_bf_getreadbuffer", None, "wrap_getreadbuffer", ""),
+        TPSLOT("__rbuffer__", "tp_as_buffer.c_bf_getreadbuffer", None, "wrap_getreadbuffer", ""),
+        TPSLOT("__wbuffer__", "tp_as_buffer.c_bf_getwritebuffer", None, "wrap_getwritebuffer", ""),
     )
 
 
diff --git a/pypy/module/cpyext/test/array.c b/pypy/module/cpyext/test/array.c
--- a/pypy/module/cpyext/test/array.c
+++ b/pypy/module/cpyext/test/array.c
@@ -2394,14 +2394,29 @@
     (iternextfunc)arrayiter_next,               /* tp_iternext */
     0,                                          /* tp_methods */
 };
+static PyObject *
+readbuffer_as_string(PyObject *self, PyObject *args)
+{
+    PyObject *obj;
+    const void *ptr;
+    Py_ssize_t size;
+
+    if (!PyArg_ParseTuple(args, "O", &obj)) {
+        return NULL;
+    }
+    if (PyObject_AsReadBuffer(obj, &ptr, &size) < 0)
+        return NULL;
+    return PyString_FromStringAndSize((char*)ptr, size);
+}
+
 
 
 /*********************** Install Module **************************/
 
-/* No functions in array module. */
 static PyMethodDef a_methods[] = {
     {"_reconstruct",   (PyCFunction)_reconstruct, METH_VARARGS, NULL},
     {"switch_multiply",   (PyCFunction)switch_multiply, METH_NOARGS, NULL},
+    {"readbuffer_as_string",   (PyCFunction)readbuffer_as_string, METH_VARARGS, NULL},
     {NULL, NULL, 0, NULL}        /* Sentinel */
 };
 
diff --git a/pypy/module/cpyext/test/test_abstract.py b/pypy/module/cpyext/test/test_abstract.py
--- a/pypy/module/cpyext/test/test_abstract.py
+++ b/pypy/module/cpyext/test/test_abstract.py
@@ -104,3 +104,17 @@
         assert raises(TypeError, buffer_support.readbuffer_as_string, 42)
         assert raises(TypeError, buffer_support.writebuffer_as_string, 42)
         assert raises(TypeError, buffer_support.charbuffer_as_string, 42)
+
+    def test_user_class(self):
+        class MyBuf(str):
+            pass
+        s = 'a\0x'
+        buf = MyBuf(s)
+        buffer_support = self.get_buffer_support()
+
+        assert buffer_support.check_readbuffer(buf)
+        assert s == buffer_support.readbuffer_as_string(buf)
+        assert raises(TypeError, buffer_support.writebuffer_as_string, buf)
+        assert s == buffer_support.charbuffer_as_string(buf)
+
+
diff --git a/pypy/module/cpyext/test/test_arraymodule.py b/pypy/module/cpyext/test/test_arraymodule.py
--- a/pypy/module/cpyext/test/test_arraymodule.py
+++ b/pypy/module/cpyext/test/test_arraymodule.py
@@ -1,8 +1,17 @@
 from pypy.module.cpyext.test.test_cpyext import AppTestCpythonExtensionBase
-
+from pypy.conftest import option
 
 class AppTestArrayModule(AppTestCpythonExtensionBase):
-    enable_leak_checking = False
+    enable_leak_checking = True
+
+    def setup_class(cls):
+        from rpython.tool.udir import udir
+        AppTestCpythonExtensionBase.setup_class.im_func(cls)
+        if option.runappdirect:
+            cls.w_udir = str(udir)
+        else:
+            cls.w_udir = cls.space.wrap(str(udir))
+
 
     def test_basic(self):
         module = self.import_module(name='array')
@@ -90,6 +99,7 @@
         assert res == [2, 4, 6]
 
     def test_subclass(self):
+        import struct
         module = self.import_module(name='array')
         class Sub(module.array):
             pass
@@ -98,3 +108,37 @@
         res = [1, 2, 3] * arr
         assert res == [1, 2, 3, 1, 2, 3]
         
+        val = module.readbuffer_as_string(arr)
+        assert val == struct.pack('i', 2)
+
+    def test_unicode_readbuffer(self):
+        # Not really part of array, refactor
+        import struct
+        module = self.import_module(name='array')
+        val = module.readbuffer_as_string('abcd')
+        assert val == 'abcd'
+        val = module.readbuffer_as_string(u'\u03a3')
+        assert val is not None
+
+    def test_readinto(self):
+        module = self.import_module(name='array')
+        a = module.array('c')
+        a.fromstring('0123456789')
+        filename = self.udir + "/_test_file"
+        f = open(filename, 'w+b')
+        f.write('foobar')
+        f.seek(0)
+        n = f.readinto(a)
+        f.close()
+        assert n == 6
+        assert len(a) == 10
+        assert a.tostring() == 'foobar6789'
+
+    def test_iowrite(self):
+        module = self.import_module(name='array')
+        from io import BytesIO
+        a = module.array('c')
+        a.fromstring('0123456789')
+        fd = BytesIO()
+        # only test that it works
+        fd.write(a)
diff --git a/pypy/module/cpyext/test/test_getargs.py b/pypy/module/cpyext/test/test_getargs.py
--- a/pypy/module/cpyext/test/test_getargs.py
+++ b/pypy/module/cpyext/test/test_getargs.py
@@ -139,6 +139,12 @@
             return result;
             ''')
         assert 'foo\0bar\0baz' == pybuffer(buffer('foo\0bar\0baz'))
+        import sys
+        if '__pypy__' not in sys.builtin_module_names:
+            class A(object):
+                def __buffer__(self, flags):
+                    return buffer('123')
+            assert pybuffer(A()) == '123'
 
 
     def test_pyarg_parse_string_fails(self):
diff --git a/pypy/module/cpyext/test/test_memoryobject.py b/pypy/module/cpyext/test/test_memoryobject.py
--- a/pypy/module/cpyext/test/test_memoryobject.py
+++ b/pypy/module/cpyext/test/test_memoryobject.py
@@ -1,9 +1,14 @@
+import pytest
+
 from rpython.rtyper.lltypesystem import rffi
 from pypy.module.cpyext.test.test_api import BaseApiTest
 from pypy.module.cpyext.test.test_cpyext import AppTestCpythonExtensionBase
 from rpython.rlib.buffer import StringBuffer
 
+only_pypy ="config.option.runappdirect and '__pypy__' not in sys.builtin_module_names" 
+
 class TestMemoryViewObject(BaseApiTest):
+    skip('needs c_bf_getbuffer wrapper from slotdefs')
     def test_fromobject(self, space, api):
         w_hello = space.newbytes("hello")
         assert api.PyObject_CheckBuffer(w_hello)
@@ -23,6 +28,8 @@
         assert w_view.c_shape[0] == 5
         assert w_view.c_strides[0] == 1
         assert w_view.c_len == 5
+        o = rffi.charp2str(w_view.c_buf)
+        assert o == 'hello'
 
 class AppTestBufferProtocol(AppTestCpythonExtensionBase):
     def test_buffer_protocol(self):
@@ -39,8 +46,12 @@
         viewlen = module.test_buffer(arr)
         assert viewlen == y.itemsize * len(y)
 
+    @pytest.mark.skipif(only_pypy, reason='pypy only test')
     def test_buffer_info(self):
-        from _numpypy import multiarray as np
+        try:
+            from _numpypy import multiarray as np
+        except ImportError:
+            skip('pypy built without _numpypy')
         module = self.import_module(name='buffer_test')
         get_buffer_info = module.get_buffer_info
         raises(ValueError, get_buffer_info, np.arange(5)[::2], ('SIMPLE',))
@@ -50,3 +61,29 @@
         arr = np.zeros((10, 1), order='C')
         shape, strides = get_buffer_info(arr, ['C_CONTIGUOUS'])
         assert strides[-1] == 8
+        dt1 = np.dtype(
+             [('a', 'b'), ('b', 'i'), 
+              ('sub0', np.dtype('b,i')), 
+              ('sub1', np.dtype('b,i')), 
+              ('sub2', np.dtype('b,i')), 
+              ('sub3', np.dtype('b,i')), 
+              ('sub4', np.dtype('b,i')), 
+              ('sub5', np.dtype('b,i')), 
+              ('sub6', np.dtype('b,i')), 
+              ('sub7', np.dtype('b,i')), 
+              ('c', 'i')],
+             )
+        x = np.arange(dt1.itemsize, dtype='int8').view(dt1)
+        # pytest can catch warnings from v2.8 and up, we ship 2.5
+        import warnings
+        warnings.filterwarnings("error")
+        try:
+            try:
+                y = get_buffer_info(x, ['SIMPLE'])
+            except UserWarning as e:
+                pass
+            else:
+                assert False ,"PyPy-specific UserWarning not raised" \
+                          " on too long format string"
+        finally:
+            warnings.resetwarnings()
diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py
--- a/pypy/module/cpyext/typeobject.py
+++ b/pypy/module/cpyext/typeobject.py
@@ -514,10 +514,13 @@
 @cpython_api([PyObject, Py_ssize_t, rffi.VOIDPP], lltype.Signed,
              header=None, error=-1)
 def bf_getreadbuffer(space, w_buf, segment, ref):
+    from rpython.rlib.buffer import StringBuffer
     if segment != 0:
         raise oefmt(space.w_SystemError,
                     "accessing non-existent segment")
     buf = space.readbuf_w(w_buf)
+    if isinstance(buf, StringBuffer):
+        return str_getreadbuffer(space, w_buf, segment, ref)
     address = buf.get_raw_address()
     ref[0] = address
     return len(buf)
@@ -533,7 +536,6 @@
     if segment != 0:
         raise oefmt(space.w_SystemError,
                     "accessing non-existent segment")
-
     buf = space.writebuf_w(w_buf)
     ref[0] = buf.get_raw_address()
     return len(buf)
@@ -551,6 +553,20 @@
     Py_DecRef(space, pyref)
     return space.len_w(w_str)
 
+ at cpython_api([PyObject, Py_ssize_t, rffi.VOIDPP], lltype.Signed,
+             header=None, error=-1)
+def unicode_getreadbuffer(space, w_str, segment, ref):
+    from pypy.module.cpyext.unicodeobject import (
+                PyUnicode_AS_UNICODE, PyUnicode_GET_DATA_SIZE)
+    if segment != 0:
+        raise oefmt(space.w_SystemError,
+                    "accessing non-existent unicode segment")
+    pyref = make_ref(space, w_str)
+    ref[0] = PyUnicode_AS_UNICODE(space, pyref)
+    # Stolen reference: the object has better exist somewhere else
+    Py_DecRef(space, pyref)
+    return PyUnicode_GET_DATA_SIZE(space, w_str)
+
 @cpython_api([PyObject, Py_ssize_t, rffi.CCHARPP], lltype.Signed,
              header=None, error=-1)
 def str_getcharbuffer(space, w_buf, segment, ref):
@@ -574,8 +590,8 @@
 
 def setup_buffer_procs(space, w_type, pto):
     bufspec = w_type.layout.typedef.buffer
-    if bufspec is None:
-        # not a buffer
+    if bufspec is None and not space.is_w(w_type, space.w_unicode):
+        # not a buffer, but let w_unicode be a read buffer
         return
     c_buf = lltype.malloc(PyBufferProcs, flavor='raw', zero=True)
     lltype.render_immortal(c_buf)
@@ -591,6 +607,13 @@
         c_buf.c_bf_getcharbuffer = llhelper(
             str_getcharbuffer.api_func.functype,
             str_getcharbuffer.api_func.get_wrapper(space))
+    elif space.is_w(w_type, space.w_unicode):
+        # Special case: unicode doesn't support get_raw_address(), so we have a
+        # custom get*buffer that instead gives the address of the char* in the
+        # PyUnicodeObject*!
+        c_buf.c_bf_getreadbuffer = llhelper(
+            unicode_getreadbuffer.api_func.functype,
+            unicode_getreadbuffer.api_func.get_wrapper(space))
     elif space.is_w(w_type, space.w_buffer):
         # Special case: we store a permanent address on the cpyext wrapper,
         # so we'll reuse that.
@@ -706,7 +729,7 @@
     # uninitialized fields:
     # c_tp_print
     # XXX implement
-    # c_tp_compare and the following fields (see http://docs.python.org/c-api/typeobj.html )
+    # c_tp_compare and more?
     w_base = best_base(space, w_type.bases_w)
     pto.c_tp_base = rffi.cast(PyTypeObjectPtr, make_ref(space, w_base))
 
@@ -764,7 +787,6 @@
     return find_best_base(bases_w)
 
 def inherit_slots(space, pto, w_base):
-    # XXX missing: nearly everything
     base_pyo = make_ref(space, w_base)
     try:
         base = rffi.cast(PyTypeObjectPtr, base_pyo)
@@ -783,6 +805,25 @@
             pto.c_tp_getattro = base.c_tp_getattro
         if not pto.c_tp_as_buffer:
             pto.c_tp_as_buffer = base.c_tp_as_buffer
+        if base.c_tp_as_buffer:
+            # inherit base.c_tp_as_buffer functions not inherited from w_type
+            # note: builtin types are handled in setup_buffer_procs
+            pto_as = pto.c_tp_as_buffer
+            base_as = base.c_tp_as_buffer
+            if not pto_as.c_bf_getbuffer:
+                pto_as.c_bf_getbuffer = base_as.c_bf_getbuffer
+            if not pto_as.c_bf_getcharbuffer:
+                pto_as.c_bf_getcharbuffer = base_as.c_bf_getcharbuffer
+            if not pto_as.c_bf_getwritebuffer:
+                pto_as.c_bf_getwritebuffer = base_as.c_bf_getwritebuffer
+            if not pto_as.c_bf_getreadbuffer:
+                pto_as.c_bf_getreadbuffer = base_as.c_bf_getreadbuffer
+            if not pto_as.c_bf_getsegcount:
+                pto_as.c_bf_getsegcount = base_as.c_bf_getsegcount
+            if not pto_as.c_bf_getcharbuffer:
+                pto_as.c_bf_getcharbuffer = base_as.c_bf_getcharbuffer
+            if not pto_as.c_bf_releasebuffer:
+                pto_as.c_bf_releasebuffer = base_as.c_bf_releasebuffer
     finally:
         Py_DecRef(space, base_pyo)
 
@@ -812,13 +853,14 @@
 
     w_obj = space.allocate_instance(W_PyCTypeObject, w_metatype)
     track_reference(space, py_obj, w_obj)
-    w_obj.__init__(space, py_type)
+    # __init__ wraps all slotdefs functions from py_type via add_operators
+    w_obj.__init__(space, py_type) 
     w_obj.ready()
 
     finish_type_2(space, py_type, w_obj)
-    # inheriting tp_as_* slots
     base = py_type.c_tp_base
     if base:
+        # XXX refactor - parts of this are done in finish_type_2 -> inherit_slots
         if not py_type.c_tp_as_number: 
             py_type.c_tp_as_number = base.c_tp_as_number
             py_type.c_tp_flags |= base.c_tp_flags & Py_TPFLAGS_CHECKTYPES
@@ -827,7 +869,7 @@
             py_type.c_tp_as_sequence = base.c_tp_as_sequence
             py_type.c_tp_flags |= base.c_tp_flags & Py_TPFLAGS_HAVE_INPLACEOPS
         if not py_type.c_tp_as_mapping: py_type.c_tp_as_mapping = base.c_tp_as_mapping
-        if not py_type.c_tp_as_buffer: py_type.c_tp_as_buffer = base.c_tp_as_buffer
+        #if not py_type.c_tp_as_buffer: py_type.c_tp_as_buffer = base.c_tp_as_buffer
 
     return w_obj
 
diff --git a/pypy/objspace/std/bufferobject.py b/pypy/objspace/std/bufferobject.py
--- a/pypy/objspace/std/bufferobject.py
+++ b/pypy/objspace/std/bufferobject.py
@@ -32,6 +32,10 @@
     def charbuf_w(self, space):
         return self.buf.as_str()
 
+    def descr_getbuffer(self, space, w_flags):
+        space.check_buf_flags(space.int_w(w_flags), self.buf.readonly)
+        return self
+
     @staticmethod
     @unwrap_spec(offset=int, size=int)
     def descr_new_buffer(space, w_subtype, w_object, offset=0, size=-1):
@@ -160,6 +164,7 @@
     __mul__ = interp2app(W_Buffer.descr_mul),
     __rmul__ = interp2app(W_Buffer.descr_mul),
     __repr__ = interp2app(W_Buffer.descr_repr),
+    __buffer__ = interp2app(W_Buffer.descr_getbuffer),
     _pypy_raw_address = interp2app(W_Buffer.descr_pypy_raw_address),
 )
 W_Buffer.typedef.acceptable_as_base_class = False
diff --git a/pypy/objspace/std/memoryobject.py b/pypy/objspace/std/memoryobject.py
--- a/pypy/objspace/std/memoryobject.py
+++ b/pypy/objspace/std/memoryobject.py
@@ -131,9 +131,13 @@
         return space.newbool(bool(self.buf.readonly))
 
     def w_get_shape(self, space):
+        if self.buf.getndim() == 0:
+            return space.w_None
         return space.newtuple([space.wrap(x) for x in self.buf.getshape()])
 
     def w_get_strides(self, space):
+        if self.buf.getndim() == 0:
+            return space.w_None
         return space.newtuple([space.wrap(x) for x in self.buf.getstrides()])
 
     def w_get_suboffsets(self, space):


More information about the pypy-commit mailing list