PyCyBase 0.1.8.post4__cp313-cp313-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. cbase/__infra__.pxd +6 -0
  2. cbase/__init__.pxd +6 -0
  3. cbase/__init__.py +28 -0
  4. cbase/allocator_protocol/__infra__.pxd +58 -0
  5. cbase/allocator_protocol/__init__.pxd +58 -0
  6. cbase/allocator_protocol/__init__.py +45 -0
  7. cbase/allocator_protocol/c_allocator_protocol.c +31818 -0
  8. cbase/allocator_protocol/c_allocator_protocol.cp313-win_amd64.pyd +0 -0
  9. cbase/allocator_protocol/c_allocator_protocol.h +352 -0
  10. cbase/allocator_protocol/c_allocator_protocol.pxd +64 -0
  11. cbase/allocator_protocol/c_allocator_protocol.pyi +144 -0
  12. cbase/allocator_protocol/c_allocator_protocol.pyx +209 -0
  13. cbase/allocator_protocol/c_heap_allocator.c +38144 -0
  14. cbase/allocator_protocol/c_heap_allocator.cp313-win_amd64.pyd +0 -0
  15. cbase/allocator_protocol/c_heap_allocator.h +444 -0
  16. cbase/allocator_protocol/c_heap_allocator.pxd +97 -0
  17. cbase/allocator_protocol/c_heap_allocator.pyi +161 -0
  18. cbase/allocator_protocol/c_heap_allocator.pyx +300 -0
  19. cbase/allocator_protocol/c_nt_shm_allocator.c +33399 -0
  20. cbase/allocator_protocol/c_nt_shm_allocator.cp313-win_amd64.pyd +0 -0
  21. cbase/allocator_protocol/c_nt_shm_allocator.h +996 -0
  22. cbase/allocator_protocol/c_nt_shm_allocator.pxd +102 -0
  23. cbase/allocator_protocol/c_nt_shm_allocator.pyx +223 -0
  24. cbase/allocator_protocol/c_shm_allocator.h +1168 -0
  25. cbase/allocator_protocol/c_shm_allocator.pxd +142 -0
  26. cbase/allocator_protocol/c_shm_allocator.pyi +299 -0
  27. cbase/allocator_protocol/c_shm_allocator.pyx +442 -0
  28. cbase/allocator_protocol/c_shm_comp.c +4643 -0
  29. cbase/allocator_protocol/c_shm_comp.cp313-win_amd64.pyd +0 -0
  30. cbase/allocator_protocol/c_shm_comp.h +49 -0
  31. cbase/allocator_protocol/c_shm_comp.pxd +38 -0
  32. cbase/allocator_protocol/c_shm_comp.pyx +1 -0
  33. cbase/backports/__infra__.pxd +17 -0
  34. cbase/backports/__init__.pxd +17 -0
  35. cbase/backports/__init__.py +9 -0
  36. cbase/backports/pydict.h +55 -0
  37. cbase/backports/pydict.pxd +5 -0
  38. cbase/backports/pylong.c +9946 -0
  39. cbase/backports/pylong.cp313-win_amd64.pyd +0 -0
  40. cbase/backports/pylong.h +79 -0
  41. cbase/backports/pylong.pxd +23 -0
  42. cbase/backports/pylong.pyx +61 -0
  43. cbase/bytemap/__infra__.pxd +42 -0
  44. cbase/bytemap/__init__.pxd +42 -0
  45. cbase/bytemap/__init__.py +21 -0
  46. cbase/bytemap/c_bytemap.c +52076 -0
  47. cbase/bytemap/c_bytemap.cp313-win_amd64.pyd +0 -0
  48. cbase/bytemap/c_bytemap.h +867 -0
  49. cbase/bytemap/c_bytemap.pxd +370 -0
  50. cbase/bytemap/c_bytemap.pyx +1758 -0
  51. cbase/bytemap/xxh3.h +55 -0
  52. cbase/bytemap/xxhash.c +42 -0
  53. cbase/bytemap/xxhash.h +7487 -0
  54. cbase/env.c +11109 -0
  55. cbase/env.cp313-win_amd64.pyd +0 -0
  56. cbase/env.pxd +8 -0
  57. cbase/env.pyi +48 -0
  58. cbase/env.pyx +46 -0
  59. cbase/includes/cbase/allocator_protocol/c_allocator_protocol.c +31818 -0
  60. cbase/includes/cbase/allocator_protocol/c_allocator_protocol.h +352 -0
  61. cbase/includes/cbase/allocator_protocol/c_heap_allocator.c +38144 -0
  62. cbase/includes/cbase/allocator_protocol/c_heap_allocator.h +444 -0
  63. cbase/includes/cbase/allocator_protocol/c_nt_shm_allocator.c +33399 -0
  64. cbase/includes/cbase/allocator_protocol/c_nt_shm_allocator.h +996 -0
  65. cbase/includes/cbase/allocator_protocol/c_shm_allocator.h +1168 -0
  66. cbase/includes/cbase/allocator_protocol/c_shm_comp.c +4643 -0
  67. cbase/includes/cbase/allocator_protocol/c_shm_comp.h +49 -0
  68. cbase/includes/cbase/backports/pydict.h +55 -0
  69. cbase/includes/cbase/backports/pylong.c +9946 -0
  70. cbase/includes/cbase/backports/pylong.h +79 -0
  71. cbase/includes/cbase/bytemap/c_bytemap.c +52076 -0
  72. cbase/includes/cbase/bytemap/c_bytemap.h +867 -0
  73. cbase/includes/cbase/bytemap/xxh3.h +55 -0
  74. cbase/includes/cbase/bytemap/xxhash.c +42 -0
  75. cbase/includes/cbase/bytemap/xxhash.h +7487 -0
  76. cbase/includes/cbase/env.c +11109 -0
  77. cbase/includes/cbase/int128.h +81 -0
  78. cbase/includes/cbase/intern_string/c_intern_string.c +28081 -0
  79. cbase/includes/cbase/intern_string/c_intern_string.h +614 -0
  80. cbase/includes/cbase/nt/pthread_nt_compat.h +102 -0
  81. cbase/includes/cbase/nt/regex_nt_compat.h +133 -0
  82. cbase/includes/cbase/nt/time_sched_nt_compat.h +32 -0
  83. cbase/int128.h +81 -0
  84. cbase/int128.pxd +15 -0
  85. cbase/intern_string/BENCHMARK.md +157 -0
  86. cbase/intern_string/__infra__.pxd +11 -0
  87. cbase/intern_string/__init__.pxd +11 -0
  88. cbase/intern_string/__init__.py +19 -0
  89. cbase/intern_string/c_intern_string.c +28081 -0
  90. cbase/intern_string/c_intern_string.cp313-win_amd64.pyd +0 -0
  91. cbase/intern_string/c_intern_string.h +614 -0
  92. cbase/intern_string/c_intern_string.pxd +118 -0
  93. cbase/intern_string/c_intern_string.pyi +376 -0
  94. cbase/intern_string/c_intern_string.pyx +765 -0
  95. cbase/nt/pthread_nt_compat.h +102 -0
  96. cbase/nt/regex_nt_compat.h +133 -0
  97. cbase/nt/time_sched_nt_compat.h +32 -0
  98. pycybase-0.1.8.post4.dist-info/METADATA +180 -0
  99. pycybase-0.1.8.post4.dist-info/RECORD +101 -0
  100. pycybase-0.1.8.post4.dist-info/WHEEL +5 -0
  101. pycybase-0.1.8.post4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1758 @@
