PyCyBase 0.1.10__cp315-cp315-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 (105) hide show
  1. cbase/__infra__.pxd +6 -0
  2. cbase/__init__.pxd +6 -0
  3. cbase/__init__.py +31 -0
  4. cbase/allocator_protocol/__infra__.pxd +65 -0
  5. cbase/allocator_protocol/__init__.pxd +65 -0
  6. cbase/allocator_protocol/__init__.py +45 -0
  7. cbase/allocator_protocol/c_allocator_protocol.c +31874 -0
  8. cbase/allocator_protocol/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
  9. cbase/allocator_protocol/c_allocator_protocol.h +352 -0
  10. cbase/allocator_protocol/c_allocator_protocol.pxd +66 -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 +38225 -0
  14. cbase/allocator_protocol/c_heap_allocator.cp315-win_amd64.pyd +0 -0
  15. cbase/allocator_protocol/c_heap_allocator.h +554 -0
  16. cbase/allocator_protocol/c_heap_allocator.pxd +105 -0
  17. cbase/allocator_protocol/c_heap_allocator.pyi +161 -0
  18. cbase/allocator_protocol/c_heap_allocator.pyx +303 -0
  19. cbase/allocator_protocol/c_nt_shm_allocator.c +33455 -0
  20. cbase/allocator_protocol/c_nt_shm_allocator.cp315-win_amd64.pyd +0 -0
  21. cbase/allocator_protocol/c_nt_shm_allocator.h +1105 -0
  22. cbase/allocator_protocol/c_nt_shm_allocator.pxd +110 -0
  23. cbase/allocator_protocol/c_nt_shm_allocator.pyx +223 -0
  24. cbase/allocator_protocol/c_shm_allocator.h +1270 -0
  25. cbase/allocator_protocol/c_shm_allocator.pxd +150 -0
  26. cbase/allocator_protocol/c_shm_allocator.pyi +299 -0
  27. cbase/allocator_protocol/c_shm_allocator.pyx +445 -0
  28. cbase/allocator_protocol/c_shm_comp.c +4654 -0
  29. cbase/allocator_protocol/c_shm_comp.cp315-win_amd64.pyd +0 -0
  30. cbase/allocator_protocol/c_shm_comp.h +49 -0
  31. cbase/allocator_protocol/c_shm_comp.pxd +46 -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 +9965 -0
  39. cbase/backports/pylong.cp315-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 +52151 -0
  47. cbase/bytemap/c_bytemap.cp315-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 +1760 -0
  51. cbase/bytemap/xxh3.h +55 -0
  52. cbase/bytemap/xxhash.c +42 -0
  53. cbase/bytemap/xxhash.h +7487 -0
  54. cbase/config_view.c +7253 -0
  55. cbase/config_view.cp315-win_amd64.pyd +0 -0
  56. cbase/config_view.pyx +85 -0
  57. cbase/env.c +11120 -0
  58. cbase/env.cp315-win_amd64.pyd +0 -0
  59. cbase/env.pxd +8 -0
  60. cbase/env.pyi +48 -0
  61. cbase/env.pyx +46 -0
  62. cbase/includes/cbase/allocator_protocol/c_allocator_protocol.c +31874 -0
  63. cbase/includes/cbase/allocator_protocol/c_allocator_protocol.h +352 -0
  64. cbase/includes/cbase/allocator_protocol/c_heap_allocator.c +38225 -0
  65. cbase/includes/cbase/allocator_protocol/c_heap_allocator.h +554 -0
  66. cbase/includes/cbase/allocator_protocol/c_nt_shm_allocator.c +33455 -0
  67. cbase/includes/cbase/allocator_protocol/c_nt_shm_allocator.h +1105 -0
  68. cbase/includes/cbase/allocator_protocol/c_shm_allocator.h +1270 -0
  69. cbase/includes/cbase/allocator_protocol/c_shm_comp.c +4654 -0
  70. cbase/includes/cbase/allocator_protocol/c_shm_comp.h +49 -0
  71. cbase/includes/cbase/backports/pydict.h +55 -0
  72. cbase/includes/cbase/backports/pylong.c +9965 -0
  73. cbase/includes/cbase/backports/pylong.h +79 -0
  74. cbase/includes/cbase/bytemap/c_bytemap.c +52151 -0
  75. cbase/includes/cbase/bytemap/c_bytemap.h +867 -0
  76. cbase/includes/cbase/bytemap/xxh3.h +55 -0
  77. cbase/includes/cbase/bytemap/xxhash.c +42 -0
  78. cbase/includes/cbase/bytemap/xxhash.h +7487 -0
  79. cbase/includes/cbase/config_view.c +7253 -0
  80. cbase/includes/cbase/env.c +11120 -0
  81. cbase/includes/cbase/int128.h +81 -0
  82. cbase/includes/cbase/intern_string/c_intern_string.c +28138 -0
  83. cbase/includes/cbase/intern_string/c_intern_string.h +614 -0
  84. cbase/includes/cbase/nt/pthread_nt_compat.h +102 -0
  85. cbase/includes/cbase/nt/regex_nt_compat.h +133 -0
  86. cbase/includes/cbase/nt/time_sched_nt_compat.h +32 -0
  87. cbase/int128.h +81 -0
  88. cbase/int128.pxd +15 -0
  89. cbase/intern_string/BENCHMARK.md +157 -0
  90. cbase/intern_string/__infra__.pxd +13 -0
  91. cbase/intern_string/__init__.pxd +13 -0
  92. cbase/intern_string/__init__.py +19 -0
  93. cbase/intern_string/c_intern_string.c +28138 -0
  94. cbase/intern_string/c_intern_string.cp315-win_amd64.pyd +0 -0
  95. cbase/intern_string/c_intern_string.h +614 -0
  96. cbase/intern_string/c_intern_string.pxd +121 -0
  97. cbase/intern_string/c_intern_string.pyi +376 -0
  98. cbase/intern_string/c_intern_string.pyx +765 -0
  99. cbase/nt/pthread_nt_compat.h +102 -0
  100. cbase/nt/regex_nt_compat.h +133 -0
  101. cbase/nt/time_sched_nt_compat.h +32 -0
  102. pycybase-0.1.10.dist-info/METADATA +190 -0
  103. pycybase-0.1.10.dist-info/RECORD +105 -0
  104. pycybase-0.1.10.dist-info/WHEEL +5 -0
  105. pycybase-0.1.10.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1760 @@
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=0, *args, **kwargs):
1243
+ if slot_capacity == 0:
1244
+ slot_capacity = sizeof(void*)
1245
+ self._init_bound_header(slot_capacity)
1246
+ super().__init__()
1247
+ cdef dict payload = dict(*args, **kwargs)
1248
+ if payload:
1249
+ self.c_update(payload)
1250
+
1251
+ @staticmethod
1252
+ cdef BoundByteMapEx c_from_header(bytemap* header, bint owner=False):
1253
+ cdef BoundByteMapEx instance = BoundByteMapEx.__new__(BoundByteMapEx)
1254
+ instance.owner = owner
1255
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
1256
+ instance.c_bind(header)
1257
+ return instance
1258
+
1259
+
1260
+ # ============================================================
1261
+ # BoundByteMapExDouble — synchronized dict (str → double)
1262
+ # ============================================================
1263
+
1264
+ cdef class BoundByteMapExDouble(_BoundByteMapBase):
1265
+ def __init__(self, *args, **kwargs):
1266
+ self._init_bound_header(sizeof(double))
1267
+ super().__init__()
1268
+ cdef dict payload = dict(*args, **kwargs)
1269
+ if payload:
1270
+ self.c_update(payload)
1271
+
1272
+ @staticmethod
1273
+ cdef BoundByteMapExDouble c_from_header(bytemap* header, bint owner=False):
1274
+ cdef BoundByteMapExDouble instance = BoundByteMapExDouble.__new__(BoundByteMapExDouble)
1275
+ instance.owner = owner
1276
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
1277
+ instance.c_bind(header)
1278
+ return instance
1279
+
1280
+ cdef const char* c_serialize_value(self, object obj, size_t* value_len):
1281
+ self.ws = <double> float(obj)
1282
+ if value_len:
1283
+ value_len[0] = sizeof(double)
1284
+ return <const char*> &self.ws
1285
+
1286
+ cdef object c_deserialize_value(self, const char* value, size_t value_len):
1287
+ cdef PyObject* py_value = PyFloat_FromDouble((<double*> value)[0])
1288
+ Py_XINCREF(py_value)
1289
+ return <object> py_value
1290
+
1291
+ property ws:
1292
+ def __get__(self):
1293
+ return self.ws
1294
+
1295
+
1296
+ # ============================================================
1297
+ # BoundByteMap — synchronized dict (str/bytes → PyObject*)
1298
+ # ============================================================
1299
+
1300
+ cdef class BoundByteMap(_BoundByteMapBase):
1301
+ def __init__(self, *args, **kwargs):
1302
+ self._init_bound_header(sizeof(void*))
1303
+ super().__init__()
1304
+ cdef dict payload = dict(*args, **kwargs)
1305
+ if payload:
1306
+ self.c_update(payload)
1307
+
1308
+ @staticmethod
1309
+ cdef BoundByteMap c_from_header(bytemap* header, bint owner=False):
1310
+ cdef BoundByteMap instance = BoundByteMap.__new__(BoundByteMap)
1311
+ instance.owner = owner
1312
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
1313
+ instance.c_bind(header)
1314
+ return instance
1315
+
1316
+ cdef const char* c_serialize_value(self, object obj, size_t* value_len):
1317
+ self._ws_ptr = <void*> <PyObject*> obj
1318
+ if value_len:
1319
+ value_len[0] = sizeof(void*)
1320
+ return <const char*> &self._ws_ptr
1321
+
1322
+ cdef object c_deserialize_value(self, const char* value, size_t value_len):
1323
+ cdef void** vp = <void**> value
1324
+ return <object> <PyObject*> vp[0]
1325
+
1326
+
1327
+ # ============================================================
1328
+ # BoundByteSet — synchronized set backed by bytemap
1329
+ # ============================================================
1330
+
1331
+ cdef class BoundByteSet(set):
1332
+ def __init__(self, *args, **kwargs):
1333
+ self.header = c_bytemap_new(DEFAULT_BYTEMAP_CAPACITY, AP_DEFAULT_ALLOCATOR)
1334
+ self.seq_id = c_bytemap_gen_seq_id(<void*> self)
1335
+ cdef int ret_code = c_bytemap_ex_register_callback(
1336
+ self.header,
1337
+ self.c_sync,
1338
+ <void*> <PyObject*> self,
1339
+ &self.callback_id
1340
+ )
1341
+
1342
+ if ret_code != bytemap_ret_code.BYTEMAP_OK:
1343
+ c_bytemap_free(self.header)
1344
+ raise RuntimeError(f'Failed to register callback for {self.__class__.__name__}, error code {ret_code}')
1345
+ self.owner = True
1346
+
1347
+ super().__init__()
1348
+ cdef set payload = set(*args, **kwargs)
1349
+ if payload:
1350
+ self.c_update(payload)
1351
+
1352
+ def __dealloc__(self):
1353
+ if self.callback_id:
1354
+ c_bytemap_ex_unregister_callback(self.header, self.callback_id)
1355
+
1356
+ if not self.owner:
1357
+ return
1358
+
1359
+ if self.header:
1360
+ c_bytemap_free(self.header)
1361
+
1362
+ @staticmethod
1363
+ cdef BoundByteSet c_from_header(bytemap* header, bint owner=False):
1364
+ cdef BoundByteSet instance = BoundByteSet.__new__(BoundByteSet)
1365
+ instance.owner = owner
1366
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
1367
+ instance.c_bind(header)
1368
+ return instance
1369
+
1370
+ @staticmethod
1371
+ cdef void c_sync(bytemap_callback_event event,
1372
+ const char* key, size_t key_len,
1373
+ const char* value, size_t value_len,
1374
+ uint64_t seq_id, void* user_data) noexcept:
1375
+ cdef BoundByteSet instance = <BoundByteSet> <PyObject*> user_data
1376
+ cdef object py_key
1377
+
1378
+ if event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_MODIFIED or event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_ADDED:
1379
+ if seq_id == instance.seq_id:
1380
+ return
1381
+ py_key = instance.c_deserialize_key(key, key_len)
1382
+ set.add(instance, py_key)
1383
+ elif event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_POPPED:
1384
+ if seq_id == instance.seq_id:
1385
+ return
1386
+ py_key = instance.c_deserialize_key(key, key_len)
1387
+ set.discard(instance, py_key)
1388
+ elif event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_CLEARED:
1389
+ set.clear(instance)
1390
+ elif event == bytemap_callback_event.BYTEMAP_CALLBACK_EVENT_FREED:
1391
+ instance.header = NULL
1392
+ instance.callback_id = 0
1393
+ instance.owner = False
1394
+ set.clear(instance)
1395
+
1396
+ cdef void c_bind(self, bytemap* header):
1397
+ if self.header:
1398
+ raise RuntimeError(f'{self.__class__.__name__} already bound')
1399
+ self.header = header
1400
+ self.owner = False
1401
+
1402
+ cdef int ret_code = c_bytemap_ex_register_callback(
1403
+ header,
1404
+ self.c_sync,
1405
+ <void*> <PyObject*> self,
1406
+ &self.callback_id
1407
+ )
1408
+ if ret_code != bytemap_ret_code.BYTEMAP_OK:
1409
+ raise RuntimeError(f'Failed to register callback for {self.__class__.__name__}, error code {ret_code}')
1410
+
1411
+ set.clear(self)
1412
+ cdef bytemap_entry* entry = header.first
1413
+ cdef object py_key
1414
+ while entry:
1415
+ py_key = self.c_deserialize_key(entry.key, entry.key_length)
1416
+ set.add(self, py_key)
1417
+ entry = entry.next
1418
+
1419
+ cdef void c_rebind(self, bytemap* header):
1420
+ if self.callback_id:
1421
+ c_bytemap_ex_unregister_callback(self.header, self.callback_id)
1422
+
1423
+ if self.owner:
1424
+ c_bytemap_free(self.header)
1425
+
1426
+ self.header = NULL
1427
+ self.c_bind(header)
1428
+
1429
+ cdef const char* c_serialize_key(self, object obj, size_t* key_len):
1430
+ cdef const char* blob
1431
+ cdef Py_ssize_t blob_size
1432
+ if isinstance(obj, bytes):
1433
+ blob_size = PyBytes_GET_SIZE(obj)
1434
+ blob = <const char*> obj
1435
+ elif isinstance(obj, str):
1436
+ blob = PyUnicode_AsUTF8AndSize(obj, &blob_size)
1437
+ else:
1438
+ raise TypeError('Key must be str or bytes')
1439
+
1440
+ if key_len:
1441
+ key_len[0] = blob_size
1442
+ return blob
1443
+
1444
+ cdef object c_deserialize_key(self, const char* key, size_t key_len):
1445
+ return PyUnicode_FromStringAndSize(key, key_len)
1446
+
1447
+ cdef inline void c_sync_key(self, object py_key, const char* c_key, size_t c_key_len, bint added):
1448
+ cdef int ret_code = c_bytemap_contains(self.header, c_key, c_key_len)
1449
+ if ret_code == bytemap_ret_code.BYTEMAP_OK and added:
1450
+ set.add(self, py_key)
1451
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
1452
+ set.discard(self, py_key)
1453
+
1454
+ cdef void c_add(self, object py_key):
1455
+ cdef size_t c_key_len
1456
+ cdef const char* c_key = self.c_serialize_key(py_key, &c_key_len)
1457
+ cdef void* sentinel = <void*> 1
1458
+
1459
+ set.add(self, py_key)
1460
+
1461
+ cdef int ret_code = c_bytemap_ex_set(self.header, c_key, c_key_len, <const char*>&sentinel, sizeof(void*), self.seq_id, NULL)
1462
+
1463
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
1464
+ return
1465
+ self.c_sync_key(py_key, c_key, c_key_len, ret_code == bytemap_ret_code.BYTEMAP_OK)
1466
+
1467
+ if ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
1468
+ raise ValueError('Invalid args')
1469
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
1470
+ raise KeyError(f'Invalid key {py_key}')
1471
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_FULL:
1472
+ raise MemoryError('Set is full')
1473
+ else:
1474
+ raise RuntimeError(f'Failed to add to {self.__class__.__name__}, err code: {ret_code}')
1475
+
1476
+ cdef object c_discard(self, object py_key):
1477
+ cdef size_t c_key_len
1478
+ cdef const char* c_key = self.c_serialize_key(py_key, &c_key_len)
1479
+ cdef char* out = NULL
1480
+ cdef size_t out_len = 0
1481
+
1482
+ cdef object py_removed = py_key if py_key in self else None
1483
+ set.discard(self, py_key)
1484
+
1485
+ cdef int ret_code = c_bytemap_ex_pop_ptr(self.header, c_key, c_key_len, self.seq_id, &out, &out_len)
1486
+
1487
+ if ret_code == bytemap_ret_code.BYTEMAP_OK:
1488
+ return py_removed
1489
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_BUF:
1490
+ raise ValueError('Invalid args')
1491
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_INVALID_KEY:
1492
+ raise KeyError(f'Invalid key {py_key}')
1493
+ elif ret_code == bytemap_ret_code.BYTEMAP_ERR_NOT_FOUND:
1494
+ return None
1495
+ else:
1496
+ raise RuntimeError(f'Failed to discard from {self.__class__.__name__}, err code: {ret_code}')
1497
+
1498
+ cdef void c_update(self, set payload):
1499
+ cdef object py_key
1500
+ for py_key in payload:
1501
+ self.c_add(py_key)
1502
+
1503
+ cdef void c_clear(self):
1504
+ c_bytemap_clear(self.header)
1505
+
1506
+ # === Python Interfaces ===
1507
+
1508
+ def __repr__(self):
1509
+ if self.header:
1510
+ if len(self) > 5:
1511
+ return f'<{self.__class__.__name__} {<uintptr_t> self.header:#0x}>{{{", ".join(list(self)[:5])}...}}'
1512
+ return f'<{self.__class__.__name__} {<uintptr_t> self.header:#0x}>{{{", ".join(list(self))}}}'
1513
+ return f'<{self.__class__.__name__} Unbound>{{}}'
1514
+
1515
+ def add(self, object py_key):
1516
+ self.c_add(py_key)
1517
+
1518
+ def discard(self, object py_key):
1519
+ self.c_discard(py_key)
1520
+
1521
+ def remove(self, object py_key):
1522
+ cdef object result = self.c_discard(py_key)
1523
+ if result is None:
1524
+ raise KeyError(py_key)
1525
+
1526
+ def pop(self):
1527
+ if not self:
1528
+ raise KeyError('pop from an empty set')
1529
+ cdef object py_key = next(iter(self))
1530
+ self.c_discard(py_key)
1531
+ return py_key
1532
+
1533
+ def update(self, *args):
1534
+ cdef set payload = set(*args) if args else set()
1535
+ self.c_update(payload)
1536
+
1537
+ def clear(self):
1538
+ self.c_clear()
1539
+
1540
+ def fork(self):
1541
+ cdef object cls = self.__class__
1542
+ cdef BoundByteSet instance = cls.__new__(cls)
1543
+ cdef int ret_code = c_bytemap_ex_register_callback(
1544
+ self.header,
1545
+ BoundByteSet.c_sync,
1546
+ <void*> <PyObject*> instance,
1547
+ &instance.callback_id
1548
+ )
1549
+
1550
+ if ret_code != bytemap_ret_code.BYTEMAP_OK:
1551
+ raise RuntimeError(f'Failed to register callback for {self.__class__.__name__}, error code {ret_code}')
1552
+
1553
+ cdef object py_key
1554
+ for py_key in self:
1555
+ set.add(instance, py_key)
1556
+ instance.header = self.header
1557
+ instance.owner = False
1558
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
1559
+ return instance
1560
+
1561
+ def rebind(self, object src):
1562
+ cdef bytemap* src_header
1563
+ if isinstance(src, _ByteMapBase):
1564
+ src_header = (<_ByteMapBase> src).header
1565
+ elif isinstance(src, _BoundByteMapBase):
1566
+ src_header = (<_BoundByteMapBase> src).header
1567
+ elif isinstance(src, BoundByteSet):
1568
+ src_header = (<BoundByteSet> src).header
1569
+ else:
1570
+ raise TypeError('src must be a ByteMap, BoundByteMap or BoundByteSet variant')
1571
+ self.c_rebind(src_header)
1572
+
1573
+
1574
+ # ============================================================
1575
+ # ByteMapPerformanceTestToolkit
1576
+ # ============================================================
1577
+
1578
+ cdef class ByteMapPerformanceTestToolkit:
1579
+ @staticmethod
1580
+ def gen_seq_id(uintptr_t addr):
1581
+ """Generate a seq_id from an address using c_bytemap_gen_seq_id.
1582
+
1583
+ Public for testing multiprocessing seq_id uniqueness across processes.
1584
+ """
1585
+ return c_bytemap_gen_seq_id(<void*> addr)
1586
+
1587
+ def __cinit__(self, size_t n_iters=1_000, size_t n_payloads=1_000):
1588
+ self.n_iters = n_iters
1589
+ self.n_payloads = n_payloads
1590
+ self.py_keys = []
1591
+ self.py_payloads = []
1592
+ self.py_map = {}
1593
+ self.c_map = c_bytemap_new(0, AP_DEFAULT_ALLOCATOR)
1594
+ if self.c_map == NULL:
1595
+ raise MemoryError(f'Failed to allocate memory for <{self.__class__.__name__}>.')
1596
+
1597
+ if n_payloads == 0:
1598
+ self.payloads = NULL
1599
+ self.keys = NULL
1600
+ return
1601
+
1602
+ self.payloads = <PyObject**> calloc(n_payloads, sizeof(PyObject*))
1603
+ self.keys = <const char**> calloc(n_payloads, sizeof(char*))
1604
+ if self.payloads == NULL or self.keys == NULL:
1605
+ if self.payloads != NULL:
1606
+ free(self.payloads)
1607
+ self.payloads = NULL
1608
+ if self.keys != NULL:
1609
+ free(self.keys)
1610
+ self.keys = NULL
1611
+ c_bytemap_free(self.c_map)
1612
+ self.c_map = NULL
1613
+ raise MemoryError(f'Failed to allocate benchmark buffers for <{self.__class__.__name__}>.')
1614
+
1615
+ self.c_gen_keys()
1616
+ self.c_gen_payloads()
1617
+
1618
+ def __dealloc__(self):
1619
+ if self.payloads != NULL:
1620
+ free(self.payloads)
1621
+ self.payloads = NULL
1622
+ if self.keys != NULL:
1623
+ free(self.keys)
1624
+ self.keys = NULL
1625
+ if self.c_map != NULL:
1626
+ c_bytemap_free(self.c_map)
1627
+ self.c_map = NULL
1628
+
1629
+ cdef inline void c_gen_keys(self):
1630
+ cdef size_t i
1631
+ cdef bytes key_obj
1632
+ if self.keys != NULL and self.n_payloads > 0:
1633
+ memset(<void*> self.keys, 0, self.n_payloads * sizeof(char*))
1634
+ self.py_keys = []
1635
+ for i in range(self.n_payloads):
1636
+ key_obj = f'bench_key_{i:08d}'.encode('ascii')
1637
+ self.py_keys.append(key_obj)
1638
+ self.keys[i] = PyBytes_AsString(key_obj)
1639
+
1640
+ cdef inline void c_gen_payloads(self):
1641
+ cdef size_t i
1642
+ cdef object payload
1643
+ if self.payloads != NULL and self.n_payloads > 0:
1644
+ memset(<void*> self.payloads, 0, self.n_payloads * sizeof(PyObject*))
1645
+ self.py_payloads = []
1646
+ for i in range(self.n_payloads):
1647
+ payload = object()
1648
+ self.py_payloads.append(payload)
1649
+ self.payloads[i] = <PyObject*> payload
1650
+
1651
+ cdef inline void c_prepare_py_map(self):
1652
+ cdef size_t i
1653
+ PyDict_Clear(<PyObject*> self.py_map)
1654
+ for i in range(self.n_payloads):
1655
+ if PyDict_SetItemString(<PyObject*> self.py_map, self.keys[i], <PyObject*> self.payloads[i]) != 0:
1656
+ raise RuntimeError('PyDict_SetItemString failed during benchmark preparation')
1657
+
1658
+ cdef inline void c_prepare_c_map(self):
1659
+ cdef size_t i
1660
+ c_bytemap_clear(self.c_map)
1661
+ for i in range(self.n_payloads):
1662
+ if c_bytemap_set(self.c_map, self.keys[i], 0, <void*> self.payloads[i], NULL) != bytemap_ret_code.BYTEMAP_OK:
1663
+ raise RuntimeError('c_bytemap_set failed during benchmark preparation')
1664
+
1665
+ cpdef double c_map_set_routine(self):
1666
+ cdef size_t iter_idx
1667
+ cdef size_t payload_idx
1668
+ cdef double elapsed = 0.0
1669
+ cdef double start_ts
1670
+
1671
+ for iter_idx in range(self.n_iters):
1672
+ c_bytemap_clear(self.c_map)
1673
+ start_ts = perf_counter()
1674
+ for payload_idx in range(self.n_payloads):
1675
+ if c_bytemap_set(self.c_map, self.keys[payload_idx], 0, <void*> self.payloads[payload_idx], NULL) != bytemap_ret_code.BYTEMAP_OK:
1676
+ raise RuntimeError('c_bytemap_set failed during benchmark')
1677
+ elapsed += perf_counter() - start_ts
1678
+
1679
+ self.c_checksum = 0
1680
+ for payload_idx in range(self.n_payloads):
1681
+ self.c_checksum += <uintptr_t> self.payloads[payload_idx]
1682
+ return elapsed
1683
+
1684
+ cpdef double py_map_set_routine(self):
1685
+ cdef size_t iter_idx
1686
+ cdef size_t payload_idx
1687
+ cdef double elapsed = 0.0
1688
+ cdef double start_ts
1689
+
1690
+ for iter_idx in range(self.n_iters):
1691
+ PyDict_Clear(<PyObject*> self.py_map)
1692
+ start_ts = perf_counter()
1693
+ for payload_idx in range(self.n_payloads):
1694
+ if PyDict_SetItemString(<PyObject*> self.py_map, self.keys[payload_idx], <PyObject*> self.payloads[payload_idx]) != 0:
1695
+ raise RuntimeError('PyDict_SetItemString failed during benchmark')
1696
+ elapsed += perf_counter() - start_ts
1697
+
1698
+ self.py_checksum = 0
1699
+ for payload_idx in range(self.n_payloads):
1700
+ self.py_checksum += <uintptr_t> self.payloads[payload_idx]
1701
+ return elapsed
1702
+
1703
+ cpdef double c_map_get_routine(self):
1704
+ cdef size_t iter_idx
1705
+ cdef size_t payload_idx
1706
+ cdef double elapsed = 0.0
1707
+ cdef double start_ts
1708
+ cdef void* out = NULL
1709
+ cdef uintptr_t checksum = 0
1710
+
1711
+ self.c_prepare_c_map()
1712
+ for iter_idx in range(self.n_iters):
1713
+ start_ts = perf_counter()
1714
+ for payload_idx in range(self.n_payloads):
1715
+ out = NULL
1716
+ if c_bytemap_get(self.c_map, self.keys[payload_idx], 0, &out) != bytemap_ret_code.BYTEMAP_OK:
1717
+ raise RuntimeError('c_bytemap_get failed during benchmark')
1718
+ checksum += <uintptr_t> out
1719
+ elapsed += perf_counter() - start_ts
1720
+
1721
+ self.c_checksum = checksum
1722
+ return elapsed
1723
+
1724
+ cpdef double py_map_get_routine(self):
1725
+ cdef size_t iter_idx
1726
+ cdef size_t payload_idx
1727
+ cdef double elapsed = 0.0
1728
+ cdef double start_ts
1729
+ cdef PyObject* out
1730
+ cdef uintptr_t checksum = 0
1731
+
1732
+ self.c_prepare_py_map()
1733
+ for iter_idx in range(self.n_iters):
1734
+ start_ts = perf_counter()
1735
+ for payload_idx in range(self.n_payloads):
1736
+ out = PyDict_GetItemString(<PyObject*> self.py_map, self.keys[payload_idx])
1737
+ if out == NULL:
1738
+ raise RuntimeError('PyDict_GetItemString failed during benchmark')
1739
+ checksum += <uintptr_t> out
1740
+ elapsed += perf_counter() - start_ts
1741
+
1742
+ self.py_checksum = checksum
1743
+ return elapsed
1744
+
1745
+ cpdef dict run_test(self):
1746
+ cdef double c_set = self.c_map_set_routine()
1747
+ cdef double py_set = self.py_map_set_routine()
1748
+ cdef double c_get = self.c_map_get_routine()
1749
+ cdef double py_get = self.py_map_get_routine()
1750
+
1751
+ return {
1752
+ 'n_iters': self.n_iters,
1753
+ 'n_payloads': self.n_payloads,
1754
+ 'c_set': c_set,
1755
+ 'py_set': py_set,
1756
+ 'c_get': c_get,
1757
+ 'py_get': py_get,
1758
+ 'c_checksum': self.c_checksum,
1759
+ 'py_checksum': self.py_checksum,
1760
+ }