Revert Data fields to bytes and add get_data_as_view for zero-copy access (#390)

* get data field with view

* refine tc

* refine based on flake check

* run black again

* rebase upstream master

* add comment to tc

* refine raise exception
This commit is contained in:
Brian Xu
2026-01-16 16:40:22 +11:00
committed by GitHub
parent 9754258d46
commit 9237ba123a
4 changed files with 265 additions and 7 deletions

View File

@@ -65,6 +65,7 @@ cdef class _DynamicStructReader:
cpdef _which_str(self)
cpdef _get_by_field(self, _StructSchemaField field)
cpdef _has_by_field(self, _StructSchemaField field)
cpdef get_data_as_view(self, field)
cpdef as_builder(self, num_first_segment_words=?, allocate_seg_callable=?)
@@ -97,6 +98,7 @@ cdef class _DynamicStructBuilder:
cpdef _which_str(self)
cpdef adopt(self, field, _DynamicOrphan orphan)
cpdef disown(self, field)
cpdef get_data_as_view(self, field)
cpdef as_reader(self)
cpdef copy(self, num_first_segment_words=?, allocate_seg_callable=?)

View File

@@ -15,8 +15,8 @@ from capnp.includes.schema_cpp cimport (MessageReader,)
from builtins import memoryview as BuiltinsMemoryview
from cpython cimport array, Py_buffer, PyObject_CheckBuffer
from cpython.buffer cimport PyBUF_SIMPLE, PyBUF_WRITABLE, PyBUF_WRITE, PyBUF_READ, PyBUF_CONTIG_RO
from cpython.memoryview cimport PyMemoryView_FromMemory
from cpython.buffer cimport PyBUF_SIMPLE, PyBUF_WRITABLE, PyBUF_WRITE, PyBUF_READ, PyBUF_CONTIG_RO, PyBuffer_FillInfo
from cpython.memoryview cimport PyMemoryView_FromMemory, PyMemoryView_FromBuffer
from cpython.bytes cimport PyBytes_FromStringAndSize
from cpython.exc cimport PyErr_Clear
from cython.operator cimport dereference as deref
@@ -669,7 +669,7 @@ cdef to_python_reader(C_DynamicValue.Reader self, object parent):
return (<char*>temp_text.begin())[:temp_text.size()]
elif type == capnp.TYPE_DATA:
temp_data = self.asData()
return PyMemoryView_FromMemory(<char *> temp_data.begin(), temp_data.size(), PyBUF_READ)
return <bytes>((<char*>temp_data.begin())[:temp_data.size()])
elif type == capnp.TYPE_LIST:
return _DynamicListReader()._init(self.asList(), parent)
elif type == capnp.TYPE_STRUCT:
@@ -703,7 +703,7 @@ cdef to_python_builder(C_DynamicValue.Builder self, object parent):
return (<char*>temp_text.begin())[:temp_text.size()]
elif type == capnp.TYPE_DATA:
temp_data = self.asData()
return PyMemoryView_FromMemory(<char *> temp_data.begin(), temp_data.size(), PyBUF_WRITE)
return <bytes>((<char*>temp_data.begin())[:temp_data.size()])
elif type == capnp.TYPE_LIST:
return _DynamicListBuilder()._init(self.asList(), parent)
elif type == capnp.TYPE_STRUCT:
@@ -1226,6 +1226,29 @@ cdef class _DynamicStructReader:
cpdef _has_by_field(self, _StructSchemaField field):
return self.thisptr.hasByField(field.thisptr)
cpdef get_data_as_view(self, field):
"""
Efficiently get a read-only memoryview for a DATA field without copying.
"""
cdef C_DynamicValue.Reader val
cdef capnp.Data.Reader temp_data
try:
val = self.thisptr.get(field)
except KjException as e:
raise e._to_python() from None
if val.getType() != capnp.TYPE_DATA:
raise TypeError("Field '{}' is not a DATA field".format(field))
temp_data = val.asData()
# Return read-only memoryview
cdef Py_buffer buf
if PyBuffer_FillInfo(&buf, self, <void*>temp_data.begin(), temp_data.size(), 1, PyBUF_CONTIG_RO) < 0:
raise KjException("Failed to create buffer info")
return PyMemoryView_FromBuffer(&buf)
cpdef _which_str(self):
try:
return <char *>helpers.fixMaybe(self.thisptr.which()).getProto().getName().cStr()
@@ -1628,6 +1651,32 @@ cdef class _DynamicStructBuilder:
"""
return _DynamicOrphan()._init(self.thisptr.disown(field), self._parent)
cpdef get_data_as_view(self, field):
"""
Efficiently get a writable memoryview for a DATA field without copying.
This allows in-place modification of the underlying buffer:
msg.get_data_as_view('myField')[0] = 0xFF
"""
cdef C_DynamicValue.Builder val
cdef capnp.Data.Builder temp_data
try:
val = self.thisptr.get(field)
except KjException as e:
raise e._to_python() from None
if val.getType() != capnp.TYPE_DATA:
raise TypeError("Field '{}' is not a DATA field".format(field))
temp_data = val.asData()
# Return writable memoryview
cdef Py_buffer buf
if PyBuffer_FillInfo(&buf, self, <void*>temp_data.begin(), temp_data.size(), 0, PyBUF_WRITABLE) < 0:
raise KjException("Failed to create buffer info")
return PyMemoryView_FromBuffer(&buf)
cpdef as_reader(self):
"""A method for casting this Builder to a Reader