@@ -13,8 +13,9 @@ from capnp.helpers.helpers cimport init_capnp_api
13
13
from capnp.includes.capnp_cpp cimport AsyncIoStream, WaitScope, PyPromise, VoidPromise, EventPort, EventLoop, PyAsyncIoStream, PromiseFulfiller, VoidPromiseFulfiller, tryReadMessage, writeMessage, makeException, PythonInterfaceDynamicImpl
14
14
from capnp.includes.schema_cpp cimport (MessageReader,)
15
15
16
+ from builtins import memoryview as BuiltinsMemoryview
16
17
from cpython cimport array, Py_buffer, PyObject_CheckBuffer
17
- from cpython.buffer cimport PyBUF_SIMPLE, PyBUF_WRITABLE, PyBUF_WRITE, PyBUF_READ
18
+ from cpython.buffer cimport PyBUF_SIMPLE, PyBUF_WRITABLE, PyBUF_WRITE, PyBUF_READ, PyBUF_CONTIG_RO
18
19
from cpython.memoryview cimport PyMemoryView_FromMemory
19
20
from cpython.exc cimport PyErr_Clear
20
21
from cython.operator cimport dereference as deref
@@ -667,7 +668,7 @@ cdef to_python_reader(C_DynamicValue.Reader self, object parent):
667
668
return (< char * > temp_text.begin())[:temp_text.size()]
668
669
elif type == capnp.TYPE_DATA:
669
670
temp_data = self .asData()
670
- return < bytes > (( < char * > temp_data.begin())[: temp_data.size()] )
671
+ return PyMemoryView_FromMemory( < char * > temp_data.begin(), temp_data.size(), PyBUF_READ )
671
672
elif type == capnp.TYPE_LIST:
672
673
return _DynamicListReader()._init(self .asList(), parent)
673
674
elif type == capnp.TYPE_STRUCT:
@@ -701,7 +702,7 @@ cdef to_python_builder(C_DynamicValue.Builder self, object parent):
701
702
return (< char * > temp_text.begin())[:temp_text.size()]
702
703
elif type == capnp.TYPE_DATA:
703
704
temp_data = self .asData()
704
- return < bytes > (( < char * > temp_data.begin())[: temp_data.size()] )
705
+ return PyMemoryView_FromMemory( < char * > temp_data.begin(), temp_data.size(), PyBUF_WRITE )
705
706
elif type == capnp.TYPE_LIST:
706
707
return _DynamicListBuilder()._init(self .asList(), parent)
707
708
elif type == capnp.TYPE_STRUCT:
@@ -766,6 +767,20 @@ cdef _setBytes(_DynamicSetterClasses thisptr, field, value):
766
767
cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(temp_string)
767
768
thisptr.set(field, temp)
768
769
770
+ cdef _setMemoryview(_DynamicSetterClasses thisptr, field, value):
771
+ cdef Py_buffer buf
772
+ cdef capnp.StringPtr temp_string
773
+ cdef C_DynamicValue.Reader temp
774
+ if PyObject_GetBuffer(value, & buf, PyBUF_CONTIG_RO) != 0 :
775
+ raise KjException(
776
+ " cannot get buffer from memory view, for field '{}'" .format(field)
777
+ )
778
+ try :
779
+ temp_string = capnp.StringPtr(< char * > buf.buf, buf.len)
780
+ temp = C_DynamicValue.Reader(temp_string)
781
+ thisptr.set(field, temp)
782
+ finally :
783
+ PyBuffer_Release(& buf)
769
784
770
785
cdef _setBaseString(_DynamicSetterClasses thisptr, field, value):
771
786
encoded_value = value.encode(' utf-8' )
@@ -779,6 +794,20 @@ cdef _setBytesField(DynamicStruct_Builder thisptr, _StructSchemaField field, val
779
794
cdef C_DynamicValue.Reader temp = C_DynamicValue.Reader(temp_string)
780
795
thisptr.setByField(field.thisptr, temp)
781
796
797
+ cdef _setMemoryviewField(DynamicStruct_Builder thisptr, _StructSchemaField field, value):
798
+ cdef Py_buffer buf
799
+ cdef capnp.StringPtr temp_string
800
+ cdef C_DynamicValue.Reader temp
801
+ if PyObject_GetBuffer(value, & buf, PyBUF_CONTIG_RO) != 0 :
802
+ raise KjException(
803
+ " cannot get buffer from memory view, for field '{}'" .format(field)
804
+ )
805
+ try :
806
+ temp_string = capnp.StringPtr(< char * > buf.buf, buf.len)
807
+ temp = C_DynamicValue.Reader(temp_string)
808
+ thisptr.setByField(field.thisptr, temp)
809
+ finally :
810
+ PyBuffer_Release(& buf)
782
811
783
812
cdef _setBaseStringField(DynamicStruct_Builder thisptr, _StructSchemaField field, value):
784
813
encoded_value = value.encode(' utf-8' )
@@ -805,6 +834,8 @@ cdef _setDynamicField(_DynamicSetterClasses thisptr, field, value, parent):
805
834
thisptr.set(field, temp)
806
835
elif value_type is bytes:
807
836
_setBytes(thisptr, field, value)
837
+ elif isinstance (value, BuiltinsMemoryview):
838
+ _setMemoryview(thisptr, field, value)
808
839
elif isinstance (value, basestring ):
809
840
_setBaseString(thisptr, field, value)
810
841
elif value_type is list :
@@ -870,6 +901,8 @@ cdef _setDynamicFieldWithField(DynamicStruct_Builder thisptr, _StructSchemaField
870
901
thisptr.setByField(field.thisptr, temp)
871
902
elif value_type is bytes:
872
903
_setBytesField(thisptr, field, value)
904
+ elif isinstance (value, BuiltinsMemoryview):
905
+ _setMemoryviewField(thisptr, field, value)
873
906
elif isinstance (value, basestring ):
874
907
_setBaseStringField(thisptr, field, value)
875
908
elif value_type is list :
@@ -1242,19 +1275,28 @@ cdef class _DynamicStructReader:
1242
1275
def to_dict (self , verbose = False , ordered = False , encode_bytes_as_base64 = False ):
1243
1276
return _to_dict(self , verbose, ordered, encode_bytes_as_base64)
1244
1277
1245
- cpdef as_builder(self , num_first_segment_words = None ):
1278
+ cpdef as_builder(self , num_first_segment_words = None , allocate_seg_callable = None ):
1246
1279
""" A method for casting this Reader to a Builder
1247
1280
1248
1281
This is a copying operation with respect to the message's buffer.
1249
1282
Changes in the new builder will not reflect in the original reader.
1250
1283
1251
1284
:type num_first_segment_words: int
1252
1285
:param num_first_segment_words: Size of the first segment to allocate (in words ie. 8 byte increments)
1286
+
1287
+ :type allocate_seg_callable: Callable[[int], bytearray]
1288
+ :param allocate_seg_callable: A python callable object that takes the minimum number of 8-byte
1289
+ words to allocate (as an `int`) and returns a `bytearray`. This is used to customize the memory
1290
+ allocation strategy.
1253
1291
1254
1292
:rtype: :class:`_DynamicStructBuilder`
1255
1293
"""
1256
- builder = _MallocMessageBuilder(num_first_segment_words)
1257
- return builder.set_root(self )
1294
+ if allocate_seg_callable is None :
1295
+ builder = _MallocMessageBuilder(num_first_segment_words)
1296
+ return builder.set_root(self )
1297
+ else :
1298
+ builder = _PyCustomMessageBuilder(allocate_seg_callable, num_first_segment_words)
1299
+ return builder.set_root(self )
1258
1300
1259
1301
property total_size :
1260
1302
def __get__ (self ):
@@ -1593,19 +1635,28 @@ cdef class _DynamicStructBuilder:
1593
1635
reader._obj_to_pin = self
1594
1636
return reader
1595
1637
1596
- cpdef copy(self , num_first_segment_words = None ):
1638
+ cpdef copy(self , num_first_segment_words = None , allocate_seg_callable = None ):
1597
1639
""" A method for copying this Builder
1598
1640
1599
1641
This is a copying operation with respect to the message's buffer.
1600
1642
Changes in the new builder will not reflect in the original reader.
1601
1643
1602
1644
:type num_first_segment_words: int
1603
1645
:param num_first_segment_words: Size of the first segment to allocate (in words ie. 8 byte increments)
1646
+
1647
+ :type allocate_seg_callable: Callable[[int], bytearray]
1648
+ :param allocate_seg_callable: A python callable object that takes the minimum number of 8-byte
1649
+ words to allocate (as an `int`) and returns a `bytearray`. This is used to customize the memory
1650
+ allocation strategy.
1604
1651
1605
1652
:rtype: :class:`_DynamicStructBuilder`
1606
1653
"""
1607
- builder = _MallocMessageBuilder(num_first_segment_words)
1608
- return builder.set_root(self )
1654
+ if allocate_seg_callable is None :
1655
+ builder = _MallocMessageBuilder(num_first_segment_words)
1656
+ return builder.set_root(self )
1657
+ else :
1658
+ builder = _PyCustomMessageBuilder(allocate_seg_callable, num_first_segment_words)
1659
+ return builder.set_root(self )
1609
1660
1610
1661
property schema :
1611
1662
""" A property that returns the _StructSchema object matching this writer"""
@@ -3145,8 +3196,12 @@ class _StructABCMeta(type):
3145
3196
return isinstance (obj, cls .__base__) and obj.schema == cls ._schema
3146
3197
3147
3198
3148
- cdef _new_message(self , kwargs, num_first_segment_words):
3149
- builder = _MallocMessageBuilder(num_first_segment_words)
3199
+ cdef _new_message(self , kwargs, num_first_segment_words, allocate_seg_callable):
3200
+ cdef _MessageBuilder builder
3201
+ if allocate_seg_callable is None :
3202
+ builder = _MallocMessageBuilder(num_first_segment_words)
3203
+ else :
3204
+ builder = _PyCustomMessageBuilder(allocate_seg_callable, num_first_segment_words)
3150
3205
msg = builder.init_root(self .schema)
3151
3206
if kwargs is not None :
3152
3207
msg.from_dict(kwargs)
@@ -3387,12 +3442,17 @@ class _StructModule(object):
3387
3442
def __call__ (self , num_first_segment_words = None , **kwargs ):
3388
3443
return self .new_message(num_first_segment_words = num_first_segment_words, ** kwargs)
3389
3444
3390
- def new_message (self , num_first_segment_words = None , **kwargs ):
3445
+ def new_message (self , num_first_segment_words = None , allocate_seg_callable = None , **kwargs ):
3391
3446
""" Returns a newly allocated builder message.
3392
3447
3393
3448
:type num_first_segment_words: int
3394
3449
:param num_first_segment_words: Size of the first segment to allocate (in words ie. 8 byte increments)
3395
3450
3451
+ :type allocate_seg_callable: Callable[[int], bytearray]
3452
+ :param allocate_seg_callable: A python callable object that takes the minimum number of 8-byte
3453
+ words to allocate (as an `int`) and returns a `bytearray`. This is used to customize the memory
3454
+ allocation strategy.
3455
+
3396
3456
:type kwargs: dict
3397
3457
:param kwargs: A list of fields and their values to initialize in the struct.
3398
3458
@@ -3401,7 +3461,7 @@ class _StructModule(object):
3401
3461
3402
3462
:rtype: :class:`_DynamicStructBuilder`
3403
3463
"""
3404
- return _new_message(self , kwargs, num_first_segment_words)
3464
+ return _new_message(self , kwargs, num_first_segment_words, allocate_seg_callable )
3405
3465
3406
3466
3407
3467
class _InterfaceModule (object ):
@@ -3758,6 +3818,50 @@ cdef class _MallocMessageBuilder(_MessageBuilder):
3758
3818
self .thisptr = new schema_cpp.MallocMessageBuilder(size)
3759
3819
3760
3820
3821
+ cdef class _PyCustomMessageBuilder(_MessageBuilder):
3822
+ """ The class for building Cap'n Proto messages,
3823
+ with customised memory allocation strategy
3824
+
3825
+ You will use this class if you want to customise the allocateSegment method,
3826
+ and define your own memory allocation strategy.
3827
+ """
3828
+ def __init__ (self , allocate_seg_callable , size = None ):
3829
+ """ The constructor requires you to provide a Python callable object as a parameter.
3830
+ This callable object will be invoked in the allocateSegment method of the MessageBuilder
3831
+ to allocate memory. The allocated memory will be managed within the MessageBuilder.
3832
+
3833
+ :type allocate_seg_callable: Callable[[int], bytearray]
3834
+ :param allocate_seg_callable: A python callable object that takes the minimum number of 8-byte
3835
+ words to allocate (as an `int`) and returns a `bytearray`. This is used to customize the memory
3836
+ allocation strategy.
3837
+
3838
+ Required function signature is like this:
3839
+ def __call__(self, minimum_size: int) -> bytearray:
3840
+ Note that the unit of minimum_size is words, ie. 8 byte increments.
3841
+
3842
+ class Allocator:
3843
+ def __init__(self):
3844
+ self.cur_size = 0
3845
+ def __call__(self, minimum_size: int) -> bytearray:
3846
+ size = max(minimum_size, self.cur_size)
3847
+ self.cur_size += size
3848
+ WORD_SIZE = 8
3849
+ byte_count = size * WORD_SIZE
3850
+ return bytearray(byte_count)
3851
+
3852
+ addressbook = capnp.load('addressbook.capnp')
3853
+ message = capnp._PyCustomMessageBuilder(allocator)
3854
+ person = message.init_root(addressbook.Person)
3855
+
3856
+ :type size: int
3857
+ :param size: Size of the first segment to allocate (in words ie. 8 byte increments)
3858
+ """
3859
+ if size is None :
3860
+ self .thisptr = new schema_cpp.PyCustomMessageBuilder(< PyObject* > allocate_seg_callable)
3861
+ else :
3862
+ self .thisptr = new schema_cpp.PyCustomMessageBuilder(< PyObject* > allocate_seg_callable, size)
3863
+
3864
+
3761
3865
cdef class _MessageReader:
3762
3866
""" An abstract base class for reading Cap'n Proto messages
3763
3867
0 commit comments