1
+ from time import perf_counter
2
+
3
+ from cpython.bytes cimport PyBytes_AsString, PyBytes_FromStringAndSize, PyBytes_GET_SIZE
4
+ from cpython.ref cimport Py_XINCREF
5
+ from cpython.unicode cimport PyUnicode_AsUTF8AndSize, PyUnicode_FromStringAndSize
6
+ from libc.math cimport NAN
7
+ from libc.stdlib cimport calloc, free
8
+ from libc.string cimport memset
9
+
10
+ from cbase.allocator_protocol.c_allocator_protocol cimport AP_DEFAULT_ALLOCATOR
11
+ from cbase.backports.pydict cimport BP_PyDict_Pop
12
+
13
+
14
+ cdef object NO_DEFAULT = object()
15
+ cdef bytes NO_DEFAULT_BYTES = b''
16
+
17
+
18
+ # ============================================================
19
+ # _ByteMapBase — common base for all ByteMap variants
20
+ # ============================================================
21
+
22
+ cdef class _ByteMapBase:
23
+ def __cinit__(self):
24
+ self.header = NULL
25
+ self.owner = False
26
+ self.seq_id = 0
27
+
28
+ def __dealloc__(self):
29
+ if not self.owner:
30
+ return
31
+ if self.header:
32
+ c_bytemap_ex_free(self.header)
33
+
34
+ cdef void _init_header(self, size_t slot_capacity, size_t init_capacity):
35
+ if slot_capacity == init_capacity == 0:
36
+ return
37
+ self.owner = True
38
+ self.header = c_bytemap_ex_new(init_capacity, slot_capacity, AP_DEFAULT_ALLOCATOR)
39
+ if self.header == NULL:
40
+ raise MemoryError(f'Failed to allocate memory for <{self.__class__.__name__}>.')
41
+ self.seq_id = c_bytemap_gen_seq_id(<void*> self)
42
+
43
+ cdef void c_clear(self):
44
+ c_bytemap_ex_clear(self.header)
45
+
46
+ cdef void c_rehash(self, size_t new_capacity):
47
+ cdef int ret_code = c_bytemap_ex_rehash(self.header, new_capacity, self.seq_id)
48
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
49
+ return
50
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_OOM:
51
+ raise MemoryError('Out of memory')
52
+ else:
53
+ raise RuntimeError(f'c_bytemap_ex_rehash failed with err code: {ret_code}')
54
+
55
+ # === Shared Python Interfaces ===
56
+
57
+ def __len__(self):
58
+ return c_bytemap_ex_len(self.header)
59
+
60
+ def clear(self):
61
+ self.c_clear()
62
+
63
+ property size:
64
+ def __get__(self):
65
+ if self.header:
66
+ return self.header.size
67
+ return 0
68
+
69
+ property occupied:
70
+ def __get__(self):
71
+ if self.header:
72
+ return self.header.occupied
73
+ return 0
74
+
75
+ property capacity:
76
+ def __get__(self):
77
+ if self.header:
78
+ return self.header.capacity
79
+ return 0
80
+
81
+ property salt:
82
+ def __get__(self):
83
+ if self.header:
84
+ return self.header.salt
85
+ return 0
86
+
87
+ property slot_capacity:
88
+ def __get__(self):
89
+ if self.header:
90
+ return self.header.slot_capacity
91
+ return 0
92
+
93
+
94
+ # ============================================================
95
+ # ByteMapEx — str → bytes mapping
96
+ # ============================================================
97
+
98
+ cdef class ByteMapEx(_ByteMapBase):
99
+ def __init__(self, size_t slot_capacity, size_t init_capacity=DEFAULT_BYTEMAP_CAPACITY):
100
+ self._init_header(slot_capacity, init_capacity)
101
+
102
+ @staticmethod
103
+ cdef inline ByteMapEx c_from_header(bytemap* header, bint owner):
104
+ cdef ByteMapEx instance = ByteMapEx.__new__(ByteMapEx)
105
+ instance.header = header
106
+ instance.owner = owner
107
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
108
+ return instance
109
+
110
+ @staticmethod
111
+ cdef inline const char* c_key_to_string(str key, size_t* key_len):
112
+ cdef Py_ssize_t out_len = 0
113
+ cdef const char* out_ptr = PyUnicode_AsUTF8AndSize(key, &out_len)
114
+ if key_len:
115
+ key_len[0] = out_len
116
+ return out_ptr
117
+
118
+ @staticmethod
119
+ cdef inline const char* c_value_to_string(bytes value, size_t* value_len):
120
+ cdef Py_ssize_t out_len = PyBytes_GET_SIZE(value)
121
+ cdef const char* out_ptr = <const char*> value
122
+ if value_len:
123
+ value_len[0] = out_len
124
+ return out_ptr
125
+
126
+ cdef bytes c_get(self, str key):
127
+ cdef size_t c_str_len = 0
128
+ cdef const char* c_str = ByteMapEx.c_key_to_string(key, &c_str_len)
129
+ cdef size_t out_len = 0
130
+ cdef char* out = NULL
131
+ cdef int ret_code = c_bytemap_ex_get_ptr(self.header, c_str, c_str_len, &out, &out_len)
132
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
133
+ return PyBytes_FromStringAndSize(out, out_len)
134
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
135
+ raise ValueError('Uninitialized bytemap table')
136
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
137
+ raise TypeError(f'Invalid key {key}')
138
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
139
+ raise KeyError(key)
140
+ else:
141
+ raise RuntimeError(f'c_bytemap_ex_get_ptr failed with err code: {ret_code}')
142
+
143
+ cdef void c_set(self, str key, bytes value):
144
+ cdef size_t key_len = 0
145
+ cdef const char* key_ptr = ByteMapEx.c_key_to_string(key, &key_len)
146
+ cdef size_t value_len = 0
147
+ cdef const char* value_ptr = ByteMapEx.c_value_to_string(value, &value_len)
148
+ cdef int ret_code = c_bytemap_ex_set(self.header, key_ptr, key_len, value_ptr, value_len, self.seq_id, NULL)
149
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
150
+ return
151
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
152
+ raise ValueError('Uninitialized bytemap table')
153
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
154
+ raise TypeError(f'Invalid key {key}')
155
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_FULL:
156
+ raise MemoryError('Mapping is full')
157
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_OOM:
158
+ raise MemoryError('Out of memory')
159
+ else:
160
+ raise RuntimeError(f'c_bytemap_ex_set failed with err code: {ret_code}')
161
+
162
+ cdef bytes c_pop(self, str key):
163
+ cdef size_t c_str_len = 0
164
+ cdef const char* c_str = ByteMapEx.c_key_to_string(key, &c_str_len)
165
+ cdef size_t out_len = 0
166
+ cdef char* out = NULL
167
+ cdef int ret_code = c_bytemap_ex_pop_ptr(self.header, c_str, c_str_len, self.seq_id, &out, &out_len)
168
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
169
+ return PyBytes_FromStringAndSize(out, out_len)
170
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
171
+ raise ValueError('Uninitialized bytemap table')
172
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
173
+ raise TypeError(f'Invalid key {key}')
174
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
175
+ raise KeyError(key)
176
+ else:
177
+ raise RuntimeError(f'c_bytemap_ex_pop failed with err code: {ret_code}')
178
+
179
+ cdef bint c_contains(self, str key):
180
+ cdef size_t c_str_len = 0
181
+ cdef const char* c_str = ByteMapEx.c_key_to_string(key, &c_str_len)
182
+ cdef int ret_code = c_bytemap_ex_contains(self.header, c_str, c_str_len)
183
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
184
+ return True
185
+ return False
186
+
187
+ # === Python Interfaces ===
188
+
189
+ def __contains__(self, str key):
190
+ return self.c_contains(key)
191
+
192
+ def __getitem__(self, str key):
193
+ return self.c_get(key)
194
+
195
+ def __setitem__(self, str key, bytes value):
196
+ self.c_set(key, value)
197
+
198
+ def __repr__(self):
199
+ if self.header:
200
+ return (f"<{self.__class__.__name__} {<uintptr_t> self.header:#0x}>("
201
+ f"size={self.header.size}, "
202
+ f"slot_capacity={self.header.slot_capacity}, "
203
+ f"occupied={self.header.occupied}, "
204
+ f"capacity={self.header.capacity})")
205
+ return f"<{self.__class__.__name__} Unbound>"
206
+
207
+ def __copy__(self):
208
+ cdef bytemap* cloned = c_bytemap_ex_clone(self.header, AP_DEFAULT_ALLOCATOR)
209
+ if cloned == NULL:
210
+ raise MemoryError('Failed to clone ByteMapEx')
211
+ return ByteMapEx.c_from_header(cloned, True)
212
+
213
+ def __deepcopy__(self, memo):
214
+ return self.__copy__()
215
+
216
+ def __iter__(self):
217
+ cdef bytemap_entry* entry = self.header.first if self.header else NULL
218
+ while entry:
219
+ yield PyUnicode_FromStringAndSize(entry.key, entry.key_length)
220
+ entry = entry.next
221
+
222
+ def get(self, str key, bytes default=None, *):
223
+ cdef size_t c_str_len = 0
224
+ cdef const char* c_str = ByteMapEx.c_key_to_string(key, &c_str_len)
225
+ cdef size_t out_len = 0
226
+ cdef char* out = NULL
227
+ cdef int ret_code = c_bytemap_ex_get_ptr(self.header, c_str, c_str_len, &out, &out_len)
228
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
229
+ return PyBytes_FromStringAndSize(out, out_len)
230
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
231
+ raise ValueError('Uninitialized bytemap table')
232
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
233
+ raise TypeError(f'Invalid key {key}')
234
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
235
+ return default
236
+ else:
237
+ raise RuntimeError(f'c_bytemap_ex_get_ptr failed with err code: {ret_code}')
238
+
239
+ def set(self, str key, bytes value):
240
+ return self.c_set(key, value)
241
+
242
+ def pop(self, str key, bytes default=NO_DEFAULT_BYTES, *):
243
+ cdef size_t c_str_len = 0
244
+ cdef const char* c_str = ByteMapEx.c_key_to_string(key, &c_str_len)
245
+ cdef size_t out_len = 0
246
+ cdef char* out = NULL
247
+ cdef int ret_code = c_bytemap_ex_pop_ptr(self.header, c_str, c_str_len, self.seq_id, &out, &out_len)
248
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
249
+ return PyBytes_FromStringAndSize(out, out_len)
250
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
251
+ raise ValueError('Uninitialized bytemap table')
252
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
253
+ raise TypeError(f'Invalid key {key}')
254
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
255
+ if default is NO_DEFAULT_BYTES:
256
+ raise KeyError(key)
257
+ else:
258
+ return default
259
+ else:
260
+ raise RuntimeError(f'c_bytemap_ex_pop failed with err code: {ret_code}')
261
+
262
+ def contains(self, str key):
263
+ return self.c_contains(key)
264
+
265
+ def keys(self):
266
+ cdef bytemap_entry* entry = self.header.first if self.header else NULL
267
+ while entry:
268
+ yield PyUnicode_FromStringAndSize(entry.key, entry.key_length)
269
+ entry = entry.next
270
+
271
+ def values(self):
272
+ cdef bytemap_entry* entry = self.header.first if self.header else NULL
273
+ while entry:
274
+ yield PyBytes_FromStringAndSize(entry.value, entry.value_length)
275
+ entry = entry.next
276
+
277
+ def items(self):
278
+ cdef bytemap_entry* entry = self.header.first if self.header else NULL
279
+ while entry:
280
+ yield (PyUnicode_FromStringAndSize(entry.key, entry.key_length),
281
+ PyBytes_FromStringAndSize(entry.value, entry.value_length))
282
+ entry = entry.next
283
+
284
+ def fork(self):
285
+ return ByteMapEx.c_from_header(self.header, False)
286
+
287
+ property as_dict:
288
+ def __get__(self):
289
+ cdef out = {}
290
+ if not self.header:
291
+ return out
292
+ cdef bytemap_entry* entry = self.header.first
293
+ cdef str key
294
+ cdef bytes value
295
+ while entry:
296
+ key = PyUnicode_FromStringAndSize(entry.key, entry.key_length)
297
+ value = PyBytes_FromStringAndSize(entry.value, entry.value_length)
298
+ out[key] = value
299
+ entry = entry.next
300
+ return out
301
+
302
+
303
+ # ============================================================
304
+ # ByteMapExDouble — str → double mapping
305
+ # ============================================================
306
+
307
+ cdef class ByteMapExDouble(_ByteMapBase):
308
+ def __init__(self, size_t init_capacity=DEFAULT_BYTEMAP_CAPACITY):
309
+ self._init_header(sizeof(double), init_capacity)
310
+
311
+ cdef double c_get_double(self, str key):
312
+ cdef size_t c_str_len = 0
313
+ cdef const char* c_str = ByteMapEx.c_key_to_string(key, &c_str_len)
314
+ cdef double out
315
+ cdef int ret_code = c_bytemap_ex_get_double(self.header, c_str, c_str_len, &out)
316
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
317
+ return out
318
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
319
+ raise ValueError('Uninitialized bytemap table')
320
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
321
+ raise TypeError(f'Invalid key {key}')
322
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
323
+ raise KeyError(key)
324
+ else:
325
+ raise RuntimeError(f'c_bytemap_ex_get_double failed with err code: {ret_code}')
326
+
327
+ cdef void c_set_double(self, str key, double value):
328
+ cdef size_t key_len = 0
329
+ cdef const char* key_ptr = ByteMapEx.c_key_to_string(key, &key_len)
330
+ cdef int ret_code = c_bytemap_ex_set_double(self.header, key_ptr, key_len, value, self.seq_id)
331
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
332
+ return
333
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
334
+ raise ValueError('Uninitialized bytemap table')
335
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
336
+ raise TypeError(f'Invalid key {key}')
337
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_FULL:
338
+ raise MemoryError('Mapping is full')
339
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_OOM:
340
+ raise MemoryError('Out of memory')
341
+ else:
342
+ raise RuntimeError(f'c_bytemap_ex_set failed with err code: {ret_code}')
343
+
344
+ cdef double c_pop_double(self, str key):
345
+ cdef size_t c_str_len = 0
346
+ cdef const char* c_str = ByteMapEx.c_key_to_string(key, &c_str_len)
347
+ cdef double out
348
+ cdef int ret_code = c_bytemap_ex_pop_double(self.header, c_str, c_str_len, self.seq_id, &out)
349
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
350
+ return out
351
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
352
+ raise ValueError('Uninitialized bytemap table')
353
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
354
+ raise TypeError(f'Invalid key {key}')
355
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
356
+ raise KeyError(key)
357
+ else:
358
+ raise RuntimeError(f'c_bytemap_ex_pop failed with err code: {ret_code}')
359
+
360
+ # === Python Interfaces ===
361
+
362
+ def __contains__(self, str key):
363
+ cdef size_t c_str_len = 0
364
+ cdef const char* c_str = ByteMapEx.c_key_to_string(key, &c_str_len)
365
+ cdef int ret_code = c_bytemap_ex_contains(self.header, c_str, c_str_len)
366
+ return ret_code == bytemap_ret_code.BYTEMAP_OK
367
+
368
+ def __getitem__(self, str key):
369
+ return self.c_get_double(key)
370
+
371
+ def __setitem__(self, str key, double value):
372
+ self.c_set_double(key, value)
373
+
374
+ def __repr__(self):
375
+ if self.header:
376
+ return (f"<{self.__class__.__name__} {<uintptr_t> self.header:#0x}>("
377
+ f"size={self.header.size}, "
378
+ f"slot_capacity={self.header.slot_capacity}, "
379
+ f"occupied={self.header.occupied}, "
380
+ f"capacity={self.header.capacity})")
381
+ return f"<{self.__class__.__name__} Unbound>"
382
+
383
+ def __copy__(self):
384
+ cdef bytemap* cloned = c_bytemap_ex_clone(self.header, AP_DEFAULT_ALLOCATOR)
385
+ if cloned == NULL:
386
+ raise MemoryError('Failed to clone ByteMapExDouble')
387
+ cdef ByteMapExDouble instance = ByteMapExDouble.__new__(ByteMapExDouble)
388
+ instance.header = cloned
389
+ instance.owner = True
390
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
391
+ return instance
392
+
393
+ def __deepcopy__(self, memo):
394
+ return self.__copy__()
395
+
396
+ def __iter__(self):
397
+ cdef bytemap_entry* entry = self.header.first if self.header else NULL
398
+ while entry:
399
+ yield PyUnicode_FromStringAndSize(entry.key, entry.key_length)
400
+ entry = entry.next
401
+
402
+ def get(self, str key, double default=NAN, *):
403
+ cdef size_t c_str_len = 0
404
+ cdef const char* c_str = ByteMapEx.c_key_to_string(key, &c_str_len)
405
+ cdef double out = default
406
+ cdef int ret_code = c_bytemap_ex_get_double(self.header, c_str, c_str_len, &out)
407
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
408
+ return out
409
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
410
+ raise ValueError('Uninitialized bytemap table')
411
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
412
+ raise TypeError(f'Invalid key {key}')
413
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
414
+ return default
415
+ else:
416
+ raise RuntimeError(f'c_bytemap_ex_get_double failed with err code: {ret_code}')
417
+
418
+ def set(self, str key, double value):
419
+ return self.c_set_double(key, value)
420
+
421
+ def pop(self, str key, object default=NO_DEFAULT, *):
422
+ cdef size_t c_str_len = 0
423
+ cdef const char* c_str = ByteMapEx.c_key_to_string(key, &c_str_len)
424
+ cdef double out = 0
425
+ cdef int ret_code = c_bytemap_ex_pop_double(self.header, c_str, c_str_len, self.seq_id, &out)
426
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
427
+ return out
428
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
429
+ raise ValueError('Uninitialized bytemap table')
430
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
431
+ raise TypeError(f'Invalid key {key}')
432
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
433
+ if default is NO_DEFAULT:
434
+ raise KeyError(key)
435
+ else:
436
+ return <double> default
437
+ else:
438
+ raise RuntimeError(f'c_bytemap_ex_pop failed with err code: {ret_code}')
439
+
440
+ def contains(self, str key):
441
+ cdef size_t c_str_len = 0
442
+ cdef const char* c_str = ByteMapEx.c_key_to_string(key, &c_str_len)
443
+ cdef int ret_code = c_bytemap_ex_contains(self.header, c_str, c_str_len)
444
+ return ret_code == bytemap_ret_code.BYTEMAP_OK
445
+
446
+ def keys(self):
447
+ cdef bytemap_entry* entry = self.header.first if self.header else NULL
448
+ while entry:
449
+ yield PyUnicode_FromStringAndSize(entry.key, entry.key_length)
450
+ entry = entry.next
451
+
452
+ def values(self):
453
+ cdef bytemap_entry* entry = self.header.first if self.header else NULL
454
+ while entry:
455
+ yield (<double*> entry.value)[0]
456
+ entry = entry.next
457
+
458
+ def items(self):
459
+ cdef bytemap_entry* entry = self.header.first if self.header else NULL
460
+ while entry:
461
+ yield (PyUnicode_FromStringAndSize(entry.key, entry.key_length),
462
+ (<double*> entry.value)[0])
463
+ entry = entry.next
464
+
465
+ def fork(self):
466
+ cdef ByteMapExDouble instance = ByteMapExDouble.__new__(ByteMapExDouble)
467
+ instance.header = self.header
468
+ instance.owner = False
469
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
470
+ return instance
471
+
472
+ property as_dict:
473
+ def __get__(self):
474
+ cdef out = {}
475
+ if not self.header:
476
+ return out
477
+ cdef bytemap_entry* entry = self.header.first
478
+ cdef str key
479
+ cdef double value
480
+ while entry:
481
+ key = PyUnicode_FromStringAndSize(entry.key, entry.key_length)
482
+ value = (<double*> entry.value)[0]
483
+ out[key] = value
484
+ entry = entry.next
485
+ return out
486
+
487
+
488
+ # ============================================================
489
+ # ByteMap — str/bytes → PyObject* mapping (void* variant)
490
+ # ============================================================
491
+
492
+ cdef class ByteMap(_ByteMapBase):
493
+ def __init__(self, size_t init_capacity=DEFAULT_BYTEMAP_CAPACITY):
494
+ self._init_header(sizeof(void*), init_capacity)
495
+
496
+ @staticmethod
497
+ cdef inline ByteMap c_from_header(bytemap* header, bint owner):
498
+ cdef ByteMap instance = ByteMap.__new__(ByteMap)
499
+ instance.header = header
500
+ instance.owner = owner
501
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
502
+ return instance
503
+
504
+ # --- C-level API ---
505
+
506
+ cdef inline void* c_get_ptr(self, const char* key):
507
+ cdef void* out
508
+ cdef int ret_code = c_bytemap_get(self.header, key, 0, &out)
509
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
510
+ return out
511
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
512
+ raise ValueError('Invalid args')
513
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
514
+ raise KeyError('Invalid key')
515
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
516
+ raise KeyError('Not found')
517
+ else:
518
+ raise RuntimeError(f'Failed to get from {self.__class__.__name__}, err code: {ret_code}')
519
+
520
+ cdef inline void* c_get_bytes(self, bytes key_bytes):
521
+ cdef size_t length = PyBytes_GET_SIZE(key_bytes)
522
+ cdef const char* key = <const char*> key_bytes
523
+ cdef void* out
524
+ cdef int ret_code = c_bytemap_get(self.header, key, length, &out)
525
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
526
+ return out
527
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
528
+ raise ValueError('Invalid args')
529
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
530
+ raise KeyError('Invalid key')
531
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
532
+ raise KeyError(key_bytes)
533
+ else:
534
+ raise RuntimeError(f'Failed to get from {self.__class__.__name__}, err code: {ret_code}')
535
+
536
+ cdef inline void* c_get_str(self, str key_str):
537
+ cdef Py_ssize_t length
538
+ cdef const char* key = PyUnicode_AsUTF8AndSize(key_str, &length)
539
+ cdef void* out
540
+ cdef int ret_code = c_bytemap_get(self.header, key, length, &out)
541
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
542
+ return out
543
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
544
+ raise ValueError('Invalid args')
545
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
546
+ raise KeyError('Invalid key')
547
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
548
+ raise KeyError(key_str)
549
+ else:
550
+ raise RuntimeError(f'Failed to get from {self.__class__.__name__}, err code: {ret_code}')
551
+
552
+ cdef inline void c_set_ptr(self, const char* key, void* value):
553
+ cdef int ret_code = c_bytemap_set(self.header, key, 0, value, NULL)
554
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
555
+ return
556
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
557
+ raise ValueError('Invalid args')
558
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
559
+ raise KeyError('Invalid key')
560
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_FULL:
561
+ raise MemoryError('Mapping is full')
562
+ else:
563
+ raise RuntimeError(f'Failed to set to {self.__class__.__name__}, err code: {ret_code}')
564
+
565
+ cdef inline void c_set_bytes(self, bytes key_bytes, void* value):
566
+ cdef size_t length = PyBytes_GET_SIZE(key_bytes)
567
+ cdef const char* key = <const char*> key_bytes
568
+ cdef int ret_code = c_bytemap_set(self.header, key, length, value, NULL)
569
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
570
+ return
571
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
572
+ raise ValueError('Invalid args')
573
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
574
+ raise KeyError('Invalid key')
575
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_FULL:
576
+ raise MemoryError('Mapping is full')
577
+ else:
578
+ raise RuntimeError(f'Failed to set to {self.__class__.__name__}, err code: {ret_code}')
579
+
580
+ cdef inline void c_set_str(self, str key_str, void* value):
581
+ cdef Py_ssize_t length
582
+ cdef const char* key = PyUnicode_AsUTF8AndSize(key_str, &length)
583
+ cdef int ret_code = c_bytemap_set(self.header, key, length, value, NULL)
584
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
585
+ return
586
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
587
+ raise ValueError('Invalid args')
588
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
589
+ raise KeyError('Invalid key')
590
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_FULL:
591
+ raise MemoryError('Mapping is full')
592
+ else:
593
+ raise RuntimeError(f'Failed to set to {self.__class__.__name__}, err code: {ret_code}')
594
+
595
+ cdef inline void c_pop_ptr(self, const char* key, void** out):
596
+ cdef int ret_code = c_bytemap_pop(self.header, key, 0, out)
597
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
598
+ return
599
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
600
+ raise ValueError('Invalid args')
601
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
602
+ raise KeyError('Invalid key')
603
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
604
+ raise KeyError('Not found')
605
+ else:
606
+ raise RuntimeError(f'Failed to pop from {self.__class__.__name__}, err code: {ret_code}')
607
+
608
+ cdef inline void c_pop_bytes(self, bytes key_bytes, void** out):
609
+ cdef size_t length = PyBytes_GET_SIZE(key_bytes)
610
+ cdef const char* key = <const char*> key_bytes
611
+ cdef int ret_code = c_bytemap_pop(self.header, key, length, out)
612
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
613
+ return
614
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
615
+ raise ValueError('Invalid args')
616
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
617
+ raise KeyError('Invalid key')
618
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
619
+ raise KeyError(key_bytes)
620
+ else:
621
+ raise RuntimeError(f'Failed to pop from {self.__class__.__name__}, err code: {ret_code}')
622
+
623
+ cdef inline void c_pop_str(self, str key_str, void** out):
624
+ cdef Py_ssize_t length
625
+ cdef const char* key = PyUnicode_AsUTF8AndSize(key_str, &length)
626
+ cdef int ret_code = c_bytemap_pop(self.header, key, length, out)
627
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
628
+ return
629
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
630
+ raise ValueError('Invalid args')
631
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
632
+ raise KeyError('Invalid key')
633
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
634
+ raise KeyError(key_str)
635
+ else:
636
+ raise RuntimeError(f'Failed to pop from {self.__class__.__name__}, err code: {ret_code}')
637
+
638
+ cdef inline bint c_contains_ptr(self, const char* key):
639
+ cdef int ret_code = c_bytemap_contains(self.header, key, 0)
640
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
641
+ return True
642
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
643
+ return False
644
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
645
+ raise ValueError('Invalid args')
646
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
647
+ raise KeyError('Invalid key')
648
+ else:
649
+ raise RuntimeError(f'Failed to check {self.__class__.__name__} contain, err code: {ret_code}')
650
+
651
+ cdef inline bint c_contains_bytes(self, bytes key_bytes):
652
+ cdef size_t length = PyBytes_GET_SIZE(key_bytes)
653
+ cdef const char* key = <const char*> key_bytes
654
+ cdef int ret_code = c_bytemap_contains(self.header, key, length)
655
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
656
+ return True
657
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
658
+ return False
659
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
660
+ raise ValueError('Invalid args')
661
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
662
+ raise KeyError('Invalid key')
663
+ else:
664
+ raise RuntimeError(f'Failed to check {self.__class__.__name__} contain, err code: {ret_code}')
665
+
666
+ cdef inline bint c_contains_str(self, str key_str):
667
+ cdef Py_ssize_t length
668
+ cdef const char* key = PyUnicode_AsUTF8AndSize(key_str, &length)
669
+ cdef int ret_code = c_bytemap_contains(self.header, key, length)
670
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
671
+ return True
672
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
673
+ return False
674
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
675
+ raise ValueError('Invalid args')
676
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
677
+ raise KeyError('Invalid key')
678
+ else:
679
+ raise RuntimeError(f'Failed to check {self.__class__.__name__} contain, err code: {ret_code}')
680
+
681
+ # --- Python Interfaces ---
682
+
683
+ def __contains__(self, object key):
684
+ cdef Py_ssize_t length
685
+ cdef const char* key_ptr
686
+ if isinstance(key, str):
687
+ key_ptr = PyUnicode_AsUTF8AndSize(key, &length)
688
+ elif isinstance(key, bytes):
689
+ length = PyBytes_GET_SIZE(key)
690
+ key_ptr = <const char*> key
691
+ else:
692
+ raise TypeError('Key must be str or bytes')
693
+
694
+ cdef int ret_code = c_bytemap_contains(self.header, key_ptr, length)
695
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
696
+ return True
697
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
698
+ return False
699
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
700
+ raise ValueError('Invalid args')
701
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
702
+ raise KeyError('Invalid key')
703
+ else:
704
+ raise RuntimeError(f'Failed to check {self.__class__.__name__} contain, err code: {ret_code}')
705
+
706
+ def __getitem__(self, object key):
707
+ cdef Py_ssize_t length
708
+ cdef const char* key_ptr
709
+ if isinstance(key, str):
710
+ key_ptr = PyUnicode_AsUTF8AndSize(key, &length)
711
+ elif isinstance(key, bytes):
712
+ length = PyBytes_GET_SIZE(key)
713
+ key_ptr = <const char*> key
714
+ else:
715
+ raise TypeError('Key must be str or bytes')
716
+
717
+ cdef void* out
718
+ cdef int ret_code = c_bytemap_get(self.header, key_ptr, length, &out)
719
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
720
+ return <object> <PyObject*> out
721
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
722
+ raise ValueError('Invalid args')
723
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
724
+ raise KeyError('Invalid key')
725
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
726
+ raise KeyError(key)
727
+ else:
728
+ raise RuntimeError(f'Failed to get from {self.__class__.__name__}, err code: {ret_code}')
729
+
730
+ def __setitem__(self, object key, object value):
731
+ cdef Py_ssize_t length
732
+ cdef const char* key_ptr
733
+ if isinstance(key, str):
734
+ key_ptr = PyUnicode_AsUTF8AndSize(key, &length)
735
+ elif isinstance(key, bytes):
736
+ length = PyBytes_GET_SIZE(key)
737
+ key_ptr = <const char*> key
738
+ else:
739
+ raise TypeError('Key must be str or bytes')
740
+
741
+ cdef int ret_code = c_bytemap_set(self.header, key_ptr, length, <void*> <PyObject*> value, NULL)
742
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
743
+ return
744
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
745
+ raise ValueError('Invalid args')
746
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
747
+ raise KeyError('Invalid key')
748
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_FULL:
749
+ raise MemoryError('Mapping is full')
750
+ else:
751
+ raise RuntimeError(f'Failed to set to {self.__class__.__name__}, err code: {ret_code}')
752
+
753
+ def __repr__(self) -> str:
754
+ return f"<{self.__class__.__name__}>(size={self.header.size}, occupied={self.header.occupied}, capacity={self.header.capacity})"
755
+
756
+ def __copy__(self):
757
+ cdef bytemap* cloned = c_bytemap_clone(self.header, AP_DEFAULT_ALLOCATOR)
758
+ if cloned == NULL:
759
+ raise MemoryError('Failed to clone ByteMap')
760
+ cdef ByteMap instance = ByteMap.__new__(ByteMap)
761
+ instance.header = cloned
762
+ instance.owner = True
763
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
764
+ return instance
765
+
766
+ def __deepcopy__(self, memo):
767
+ return self.__copy__()
768
+
769
+ cpdef uint64_t hash(self, object key):
770
+ cdef Py_ssize_t length
771
+ cdef const char* key_ptr
772
+ if isinstance(key, str):
773
+ key_ptr = PyUnicode_AsUTF8AndSize(key, &length)
774
+ elif isinstance(key, bytes):
775
+ length = PyBytes_GET_SIZE(key)
776
+ key_ptr = <const char*> key
777
+ else:
778
+ raise TypeError('Key must be str or bytes')
779
+ cdef uint64_t out
780
+ cdef int ret_code = c_bytemap_hash(self.header, key_ptr, length, &out)
781
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
782
+ return out
783
+ raise RuntimeError(f'c_bytemap_hash failed with err_code: {ret_code}')
784
+
785
+ def get(self, object key, object default=None):
786
+ cdef Py_ssize_t length
787
+ cdef const char* key_ptr
788
+ if isinstance(key, str):
789
+ key_ptr = PyUnicode_AsUTF8AndSize(key, &length)
790
+ elif isinstance(key, bytes):
791
+ length = PyBytes_GET_SIZE(key)
792
+ key_ptr = <const char*> key
793
+ else:
794
+ raise TypeError('Key must be str or bytes')
795
+
796
+ cdef void* out
797
+ cdef int ret_code = c_bytemap_get(self.header, key_ptr, length, &out)
798
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
799
+ return <object> <PyObject*> out
800
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
801
+ raise ValueError('Invalid args')
802
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
803
+ raise KeyError('Invalid key')
804
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
805
+ return default
806
+ else:
807
+ raise RuntimeError(f'Failed to get from {self.__class__.__name__}, err code: {ret_code}')
808
+
809
+ def get_addr(self, object key):
810
+ cdef Py_ssize_t length
811
+ cdef const char* key_ptr
812
+ if isinstance(key, str):
813
+ key_ptr = PyUnicode_AsUTF8AndSize(key, &length)
814
+ elif isinstance(key, bytes):
815
+ length = PyBytes_GET_SIZE(key)
816
+ key_ptr = <const char*> key
817
+ else:
818
+ raise TypeError('Key must be str or bytes')
819
+
820
+ cdef void* out
821
+ cdef int ret_code = c_bytemap_get(self.header, key_ptr, length, &out)
822
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
823
+ return <uintptr_t> out
824
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
825
+ raise ValueError('Invalid args')
826
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
827
+ raise KeyError('Invalid key')
828
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
829
+ raise KeyError(key)
830
+ else:
831
+ raise RuntimeError(f'Failed to get from {self.__class__.__name__}, err code: {ret_code}')
832
+
833
+ def set(self, object key, object value):
834
+ cdef Py_ssize_t length
835
+ cdef const char* key_ptr
836
+ if isinstance(key, str):
837
+ key_ptr = PyUnicode_AsUTF8AndSize(key, &length)
838
+ elif isinstance(key, bytes):
839
+ length = PyBytes_GET_SIZE(key)
840
+ key_ptr = <const char*> key
841
+ else:
842
+ raise TypeError('Key must be str or bytes')
843
+
844
+ cdef int ret_code = c_bytemap_set(self.header, key_ptr, length, <void*> <PyObject*> value, NULL)
845
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
846
+ return
847
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
848
+ raise ValueError('Invalid args')
849
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
850
+ raise KeyError('Invalid key')
851
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_FULL:
852
+ raise MemoryError('Mapping is full')
853
+ else:
854
+ raise RuntimeError(f'Failed to set to {self.__class__.__name__}, err code: {ret_code}')
855
+
856
+ def set_addr(self, object key, uintptr_t value):
857
+ cdef Py_ssize_t length
858
+ cdef const char* key_ptr
859
+ if isinstance(key, str):
860
+ key_ptr = PyUnicode_AsUTF8AndSize(key, &length)
861
+ elif isinstance(key, bytes):
862
+ length = PyBytes_GET_SIZE(key)
863
+ key_ptr = <const char*> key
864
+ else:
865
+ raise TypeError('Key must be str or bytes')
866
+
867
+ cdef int ret_code = c_bytemap_set(self.header, key_ptr, length, <void*> value, NULL)
868
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
869
+ return
870
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
871
+ raise ValueError('Invalid args')
872
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
873
+ raise KeyError('Invalid key')
874
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_FULL:
875
+ raise MemoryError('Mapping is full')
876
+ else:
877
+ raise RuntimeError(f'Failed to set to {self.__class__.__name__}, err code: {ret_code}')
878
+
879
+ def pop(self, object key, object default=NO_DEFAULT, *):
880
+ cdef Py_ssize_t length
881
+ cdef const char* key_ptr
882
+ if isinstance(key, str):
883
+ key_ptr = PyUnicode_AsUTF8AndSize(key, &length)
884
+ elif isinstance(key, bytes):
885
+ length = PyBytes_GET_SIZE(key)
886
+ key_ptr = <const char*> key
887
+ else:
888
+ raise TypeError('Key must be str or bytes')
889
+
890
+ cdef void* out
891
+ cdef int ret_code = c_bytemap_pop(self.header, key_ptr, length, &out)
892
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
893
+ return
894
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
895
+ raise ValueError('Invalid args')
896
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
897
+ raise KeyError('Invalid key')
898
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
899
+ if default is NO_DEFAULT:
900
+ raise KeyError(key)
901
+ else:
902
+ return default
903
+ else:
904
+ raise RuntimeError(f'Failed to pop from {self.__class__.__name__}, err code: {ret_code}')
905
+
906
+ def contains(self, key: str | bytes):
907
+ cdef Py_ssize_t length
908
+ cdef const char* key_ptr
909
+ if isinstance(key, str):
910
+ key_ptr = PyUnicode_AsUTF8AndSize(key, &length)
911
+ elif isinstance(key, bytes):
912
+ length = PyBytes_GET_SIZE(key)
913
+ key_ptr = <const char*> key
914
+ else:
915
+ raise TypeError('Key must be str or bytes')
916
+
917
+ cdef int ret_code = c_bytemap_contains(self.header, key_ptr, length)
918
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
919
+ return True
920
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
921
+ return False
922
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
923
+ raise ValueError('Invalid args')
924
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
925
+ raise KeyError('Invalid key')
926
+ else:
927
+ raise RuntimeError(f'Failed to check {self.__class__.__name__} contain, err code: {ret_code}')
928
+
929
+ def bytes_keys(self):
930
+ cdef bytemap_entry* entry = self.header.first
931
+ while entry:
932
+ yield PyBytes_FromStringAndSize(entry.key, entry.key_length)
933
+ entry = entry.next
934
+
935
+ def str_keys(self):
936
+ cdef bytemap_entry* entry = self.header.first
937
+ while entry:
938
+ yield PyUnicode_FromStringAndSize(entry.key, entry.key_length)
939
+ entry = entry.next
940
+
941
+ def values(self):
942
+ cdef bytemap_entry* entry = self.header.first
943
+ while entry:
944
+ yield <uintptr_t> c_bytemap_entry_value(entry)
945
+ entry = entry.next
946
+
947
+ property capacity:
948
+ def __get__(self):
949
+ return self.header.capacity
950
+
951
+ def __set__(self, size_t capacity):
952
+ c_bytemap_rehash(self.header, capacity)
953
+
954
+ property salt:
955
+ def __get__(self):
956
+ return self.header.salt
957
+
958
+ def __set__(self, uint64_t salt):
959
+ self.header.salt = salt
960
+
961
+
962
+ # ============================================================
963
+ # _BoundByteMapBase — common base for all bound dict variants
964
+ # ============================================================
965
+
966
+ cdef class _BoundByteMapBase(dict):
967
+ def __cinit__(self):
968
+ self.header = NULL
969
+ self.owner = False
970
+ self.seq_id = 0
971
+ self.callback_id = 0
972
+
973
+ def __dealloc__(self):
974
+ if self.callback_id:
975
+ c_bytemap_ex_unregister_callback(self.header, self.callback_id)
976
+
977
+ if not self.owner:
978
+ return
979
+
980
+ if self.header:
981
+ c_bytemap_ex_free(self.header)
982
+
983
+ cdef void _init_bound_header(self, size_t slot_capacity):
984
+ self.header = c_bytemap_ex_new(DEFAULT_BYTEMAP_CAPACITY, slot_capacity, AP_DEFAULT_ALLOCATOR)
985
+ self.seq_id = c_bytemap_gen_seq_id(<void*> self)
986
+ cdef int ret_code = c_bytemap_ex_register_callback(
987
+ self.header,
988
+ self.c_sync,
989
+ <void*> <PyObject*> self,
990
+ &self.callback_id
991
+ )
992
+
993
+ if ret_code != bytemap_ret_code.BYTEMAP_OK:
994
+ c_bytemap_ex_free(self.header)
995
+ raise RuntimeError(f'Failed to register callback for {self.__class__.__name__}, error code {ret_code}')
996
+ self.owner = True
997
+
998
+ @staticmethod
999
+ cdef void c_sync(bytemap_callback_event event,
1000
+ const char* key, size_t key_len,
1001
+ const char* value, size_t value_len,
1002
+ uint64_t seq_id, void* user_data) noexcept:
1003
+ cdef PyObject* py_dict = <PyObject*> user_data
1004
+ cdef _BoundByteMapBase instance = <_BoundByteMapBase> py_dict
1005
+ cdef object py_key, py_value
1006
+
1007
+ if event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_MODIFIED or event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_ADDED:
1008
+ if seq_id == instance.seq_id:
1009
+ return
1010
+ py_key = instance.c_deserialize_key(key, key_len)
1011
+ py_value = instance.c_deserialize_value(value, value_len)
1012
+ if PyDict_SetItem(py_dict, <PyObject*> py_key, <PyObject*> py_value) < 0:
1013
+ PyErr_Clear()
1014
+ elif event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_POPPED:
1015
+ if seq_id == instance.seq_id:
1016
+ return
1017
+ py_key = instance.c_deserialize_key(key, key_len)
1018
+ if PyDict_DelItem(py_dict, <PyObject*> py_key) < 0:
1019
+ PyErr_Clear()
1020
+ elif event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_CLEARED:
1021
+ PyDict_Clear(py_dict)
1022
+ elif event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_FREED:
1023
+ instance.header = NULL
1024
+ instance.callback_id = 0
1025
+ instance.owner = False
1026
+ PyDict_Clear(py_dict)
1027
+
1028
+ cdef void c_bind(self, bytemap* header):
1029
+ if self.header:
1030
+ raise RuntimeError(f'{self.__class__.__name__} already bound')
1031
+ self.header = header
1032
+ self.owner = False
1033
+
1034
+ cdef int ret_code = c_bytemap_ex_register_callback(
1035
+ header,
1036
+ self.c_sync,
1037
+ <void*> <PyObject*> self,
1038
+ &self.callback_id
1039
+ )
1040
+ if ret_code != bytemap_ret_code.BYTEMAP_OK:
1041
+ raise RuntimeError(f'Failed to register callback for {self.__class__.__name__}, error code {ret_code}')
1042
+
1043
+ dict.clear(self)
1044
+ cdef bytemap_entry* entry = header.first
1045
+ cdef object py_key, py_value
1046
+ while entry:
1047
+ py_key = self.c_deserialize_key(entry.key, entry.key_length)
1048
+ py_value = self.c_deserialize_value(entry.value, entry.value_length)
1049
+ if PyDict_SetItem(<PyObject*> self, <PyObject*> py_key, <PyObject*> py_value) < 0:
1050
+ PyErr_Clear()
1051
+ entry = entry.next
1052
+
1053
+ cdef void c_rebind(self, bytemap* header):
1054
+ if self.callback_id:
1055
+ c_bytemap_ex_unregister_callback(self.header, self.callback_id)
1056
+
1057
+ if self.owner:
1058
+ c_bytemap_ex_free(self.header)
1059
+
1060
+ self.header = NULL
1061
+ self.c_bind(header)
1062
+
1063
+ cdef const char* c_serialize_key(self, object obj, size_t* key_len):
1064
+ cdef const char* blob
1065
+ cdef Py_ssize_t blob_size
1066
+ if isinstance(obj, bytes):
1067
+ blob_size = PyBytes_GET_SIZE(obj)
1068
+ blob = <const char*> obj
1069
+ elif isinstance(obj, str):
1070
+ blob = PyUnicode_AsUTF8AndSize(obj, &blob_size)
1071
+ else:
1072
+ raise TypeError('Key must be str or bytes')
1073
+
1074
+ if key_len:
1075
+ key_len[0] = blob_size
1076
+ return blob
1077
+
1078
+ cdef object c_deserialize_key(self, const char* key, size_t key_len):
1079
+ return PyUnicode_FromStringAndSize(key, key_len)
1080
+
1081
+ cdef const char* c_serialize_value(self, object obj, size_t* value_len):
1082
+ cdef Py_ssize_t out_len = PyBytes_GET_SIZE(obj)
1083
+ cdef const char* out_ptr = <const char*> obj
1084
+ if value_len:
1085
+ value_len[0] = out_len
1086
+ return out_ptr
1087
+
1088
+ cdef object c_deserialize_value(self, const char* value, size_t value_len):
1089
+ return PyBytes_FromStringAndSize(value, value_len)
1090
+
1091
+ cdef inline void c_sync_key(self, object py_key, const char* c_key, size_t c_key_len):
1092
+ cdef char* c_value = NULL
1093
+ cdef size_t c_value_len = 0
1094
+ cdef int ret_code = c_bytemap_ex_get_ptr(self.header, c_key, c_key_len, &c_value, &c_value_len)
1095
+ cdef object py_value
1096
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
1097
+ py_value = self.c_deserialize_value(c_value, c_value_len)
1098
+ if PyDict_SetItem(<PyObject*> self, <PyObject*> py_key, <PyObject*> py_value) < 0:
1099
+ PyErr_Clear()
1100
+ else:
1101
+ if PyDict_DelItem(<PyObject*> self, <PyObject*> py_key) < 0:
1102
+ PyErr_Clear()
1103
+
1104
+ cdef void c_set(self, object py_key, object py_value):
1105
+ cdef size_t c_key_len = 0
1106
+ cdef size_t c_value_len = 0
1107
+ cdef const char* c_key = self.c_serialize_key(py_key, &c_key_len)
1108
+ cdef const char* c_value = self.c_serialize_value(py_value, &c_value_len)
1109
+
1110
+ PyDict_SetItem(<PyObject*> self, <PyObject*> py_key, <PyObject*> py_value)
1111
+ cdef int ret_code = c_bytemap_ex_set(self.header, c_key, c_key_len, c_value, c_value_len, self.seq_id, NULL)
1112
+
1113
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
1114
+ return
1115
+ self.c_sync_key(py_key, c_key, c_key_len)
1116
+
1117
+ if ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
1118
+ raise ValueError('Invalid args')
1119
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
1120
+ raise KeyError(f'Invalid key {py_key}')
1121
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_FULL:
1122
+ raise MemoryError('Mapping is full')
1123
+ else:
1124
+ raise RuntimeError(f'Failed to set to {self.__class__.__name__}, err code: {ret_code}')
1125
+
1126
+ cdef object c_pop(self, object py_key, object default=NO_DEFAULT):
1127
+ cdef size_t c_key_len = 0
1128
+ cdef const char* c_key = self.c_serialize_key(py_key, &c_key_len)
1129
+ cdef PyObject* py_value = NULL
1130
+
1131
+ if BP_PyDict_Pop(<PyObject*> self, <PyObject*> py_key, &py_value) < 0:
1132
+ PyErr_Clear()
1133
+
1134
+ cdef char* c_value = NULL
1135
+ cdef size_t c_value_len = 0
1136
+ cdef int ret_code = c_bytemap_ex_pop_ptr(self.header, c_key, c_key_len, self.seq_id, &c_value, &c_value_len)
1137
+
1138
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
1139
+ if py_value:
1140
+ Py_XINCREF(py_value)
1141
+ return <object> py_value
1142
+ else:
1143
+ raise BufferError('c_pop succeeded but failed to pop from python dict cache. Likely caused by a buffer corruption.')
1144
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
1145
+ raise ValueError('Invalid args')
1146
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
1147
+ raise KeyError(f'Invalid key {py_key}')
1148
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
1149
+ if default is NO_DEFAULT:
1150
+ raise KeyError(py_key)
1151
+ else:
1152
+ return default
1153
+ else:
1154
+ raise RuntimeError(f'Failed to pop from {self.__class__.__name__}, err code: {ret_code}')
1155
+
1156
+ cdef void c_update(self, dict payload):
1157
+ cdef object py_key, py_value
1158
+ for py_key, py_value in payload.items():
1159
+ self.c_set(py_key, py_value)
1160
+
1161
+ cdef void c_clear(self):
1162
+ c_bytemap_ex_clear(self.header)
1163
+
1164
+ # === Python Interfaces ===
1165
+
1166
+ def __repr__(self):
1167
+ if self.header:
1168
+ return f'<{self.__class__.__name__} {<uintptr_t> self.header:#0x}>{dict.__repr__(self)}'
1169
+ return f'<{self.__class__.__name__} Unbound>{dict.__repr__(self)}'
1170
+
1171
+ def __setitem__(self, object py_key, object py_value):
1172
+ self.c_set(py_key, py_value)
1173
+
1174
+ def setdefault(self, object py_key, object default=NO_DEFAULT):
1175
+ if py_key in self:
1176
+ return self[py_key]
1177
+ if default is not NO_DEFAULT:
1178
+ self.c_set(py_key, default)
1179
+ else:
1180
+ raise KeyError(f'Invalid key {py_key}')
1181
+ return default
1182
+
1183
+ def pop(self, object py_key, object default=NO_DEFAULT):
1184
+ return self.c_pop(py_key, default)
1185
+
1186
+ def update(self, *args, **kwargs):
1187
+ cdef dict payload = dict(*args, **kwargs)
1188
+ self.c_update(payload)
1189
+
1190
+ def clear(self):
1191
+ self.c_clear()
1192
+
1193
+ def sync(self, object py_key):
1194
+ cdef size_t c_key_len = 0
1195
+ cdef const char* c_key = self.c_serialize_key(py_key, &c_key_len)
1196
+ self.c_sync_key(py_key, c_key, c_key_len)
1197
+
1198
+ def fork(self):
1199
+ cdef object cls = self.__class__
1200
+ cdef object instance = cls.__new__(cls)
1201
+
1202
+ cdef _BoundByteMapBase casted = <_BoundByteMapBase> instance
1203
+ casted.header = self.header
1204
+ casted.owner = False
1205
+ casted.seq_id = c_bytemap_gen_seq_id(<void*> instance)
1206
+ cdef int ret_code = c_bytemap_ex_register_callback(
1207
+ self.header,
1208
+ _BoundByteMapBase.c_sync,
1209
+ <void*> <PyObject*> instance,
1210
+ &casted.callback_id
1211
+ )
1212
+ if ret_code != bytemap_ret_code.BYTEMAP_OK:
1213
+ raise RuntimeError(f'Failed to register callback for {self.__class__.__name__}, error code {ret_code}')
1214
+
1215
+ cdef bytemap_entry* entry = self.header.first
1216
+ cdef object py_key, py_value
1217
+ while entry:
1218
+ py_key = casted.c_deserialize_key(entry.key, entry.key_length)
1219
+ py_value = casted.c_deserialize_value(entry.value, entry.value_length)
1220
+ if PyDict_SetItem(<PyObject*> instance, <PyObject*> py_key, <PyObject*> py_value) < 0:
1221
+ PyErr_Clear()
1222
+ entry = entry.next
1223
+
1224
+ return instance
1225
+
1226
+ def rebind(self, object src):
1227
+ cdef bytemap* src_header
1228
+ if isinstance(src, _ByteMapBase):
1229
+ src_header = (<_ByteMapBase> src).header
1230
+ elif isinstance(src, _BoundByteMapBase):
1231
+ src_header = (<_BoundByteMapBase> src).header
1232
+ else:
1233
+ raise TypeError('src must be a ByteMap or BoundByteMap variant')
1234
+ self.c_rebind(src_header)
1235
+
1236
+
1237
+ # ============================================================
1238
+ # BoundByteMapEx — synchronized dict (str → bytes)
1239
+ # ============================================================
1240
+
1241
+ cdef class BoundByteMapEx(_BoundByteMapBase):
1242
+ def __init__(self, size_t slot_capacity=sizeof(void*), *args, **kwargs):
1243
+ self._init_bound_header(slot_capacity)
1244
+ super().__init__()
1245
+ cdef dict payload = dict(*args, **kwargs)
1246
+ if payload:
1247
+ self.c_update(payload)
1248
+
1249
+ @staticmethod
1250
+ cdef BoundByteMapEx c_from_header(bytemap* header, bint owner=False):
1251
+ cdef BoundByteMapEx instance = BoundByteMapEx.__new__(BoundByteMapEx)
1252
+ instance.owner = owner
1253
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
1254
+ instance.c_bind(header)
1255
+ return instance
1256
+
1257
+
1258
+ # ============================================================
1259
+ # BoundByteMapExDouble — synchronized dict (str → double)
1260
+ # ============================================================
1261
+
1262
+ cdef class BoundByteMapExDouble(_BoundByteMapBase):
1263
+ def __init__(self, *args, **kwargs):
1264
+ self._init_bound_header(sizeof(double))
1265
+ super().__init__()
1266
+ cdef dict payload = dict(*args, **kwargs)
1267
+ if payload:
1268
+ self.c_update(payload)
1269
+
1270
+ @staticmethod
1271
+ cdef BoundByteMapExDouble c_from_header(bytemap* header, bint owner=False):
1272
+ cdef BoundByteMapExDouble instance = BoundByteMapExDouble.__new__(BoundByteMapExDouble)
1273
+ instance.owner = owner
1274
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
1275
+ instance.c_bind(header)
1276
+ return instance
1277
+
1278
+ cdef const char* c_serialize_value(self, object obj, size_t* value_len):
1279
+ self.ws = <double> float(obj)
1280
+ if value_len:
1281
+ value_len[0] = sizeof(double)
1282
+ return <const char*> &self.ws
1283
+
1284
+ cdef object c_deserialize_value(self, const char* value, size_t value_len):
1285
+ cdef PyObject* py_value = PyFloat_FromDouble((<double*> value)[0])
1286
+ Py_XINCREF(py_value)
1287
+ return <object> py_value
1288
+
1289
+ property ws:
1290
+ def __get__(self):
1291
+ return self.ws
1292
+
1293
+
1294
+ # ============================================================
1295
+ # BoundByteMap — synchronized dict (str/bytes → PyObject*)
1296
+ # ============================================================
1297
+
1298
+ cdef class BoundByteMap(_BoundByteMapBase):
1299
+ def __init__(self, *args, **kwargs):
1300
+ self._init_bound_header(sizeof(void*))
1301
+ super().__init__()
1302
+ cdef dict payload = dict(*args, **kwargs)
1303
+ if payload:
1304
+ self.c_update(payload)
1305
+
1306
+ @staticmethod
1307
+ cdef BoundByteMap c_from_header(bytemap* header, bint owner=False):
1308
+ cdef BoundByteMap instance = BoundByteMap.__new__(BoundByteMap)
1309
+ instance.owner = owner
1310
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
1311
+ instance.c_bind(header)
1312
+ return instance
1313
+
1314
+ cdef const char* c_serialize_value(self, object obj, size_t* value_len):
1315
+ self._ws_ptr = <void*> <PyObject*> obj
1316
+ if value_len:
1317
+ value_len[0] = sizeof(void*)
1318
+ return <const char*> &self._ws_ptr
1319
+
1320
+ cdef object c_deserialize_value(self, const char* value, size_t value_len):
1321
+ cdef void** vp = <void**> value
1322
+ return <object> <PyObject*> vp[0]
1323
+
1324
+
1325
+ # ============================================================
1326
+ # BoundByteSet — synchronized set backed by bytemap
1327
+ # ============================================================
1328
+
1329
+ cdef class BoundByteSet(set):
1330
+ def __init__(self, *args, **kwargs):
1331
+ self.header = c_bytemap_new(DEFAULT_BYTEMAP_CAPACITY, AP_DEFAULT_ALLOCATOR)
1332
+ self.seq_id = c_bytemap_gen_seq_id(<void*> self)
1333
+ cdef int ret_code = c_bytemap_ex_register_callback(
1334
+ self.header,
1335
+ self.c_sync,
1336
+ <void*> <PyObject*> self,
1337
+ &self.callback_id
1338
+ )
1339
+
1340
+ if ret_code != bytemap_ret_code.BYTEMAP_OK:
1341
+ c_bytemap_free(self.header)
1342
+ raise RuntimeError(f'Failed to register callback for {self.__class__.__name__}, error code {ret_code}')
1343
+ self.owner = True
1344
+
1345
+ super().__init__()
1346
+ cdef set payload = set(*args, **kwargs)
1347
+ if payload:
1348
+ self.c_update(payload)
1349
+
1350
+ def __dealloc__(self):
1351
+ if self.callback_id:
1352
+ c_bytemap_ex_unregister_callback(self.header, self.callback_id)
1353
+
1354
+ if not self.owner:
1355
+ return
1356
+
1357
+ if self.header:
1358
+ c_bytemap_free(self.header)
1359
+
1360
+ @staticmethod
1361
+ cdef BoundByteSet c_from_header(bytemap* header, bint owner=False):
1362
+ cdef BoundByteSet instance = BoundByteSet.__new__(BoundByteSet)
1363
+ instance.owner = owner
1364
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
1365
+ instance.c_bind(header)
1366
+ return instance
1367
+
1368
+ @staticmethod
1369
+ cdef void c_sync(bytemap_callback_event event,
1370
+ const char* key, size_t key_len,
1371
+ const char* value, size_t value_len,
1372
+ uint64_t seq_id, void* user_data) noexcept:
1373
+ cdef BoundByteSet instance = <BoundByteSet> <PyObject*> user_data
1374
+ cdef object py_key
1375
+
1376
+ if event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_MODIFIED or event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_ADDED:
1377
+ if seq_id == instance.seq_id:
1378
+ return
1379
+ py_key = instance.c_deserialize_key(key, key_len)
1380
+ set.add(instance, py_key)
1381
+ elif event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_POPPED:
1382
+ if seq_id == instance.seq_id:
1383
+ return
1384
+ py_key = instance.c_deserialize_key(key, key_len)
1385
+ set.discard(instance, py_key)
1386
+ elif event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_CLEARED:
1387
+ set.clear(instance)
1388
+ elif event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_FREED:
1389
+ instance.header = NULL
1390
+ instance.callback_id = 0
1391
+ instance.owner = False
1392
+ set.clear(instance)
1393
+
1394
+ cdef void c_bind(self, bytemap* header):
1395
+ if self.header:
1396
+ raise RuntimeError(f'{self.__class__.__name__} already bound')
1397
+ self.header = header
1398
+ self.owner = False
1399
+
1400
+ cdef int ret_code = c_bytemap_ex_register_callback(
1401
+ header,
1402
+ self.c_sync,
1403
+ <void*> <PyObject*> self,
1404
+ &self.callback_id
1405
+ )
1406
+ if ret_code != bytemap_ret_code.BYTEMAP_OK:
1407
+ raise RuntimeError(f'Failed to register callback for {self.__class__.__name__}, error code {ret_code}')
1408
+
1409
+ set.clear(self)
1410
+ cdef bytemap_entry* entry = header.first
1411
+ cdef object py_key
1412
+ while entry:
1413
+ py_key = self.c_deserialize_key(entry.key, entry.key_length)
1414
+ set.add(self, py_key)
1415
+ entry = entry.next
1416
+
1417
+ cdef void c_rebind(self, bytemap* header):
1418
+ if self.callback_id:
1419
+ c_bytemap_ex_unregister_callback(self.header, self.callback_id)
1420
+
1421
+ if self.owner:
1422
+ c_bytemap_free(self.header)
1423
+
1424
+ self.header = NULL
1425
+ self.c_bind(header)
1426
+
1427
+ cdef const char* c_serialize_key(self, object obj, size_t* key_len):
1428
+ cdef const char* blob
1429
+ cdef Py_ssize_t blob_size
1430
+ if isinstance(obj, bytes):
1431
+ blob_size = PyBytes_GET_SIZE(obj)
1432
+ blob = <const char*> obj
1433
+ elif isinstance(obj, str):
1434
+ blob = PyUnicode_AsUTF8AndSize(obj, &blob_size)
1435
+ else:
1436
+ raise TypeError('Key must be str or bytes')
1437
+
1438
+ if key_len:
1439
+ key_len[0] = blob_size
1440
+ return blob
1441
+
1442
+ cdef object c_deserialize_key(self, const char* key, size_t key_len):
1443
+ return PyUnicode_FromStringAndSize(key, key_len)
1444
+
1445
+ cdef inline void c_sync_key(self, object py_key, const char* c_key, size_t c_key_len, bint added):
1446
+ cdef int ret_code = c_bytemap_contains(self.header, c_key, c_key_len)
1447
+ if ret_code == bytemap_ret_code.BYTEMAP_OK and added:
1448
+ set.add(self, py_key)
1449
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
1450
+ set.discard(self, py_key)
1451
+
1452
+ cdef void c_add(self, object py_key):
1453
+ cdef size_t c_key_len
1454
+ cdef const char* c_key = self.c_serialize_key(py_key, &c_key_len)
1455
+ cdef void* sentinel = <void*> 1
1456
+
1457
+ set.add(self, py_key)
1458
+
1459
+ cdef int ret_code = c_bytemap_ex_set(self.header, c_key, c_key_len, <const char*>&sentinel, sizeof(void*), self.seq_id, NULL)
1460
+
1461
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
1462
+ return
1463
+ self.c_sync_key(py_key, c_key, c_key_len, ret_code == bytemap_ret_code.BYTEMAP_OK)
1464
+
1465
+ if ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
1466
+ raise ValueError('Invalid args')
1467
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
1468
+ raise KeyError(f'Invalid key {py_key}')
1469
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_FULL:
1470
+ raise MemoryError('Set is full')
1471
+ else:
1472
+ raise RuntimeError(f'Failed to add to {self.__class__.__name__}, err code: {ret_code}')
1473
+
1474
+ cdef object c_discard(self, object py_key):
1475
+ cdef size_t c_key_len
1476
+ cdef const char* c_key = self.c_serialize_key(py_key, &c_key_len)
1477
+ cdef char* out = NULL
1478
+ cdef size_t out_len = 0
1479
+
1480
+ cdef object py_removed = py_key if py_key in self else None
1481
+ set.discard(self, py_key)
1482
+
1483
+ cdef int ret_code = c_bytemap_ex_pop_ptr(self.header, c_key, c_key_len, self.seq_id, &out, &out_len)
1484
+
1485
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
1486
+ return py_removed
1487
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
1488
+ raise ValueError('Invalid args')
1489
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
1490
+ raise KeyError(f'Invalid key {py_key}')
1491
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
1492
+ return None
1493
+ else:
1494
+ raise RuntimeError(f'Failed to discard from {self.__class__.__name__}, err code: {ret_code}')
1495
+
1496
+ cdef void c_update(self, set payload):
1497
+ cdef object py_key
1498
+ for py_key in payload:
1499
+ self.c_add(py_key)
1500
+
1501
+ cdef void c_clear(self):
1502
+ c_bytemap_clear(self.header)
1503
+
1504
+ # === Python Interfaces ===
1505
+
1506
+ def __repr__(self):
1507
+ if self.header:
1508
+ if len(self) > 5:
1509
+ return f'<{self.__class__.__name__} {<uintptr_t> self.header:#0x}>{{{", ".join(list(self)[:5])}...}}'
1510
+ return f'<{self.__class__.__name__} {<uintptr_t> self.header:#0x}>{{{", ".join(list(self))}}}'
1511
+ return f'<{self.__class__.__name__} Unbound>{{}}'
1512
+
1513
+ def add(self, object py_key):
1514
+ self.c_add(py_key)
1515
+
1516
+ def discard(self, object py_key):
1517
+ self.c_discard(py_key)
1518
+
1519
+ def remove(self, object py_key):
1520
+ cdef object result = self.c_discard(py_key)
1521
+ if result is None:
1522
+ raise KeyError(py_key)
1523
+
1524
+ def pop(self):
1525
+ if not self:
1526
+ raise KeyError('pop from an empty set')
1527
+ cdef object py_key = next(iter(self))
1528
+ self.c_discard(py_key)
1529
+ return py_key
1530
+
1531
+ def update(self, *args):
1532
+ cdef set payload = set(*args) if args else set()
1533
+ self.c_update(payload)
1534
+
1535
+ def clear(self):
1536
+ self.c_clear()
1537
+
1538
+ def fork(self):
1539
+ cdef object cls = self.__class__
1540
+ cdef BoundByteSet instance = cls.__new__(cls)
1541
+ cdef int ret_code = c_bytemap_ex_register_callback(
1542
+ self.header,
1543
+ BoundByteSet.c_sync,
1544
+ <void*> <PyObject*> instance,
1545
+ &instance.callback_id
1546
+ )
1547
+
1548
+ if ret_code != bytemap_ret_code.BYTEMAP_OK:
1549
+ raise RuntimeError(f'Failed to register callback for {self.__class__.__name__}, error code {ret_code}')
1550
+
1551
+ cdef object py_key
1552
+ for py_key in self:
1553
+ set.add(instance, py_key)
1554
+ instance.header = self.header
1555
+ instance.owner = False
1556
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
1557
+ return instance
1558
+
1559
+ def rebind(self, object src):
1560
+ cdef bytemap* src_header
1561
+ if isinstance(src, _ByteMapBase):
1562
+ src_header = (<_ByteMapBase> src).header
1563
+ elif isinstance(src, _BoundByteMapBase):
1564
+ src_header = (<_BoundByteMapBase> src).header
1565
+ elif isinstance(src, BoundByteSet):
1566
+ src_header = (<BoundByteSet> src).header
1567
+ else:
1568
+ raise TypeError('src must be a ByteMap, BoundByteMap or BoundByteSet variant')
1569
+ self.c_rebind(src_header)
1570
+
1571
+
1572
+ # ============================================================
1573
+ # ByteMapPerformanceTestToolkit
1574
+ # ============================================================
1575
+
1576
+ cdef class ByteMapPerformanceTestToolkit:
1577
+ @staticmethod
1578
+ def gen_seq_id(uintptr_t addr):
1579
+ """Generate a seq_id from an address using c_bytemap_gen_seq_id.
1580
+
1581
+ Public for testing multiprocessing seq_id uniqueness across processes.
1582
+ """
1583
+ return c_bytemap_gen_seq_id(<void*> addr)
1584
+
1585
+ def __cinit__(self, size_t n_iters=1_000, size_t n_payloads=1_000):
1586
+ self.n_iters = n_iters
1587
+ self.n_payloads = n_payloads
1588
+ self.py_keys = []
1589
+ self.py_payloads = []
1590
+ self.py_map = {}
1591
+ self.c_map = c_bytemap_new(0, AP_DEFAULT_ALLOCATOR)
1592
+ if self.c_map == NULL:
1593
+ raise MemoryError(f'Failed to allocate memory for <{self.__class__.__name__}>.')
1594
+
1595
+ if n_payloads == 0:
1596
+ self.payloads = NULL
1597
+ self.keys = NULL
1598
+ return
1599
+
1600
+ self.payloads = <PyObject**> calloc(n_payloads, sizeof(PyObject*))
1601
+ self.keys = <const char**> calloc(n_payloads, sizeof(char*))
1602
+ if self.payloads == NULL or self.keys == NULL:
1603
+ if self.payloads != NULL:
1604
+ free(self.payloads)
1605
+ self.payloads = NULL
1606
+ if self.keys != NULL:
1607
+ free(self.keys)
1608
+ self.keys = NULL
1609
+ c_bytemap_free(self.c_map)
1610
+ self.c_map = NULL
1611
+ raise MemoryError(f'Failed to allocate benchmark buffers for <{self.__class__.__name__}>.')
1612
+
1613
+ self.c_gen_keys()
1614
+ self.c_gen_payloads()
1615
+
1616
+ def __dealloc__(self):
1617
+ if self.payloads != NULL:
1618
+ free(self.payloads)
1619
+ self.payloads = NULL
1620
+ if self.keys != NULL:
1621
+ free(self.keys)
1622
+ self.keys = NULL
1623
+ if self.c_map != NULL:
1624
+ c_bytemap_free(self.c_map)
1625
+ self.c_map = NULL
1626
+
1627
+ cdef inline void c_gen_keys(self):
1628
+ cdef size_t i
1629
+ cdef bytes key_obj
1630
+ if self.keys != NULL and self.n_payloads > 0:
1631
+ memset(<void*> self.keys, 0, self.n_payloads * sizeof(char*))
1632
+ self.py_keys = []
1633
+ for i in range(self.n_payloads):
1634
+ key_obj = f'bench_key_{i:08d}'.encode('ascii')
1635
+ self.py_keys.append(key_obj)
1636
+ self.keys[i] = PyBytes_AsString(key_obj)
1637
+
1638
+ cdef inline void c_gen_payloads(self):
1639
+ cdef size_t i
1640
+ cdef object payload
1641
+ if self.payloads != NULL and self.n_payloads > 0:
1642
+ memset(<void*> self.payloads, 0, self.n_payloads * sizeof(PyObject*))
1643
+ self.py_payloads = []
1644
+ for i in range(self.n_payloads):
1645
+ payload = object()
1646
+ self.py_payloads.append(payload)
1647
+ self.payloads[i] = <PyObject*> payload
1648
+
1649
+ cdef inline void c_prepare_py_map(self):
1650
+ cdef size_t i
1651
+ PyDict_Clear(<PyObject*> self.py_map)
1652
+ for i in range(self.n_payloads):
1653
+ if PyDict_SetItemString(<PyObject*> self.py_map, self.keys[i], <PyObject*> self.payloads[i]) != 0:
1654
+ raise RuntimeError('PyDict_SetItemString failed during benchmark preparation')
1655
+
1656
+ cdef inline void c_prepare_c_map(self):
1657
+ cdef size_t i
1658
+ c_bytemap_clear(self.c_map)
1659
+ for i in range(self.n_payloads):
1660
+ if c_bytemap_set(self.c_map, self.keys[i], 0, <void*> self.payloads[i], NULL) != bytemap_ret_code.BYTEMAP_OK:
1661
+ raise RuntimeError('c_bytemap_set failed during benchmark preparation')
1662
+
1663
+ cpdef double c_map_set_routine(self):
1664
+ cdef size_t iter_idx
1665
+ cdef size_t payload_idx
1666
+ cdef double elapsed = 0.0
1667
+ cdef double start_ts
1668
+
1669
+ for iter_idx in range(self.n_iters):
1670
+ c_bytemap_clear(self.c_map)
1671
+ start_ts = perf_counter()
1672
+ for payload_idx in range(self.n_payloads):
1673
+ if c_bytemap_set(self.c_map, self.keys[payload_idx], 0, <void*> self.payloads[payload_idx], NULL) != bytemap_ret_code.BYTEMAP_OK:
1674
+ raise RuntimeError('c_bytemap_set failed during benchmark')
1675
+ elapsed += perf_counter() - start_ts
1676
+
1677
+ self.c_checksum = 0
1678
+ for payload_idx in range(self.n_payloads):
1679
+ self.c_checksum += <uintptr_t> self.payloads[payload_idx]
1680
+ return elapsed
1681
+
1682
+ cpdef double py_map_set_routine(self):
1683
+ cdef size_t iter_idx
1684
+ cdef size_t payload_idx
1685
+ cdef double elapsed = 0.0
1686
+ cdef double start_ts
1687
+
1688
+ for iter_idx in range(self.n_iters):
1689
+ PyDict_Clear(<PyObject*> self.py_map)
1690
+ start_ts = perf_counter()
1691
+ for payload_idx in range(self.n_payloads):
1692
+ if PyDict_SetItemString(<PyObject*> self.py_map, self.keys[payload_idx], <PyObject*> self.payloads[payload_idx]) != 0:
1693
+ raise RuntimeError('PyDict_SetItemString failed during benchmark')
1694
+ elapsed += perf_counter() - start_ts
1695
+
1696
+ self.py_checksum = 0
1697
+ for payload_idx in range(self.n_payloads):
1698
+ self.py_checksum += <uintptr_t> self.payloads[payload_idx]
1699
+ return elapsed
1700
+
1701
+ cpdef double c_map_get_routine(self):
1702
+ cdef size_t iter_idx
1703
+ cdef size_t payload_idx
1704
+ cdef double elapsed = 0.0
1705
+ cdef double start_ts
1706
+ cdef void* out = NULL
1707
+ cdef uintptr_t checksum = 0
1708
+
1709
+ self.c_prepare_c_map()
1710
+ for iter_idx in range(self.n_iters):
1711
+ start_ts = perf_counter()
1712
+ for payload_idx in range(self.n_payloads):
1713
+ out = NULL
1714
+ if c_bytemap_get(self.c_map, self.keys[payload_idx], 0, &out) != bytemap_ret_code.BYTEMAP_OK:
1715
+ raise RuntimeError('c_bytemap_get failed during benchmark')
1716
+ checksum += <uintptr_t> out
1717
+ elapsed += perf_counter() - start_ts
1718
+
1719
+ self.c_checksum = checksum
1720
+ return elapsed
1721
+
1722
+ cpdef double py_map_get_routine(self):
1723
+ cdef size_t iter_idx
1724
+ cdef size_t payload_idx
1725
+ cdef double elapsed = 0.0
1726
+ cdef double start_ts
1727
+ cdef PyObject* out
1728
+ cdef uintptr_t checksum = 0
1729
+
1730
+ self.c_prepare_py_map()
1731
+ for iter_idx in range(self.n_iters):
1732
+ start_ts = perf_counter()
1733
+ for payload_idx in range(self.n_payloads):
1734
+ out = PyDict_GetItemString(<PyObject*> self.py_map, self.keys[payload_idx])
1735
+ if out == NULL:
1736
+ raise RuntimeError('PyDict_GetItemString failed during benchmark')
1737
+ checksum += <uintptr_t> out
1738
+ elapsed += perf_counter() - start_ts
1739
+
1740
+ self.py_checksum = checksum
1741
+ return elapsed
1742
+
1743
+ cpdef dict run_test(self):
1744
+ cdef double c_set = self.c_map_set_routine()
1745
+ cdef double py_set = self.py_map_set_routine()
1746
+ cdef double c_get = self.c_map_get_routine()
1747
+ cdef double py_get = self.py_map_get_routine()
1748
+
1749
+ return {
1750
+ 'n_iters': self.n_iters,
1751
+ 'n_payloads': self.n_payloads,
1752
+ 'c_set': c_set,
1753
+ 'py_set': py_set,
1754
+ 'c_get': c_get,
1755
+ 'py_get': py_get,
1756
+ 'c_checksum': self.c_checksum,
1757
+ 'py_checksum': self.py_checksum,
1758
+ }