PyEventEngine 0.6.0.post3__cp312-cp312-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 (52) hide show
  1. event_engine/__infra__.pxd +2 -0
  2. event_engine/__init__.pxd +2 -0
  3. event_engine/__init__.py +37 -0
  4. event_engine/base/__infra__.pxd +9 -0
  5. event_engine/base/__init__.pxd +9 -0
  6. event_engine/base/__init__.py +82 -0
  7. event_engine/base/c_allocator_protocol.c +10817 -0
  8. event_engine/base/c_allocator_protocol.cp312-win_amd64.pyd +0 -0
  9. event_engine/base/c_allocator_protocol.h +23 -0
  10. event_engine/base/c_allocator_protocol.pxd +31 -0
  11. event_engine/base/c_allocator_protocol.pyx +99 -0
  12. event_engine/capi/__infra__.pxd +189 -0
  13. event_engine/capi/__init__.pxd +189 -0
  14. event_engine/capi/__init__.py +84 -0
  15. event_engine/capi/c_engine.c +28711 -0
  16. event_engine/capi/c_engine.cp312-win_amd64.pyd +0 -0
  17. event_engine/capi/c_engine.h +393 -0
  18. event_engine/capi/c_engine.pxd +84 -0
  19. event_engine/capi/c_engine.pyi +424 -0
  20. event_engine/capi/c_engine.pyx +560 -0
  21. event_engine/capi/c_event.c +19921 -0
  22. event_engine/capi/c_event.cp312-win_amd64.pyd +0 -0
  23. event_engine/capi/c_event.h +342 -0
  24. event_engine/capi/c_event.pxd +159 -0
  25. event_engine/capi/c_event.pyi +226 -0
  26. event_engine/capi/c_event.pyx +387 -0
  27. event_engine/capi/c_topic.c +30978 -0
  28. event_engine/capi/c_topic.cp312-win_amd64.pyd +0 -0
  29. event_engine/capi/c_topic.h +1007 -0
  30. event_engine/capi/c_topic.pxd +147 -0
  31. event_engine/capi/c_topic.pyi +580 -0
  32. event_engine/capi/c_topic.pyx +725 -0
  33. event_engine/capi/fallback_engine.py +670 -0
  34. event_engine/includes/event_engine/base/c_allocator_protocol.c +10817 -0
  35. event_engine/includes/event_engine/base/c_allocator_protocol.h +23 -0
  36. event_engine/includes/event_engine/capi/c_engine.c +28711 -0
  37. event_engine/includes/event_engine/capi/c_engine.h +393 -0
  38. event_engine/includes/event_engine/capi/c_event.c +19921 -0
  39. event_engine/includes/event_engine/capi/c_event.h +342 -0
  40. event_engine/includes/event_engine/capi/c_topic.c +30978 -0
  41. event_engine/includes/event_engine/capi/c_topic.h +1007 -0
  42. event_engine/native/__init__.py +60 -0
  43. event_engine/native/_event.py +420 -0
  44. event_engine/native/_topic.py +171 -0
  45. event_engine/native/engine.py +746 -0
  46. event_engine/native/event.py +467 -0
  47. event_engine/native/topic.py +945 -0
  48. pyeventengine-0.6.0.post3.dist-info/METADATA +189 -0
  49. pyeventengine-0.6.0.post3.dist-info/RECORD +52 -0
  50. pyeventengine-0.6.0.post3.dist-info/WHEEL +5 -0
  51. pyeventengine-0.6.0.post3.dist-info/licenses/LICENSE +21 -0
  52. pyeventengine-0.6.0.post3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,725 @@
1
+ import enum
2
+ import re
3
+ from collections.abc import Iterable
4
+
5
+ from cpython.unicode cimport PyUnicode_AsUTF8AndSize, PyUnicode_FromStringAndSize
6
+ from libc.stdint cimport uint8_t, uintptr_t
7
+ from libc.string cimport memcpy, strlen
8
+
9
+ from cbase.allocator_protocol.c_allocator_protocol cimport c_ap_alloc, c_ap_free, c_ap_protocol_from_ptr
10
+ from cbase.bytemap.c_bytemap cimport bytemap_entry, c_bytemap_entry_value, c_bytemap_get, c_bytemap_new
11
+
12
+ from event_engine.base.c_allocator_protocol cimport EE_DEFAULT_ALLOCATOR, EE_HEAP_ALLOCATOR
13
+
14
+
15
+ class TopicType(enum.IntEnum):
16
+ TOPIC_PART_EXACT = evt_topic_type.TOPIC_PART_EXACT
17
+ TOPIC_PART_ANY = evt_topic_type.TOPIC_PART_ANY
18
+ TOPIC_PART_RANGE = evt_topic_type.TOPIC_PART_RANGE
19
+ TOPIC_PART_PATTERN = evt_topic_type.TOPIC_PART_PATTERN
20
+
21
+
22
+ cdef class TopicPart:
23
+ def __cinit__(self, *arg, bint alloc=False, **kwargs):
24
+ if not alloc:
25
+ return
26
+
27
+ self.header = <evt_topic_part_variant*> c_ap_alloc(sizeof(evt_topic_part_variant), EE_HEAP_ALLOCATOR)
28
+ if not self.header:
29
+ raise MemoryError('Failed to allocate memory!')
30
+ self.owner = True
31
+
32
+ def __dealloc__(self):
33
+ if self.owner and self.header:
34
+ c_ap_free(self.header)
35
+ self.header = NULL
36
+
37
+ @staticmethod
38
+ cdef TopicPart c_from_header(evt_topic_part_variant* header, bint owner=False):
39
+ cdef TopicPart instance = TopicPart.__new__(TopicPart, alloc=False)
40
+ instance.header = header
41
+ instance.owner = owner
42
+ return instance
43
+
44
+ cdef object c_cast(self):
45
+ cdef uint8_t ttype = self.header.header.ttype
46
+ cdef object casted
47
+ if ttype == evt_topic_type.TOPIC_PART_EXACT:
48
+ casted = TopicPartExact.__new__(TopicPartExact, alloc=False)
49
+ elif ttype == evt_topic_type.TOPIC_PART_ANY:
50
+ casted = TopicPartAny.__new__(TopicPartAny, alloc=False)
51
+ elif ttype == evt_topic_type.TOPIC_PART_RANGE:
52
+ casted = TopicPartRange.__new__(TopicPartRange, alloc=False)
53
+ elif ttype == evt_topic_type.TOPIC_PART_PATTERN:
54
+ casted = TopicPartPattern.__new__(TopicPartPattern, alloc=False)
55
+ else:
56
+ raise ValueError(f'Invalid ttype {ttype}')
57
+ (<TopicPart> casted).header = self.header
58
+ (<TopicPart> casted).owner = self.owner
59
+ self.owner = False
60
+ return casted
61
+
62
+ # --- Python Interfaces ---
63
+
64
+ def next(self):
65
+ if self.header.header.next:
66
+ return TopicPart.c_from_header(self.header.header.next, False).c_cast()
67
+ raise StopIteration
68
+
69
+ property ttype:
70
+ def __get__(self) -> TopicType:
71
+ if self.header:
72
+ return TopicType(self.header.header.ttype)
73
+ raise RuntimeError('Not initialized!')
74
+
75
+ property addr:
76
+ def __get__(self) -> int:
77
+ if self.header:
78
+ return <uintptr_t> self.header
79
+
80
+
81
+ cdef class TopicPartExact(TopicPart):
82
+ def __cinit__(self, str part=None, *arg, bint alloc=False, **kwargs):
83
+ if not alloc:
84
+ if part:
85
+ raise RuntimeError('Can not assign part string when uninitialized!')
86
+ return
87
+
88
+ if not part:
89
+ return
90
+
91
+ cdef Py_ssize_t length
92
+ cdef const char* key_ptr = PyUnicode_AsUTF8AndSize(part, &length)
93
+ cdef char* part_ptr = <char*> c_ap_alloc(length + 1, EE_HEAP_ALLOCATOR)
94
+ if not part_ptr:
95
+ c_ap_free(self.header)
96
+ self.header = NULL
97
+ raise MemoryError('Failed to allocate memory!')
98
+ memcpy(part_ptr, key_ptr, length)
99
+
100
+ self.header.exact.part = part_ptr
101
+ self.header.exact.part_len = length
102
+ self.header.header.ttype = evt_topic_type.TOPIC_PART_EXACT
103
+
104
+ def __dealloc__(self):
105
+ if self.owner and self.header:
106
+ if self.header.exact.part:
107
+ c_ap_free(self.header.exact.part)
108
+
109
+ def __repr__(self):
110
+ if self.header:
111
+ return f'<TopicPartExact>(topic="{self.part}")'
112
+ return f'<TopicPartExact uninitialized>'
113
+
114
+ def __len__(self):
115
+ return self.header.exact.part_len
116
+
117
+ property part:
118
+ def __get__(self) -> str:
119
+ return PyUnicode_FromStringAndSize(self.header.exact.part, self.header.exact.part_len)
120
+
121
+
122
+ cdef class TopicPartAny(TopicPart):
123
+ def __cinit__(self, str name=None, *arg, bint alloc=False, **kwargs):
124
+ if not alloc:
125
+ if name:
126
+ raise RuntimeError('Can not assign name string when uninitialized!')
127
+ return
128
+
129
+ if not name:
130
+ return
131
+
132
+ cdef Py_ssize_t length
133
+ cdef const char* key_ptr = PyUnicode_AsUTF8AndSize(name, &length)
134
+ cdef char* name_ptr = <char*> c_ap_alloc(length + 1, EE_HEAP_ALLOCATOR)
135
+ if not name_ptr:
136
+ c_ap_free(self.header)
137
+ self.header = NULL
138
+ raise MemoryError('Failed to allocate memory!')
139
+ memcpy(name_ptr, key_ptr, length)
140
+
141
+ self.header.any.name = name_ptr
142
+ self.header.any.name_len = length
143
+ self.header.header.ttype = evt_topic_type.TOPIC_PART_ANY
144
+
145
+ def __dealloc__(self):
146
+ if self.owner and self.header:
147
+ if self.header.any.name:
148
+ c_ap_free(self.header.any.name)
149
+
150
+ def __repr__(self):
151
+ if self.header:
152
+ return f'<TopicPartAny>(name="{self.name}")'
153
+ return f'<TopicPartAny uninitialized>'
154
+
155
+ property name:
156
+ def __get__(self) -> str:
157
+ return PyUnicode_FromStringAndSize(self.header.any.name, self.header.any.name_len)
158
+
159
+
160
+ cdef class TopicPartRange(TopicPart):
161
+ def __cinit__(self, list options=None, *arg, bint alloc=False, **kwargs):
162
+ if not alloc:
163
+ if options:
164
+ raise RuntimeError('Can not assign options when uninitialized!')
165
+ return
166
+
167
+ if not options:
168
+ return
169
+
170
+ cdef size_t n_internal = len(options) - 1 + sum(len(_) for _ in options)
171
+ cdef char* internal = <char*> c_ap_alloc(n_internal + 1, EE_HEAP_ALLOCATOR)
172
+ if not internal:
173
+ c_ap_free(self.header)
174
+ self.header = NULL
175
+ raise MemoryError('Failed to allocate memory!')
176
+
177
+ cdef size_t n_options = len(options)
178
+ cdef char** option_array = <char**> c_ap_alloc(n_options * sizeof(char*), EE_HEAP_ALLOCATOR)
179
+ if not option_array:
180
+ c_ap_free(internal)
181
+ c_ap_free(self.header)
182
+ self.header = NULL
183
+ raise MemoryError('Failed to allocate memory!')
184
+
185
+ cdef str option
186
+ cdef Py_ssize_t option_length
187
+ cdef const char* option_ptr
188
+ cdef size_t start = 0, i = 0
189
+
190
+ for option in options:
191
+ option_ptr = PyUnicode_AsUTF8AndSize(option, &option_length)
192
+ memcpy(<char*> internal + start, <void*> option_ptr, option_length)
193
+ option_array[i] = <char*> internal + start
194
+ if not option_array[i]:
195
+ c_ap_free(internal)
196
+ c_ap_free(option_array)
197
+ c_ap_free(self.header)
198
+ self.header = NULL
199
+ raise MemoryError('Failed to allocate memory!')
200
+ i += 1
201
+ start += option_length + 1
202
+
203
+ self.header.range.options = option_array
204
+ self.header.range.num_options = n_options
205
+ self.header.range.literal = internal
206
+ self.header.header.ttype = evt_topic_type.TOPIC_PART_RANGE
207
+
208
+ def __dealloc__(self):
209
+ if self.owner and self.header:
210
+ if self.header.range.options:
211
+ c_ap_free(self.header.range.options)
212
+ if self.header.range.literal:
213
+ c_ap_free(self.header.range.literal)
214
+
215
+ def __repr__(self):
216
+ if self.header:
217
+ return f'<TopicPartRange>(n={self.header.range.num_options}, options={str(list(self.options()))})'
218
+ return f'<TopicPartRange uninitialized>'
219
+
220
+ def __len__(self):
221
+ return self.header.range.num_options
222
+
223
+ def __iter__(self):
224
+ return self.options()
225
+
226
+ def options(self):
227
+ if not self.header:
228
+ raise RuntimeError('Not initialized!')
229
+
230
+ cdef char** option_array = self.header.range.options
231
+ cdef size_t num_options = self.header.range.num_options
232
+ cdef size_t i
233
+ cdef char* option_ptr
234
+ cdef size_t option_length
235
+
236
+ for i in range(num_options):
237
+ option_ptr = option_array[i]
238
+ option_length = strlen(option_ptr)
239
+ yield PyUnicode_FromStringAndSize(option_ptr, option_length)
240
+
241
+
242
+ cdef class TopicPartPattern(TopicPart):
243
+ def __cinit__(self, str regex=None, *arg, bint alloc=False, **kwargs):
244
+ if not alloc:
245
+ if regex:
246
+ raise RuntimeError('Can not assign regex string when uninitialized!')
247
+ return
248
+
249
+ if not regex:
250
+ return
251
+
252
+ cdef Py_ssize_t length
253
+ cdef const char* key_ptr = PyUnicode_AsUTF8AndSize(regex, &length)
254
+ cdef char* regex_ptr = <char*> c_ap_alloc(length + 1, EE_HEAP_ALLOCATOR)
255
+ if not regex_ptr:
256
+ c_ap_free(self.header)
257
+ self.header = NULL
258
+ raise MemoryError('Failed to allocate memory!')
259
+ memcpy(regex_ptr, key_ptr, length)
260
+
261
+ self.header.pattern.pattern = regex_ptr
262
+ self.header.pattern.pattern_len = length
263
+ self.header.header.ttype = evt_topic_type.TOPIC_PART_PATTERN
264
+
265
+ def __dealloc__(self):
266
+ if self.owner and self.header:
267
+ if self.header.pattern.pattern:
268
+ c_ap_free(self.header.pattern.pattern)
269
+
270
+ def __repr__(self):
271
+ if self.header:
272
+ return f'<TopicPartPattern>(regex="{self.pattern}")'
273
+ return f'<TopicPartPattern uninitialized>'
274
+
275
+ property pattern:
276
+ def __get__(self) -> str:
277
+ return PyUnicode_FromStringAndSize(self.header.pattern.pattern, self.header.pattern.pattern_len)
278
+
279
+ property regex:
280
+ def __get__(self):
281
+ return re.compile(self.pattern)
282
+
283
+
284
+ cdef allocator_protocol* TOPIC_ALLOCATOR = EE_DEFAULT_ALLOCATOR
285
+
286
+ # Module init: set up the global internal map using the default allocator
287
+ GLOBAL_INTERNAL_MAP = c_bytemap_new(0, EE_DEFAULT_ALLOCATOR)
288
+
289
+
290
+ cpdef Topic get_internal_topic(str key, bint owner=False):
291
+ global GLOBAL_INTERNAL_MAP
292
+ if not GLOBAL_INTERNAL_MAP:
293
+ raise RuntimeError('Internal map not initialized!')
294
+
295
+ cdef Py_ssize_t key_length
296
+ cdef const char* key_ptr = PyUnicode_AsUTF8AndSize(key, &key_length)
297
+ cdef evt_topic* topic = NULL
298
+ cdef int ret_code = c_bytemap_get(GLOBAL_INTERNAL_MAP, key_ptr, key_length, <void**> &topic)
299
+ if topic is NULL:
300
+ return None
301
+ return Topic.c_from_header(topic, owner)
302
+
303
+
304
+ cpdef dict get_internal_map():
305
+ global GLOBAL_INTERNAL_MAP
306
+ if not GLOBAL_INTERNAL_MAP:
307
+ raise RuntimeError('Internal map not initialized!')
308
+
309
+ cdef str key
310
+ cdef evt_topic* topic
311
+ cdef bytemap_entry* entry = GLOBAL_INTERNAL_MAP.first
312
+ cdef dict out = {}
313
+ while entry:
314
+ key = PyUnicode_FromStringAndSize(entry.key, entry.key_length)
315
+ topic = <evt_topic*> c_bytemap_entry_value(entry)
316
+ out[key] = Topic.c_from_header(topic, False)
317
+ entry = entry.next
318
+ return out
319
+
320
+
321
+ cdef class TopicMatchResult:
322
+ def __cinit__(self, size_t n_parts=0, bint alloc=False, **kwargs):
323
+ if not alloc:
324
+ if n_parts:
325
+ raise RuntimeError('Can not allocator buffer when uninitialized!')
326
+ return
327
+
328
+ cdef size_t i
329
+ cdef evt_topic_match* node = NULL
330
+ for i in range(n_parts):
331
+ node = c_topic_match_new(node, EE_DEFAULT_ALLOCATOR)
332
+ if not node:
333
+ c_topic_match_free(self.header)
334
+ elif not self.header:
335
+ self.header = node
336
+ self.owner = True
337
+
338
+ def __dealloc__(self):
339
+ if self.owner and self.header:
340
+ c_topic_match_free(self.header)
341
+
342
+ @staticmethod
343
+ cdef TopicMatchResult c_from_header(evt_topic_match* node, bint owner=True):
344
+ cdef TopicMatchResult instance = TopicMatchResult.__new__(TopicMatchResult, alloc=False)
345
+ instance.header = node
346
+ instance.owner = owner
347
+ return instance
348
+
349
+ @staticmethod
350
+ cdef dict c_match_res(evt_topic_match* node):
351
+ if not node:
352
+ raise RuntimeError('Not initialized!')
353
+ cdef dict info = {
354
+ 'matched': node.matched,
355
+ 'part_a': TopicPart.c_from_header(node.part_a, False).c_cast() if node.part_a else None,
356
+ 'part_b': TopicPart.c_from_header(node.part_b, False).c_cast() if node.part_b else None,
357
+ 'literal': PyUnicode_FromStringAndSize(node.literal, node.literal_len) if node.literal else None,
358
+ }
359
+ return info
360
+
361
+ def __repr__(self):
362
+ return f'<evt_topic_match {"success" if self.matched else "failed"}>(nodes={self.length})'
363
+
364
+ def __bool__(self):
365
+ return self.matched
366
+
367
+ def __len__(self):
368
+ return self.length
369
+
370
+ def __getitem__(self, ssize_t idx):
371
+ cdef ssize_t length = self.length
372
+ if idx < -length:
373
+ raise IndexError(f'Index {idx} out of range!')
374
+ elif idx < 0:
375
+ idx += length
376
+ elif idx >= length:
377
+ raise IndexError(f'Index {idx} out of range!')
378
+
379
+ cdef ssize_t i = 0
380
+ cdef evt_topic_match* node = self.header
381
+ while i < idx:
382
+ i += 1
383
+ node = node.next
384
+
385
+ return TopicMatchResult.c_match_res(node)
386
+
387
+ def __iter__(self):
388
+ cdef evt_topic_match* node = self.header
389
+ while node:
390
+ yield TopicMatchResult.c_match_res(node)
391
+ node = node.next
392
+
393
+ def to_dict(self):
394
+ cdef dict out = {}
395
+ cdef evt_topic_match* node = self.header
396
+ cdef str literal
397
+
398
+ while node:
399
+ literal = PyUnicode_FromStringAndSize(node.literal, node.literal_len)
400
+ out[literal] = TopicPart.c_from_header(node.part_b, False).c_cast()
401
+ node = node.next
402
+ return out
403
+
404
+ property length:
405
+ def __get__(self) -> size_t:
406
+ if not self.header:
407
+ raise RuntimeError('Not initialized!')
408
+ cdef size_t i = 0
409
+ cdef evt_topic_match* node = self.header
410
+ while node:
411
+ i += 1
412
+ node = node.next
413
+ return i
414
+
415
+ property matched:
416
+ def __get__(self) -> bint:
417
+ if not self.header:
418
+ raise RuntimeError('Not initialized!')
419
+
420
+ cdef evt_topic_match* node = self.header
421
+ while node:
422
+ if not node.matched:
423
+ return False
424
+ node = node.next
425
+ return True
426
+
427
+
428
+ cdef class Topic:
429
+ def __cinit__(self, str topic=None, *arg, bint alloc=True, **kwargs):
430
+ if not alloc:
431
+ if topic:
432
+ raise RuntimeError('Can not assign topic string when uninitialized!')
433
+ return
434
+
435
+ if not topic:
436
+ self.header = c_topic_new(NULL, 0, EE_DEFAULT_ALLOCATOR)
437
+ self.owner = True
438
+ if not self.header:
439
+ raise MemoryError(f'Failed to init topic ":{topic}", check if the syntax is correct!')
440
+ return
441
+
442
+ cdef Py_ssize_t topic_length
443
+ cdef const char* topic_ptr = PyUnicode_AsUTF8AndSize(topic, &topic_length)
444
+ self.header = c_topic_new(topic_ptr, topic_length, EE_DEFAULT_ALLOCATOR)
445
+ if not self.header:
446
+ raise MemoryError('Failed to allocate memory!')
447
+
448
+ def __dealloc__(self):
449
+ if self.owner and self.header:
450
+ c_topic_free(self.header)
451
+
452
+ @staticmethod
453
+ cdef Topic c_from_header(evt_topic* header, bint owner=False):
454
+ cdef Topic instance = Topic.__new__(Topic, alloc=False)
455
+ instance.header = header
456
+ instance.owner = owner
457
+ return instance
458
+
459
+ cdef void c_append(self, evt_topic_part_variant* tpart):
460
+ cdef evt_topic_type ttype = tpart.header.ttype
461
+ if ttype == evt_topic_type.TOPIC_PART_EXACT:
462
+ literal = tpart.exact.part
463
+ literal_len = tpart.exact.part_len
464
+ elif ttype == evt_topic_type.TOPIC_PART_ANY:
465
+ literal = tpart.any.name
466
+ literal_len = tpart.any.name_len
467
+ elif ttype == evt_topic_type.TOPIC_PART_RANGE:
468
+ literal = tpart.range.literal
469
+ literal_len = tpart.range.literal_len
470
+ elif ttype == evt_topic_type.TOPIC_PART_PATTERN:
471
+ literal = tpart.pattern.pattern
472
+ literal_len = tpart.pattern.pattern_len
473
+ else:
474
+ raise RuntimeError(f'Unknown topic type {TopicType(ttype)}')
475
+ c_topic_append(self.header, literal, literal_len, ttype)
476
+
477
+ cdef void c_update_literal(self):
478
+ c_topic_update_literal(self.header)
479
+
480
+ # --- Python Interface ---
481
+
482
+ def __len__(self):
483
+ if not self.header:
484
+ raise RuntimeError('Not initialized!')
485
+ return self.header.n
486
+
487
+ def __bool__(self):
488
+ if not self.header:
489
+ return False
490
+ return self.header.n > 0
491
+
492
+ def __hash__(self):
493
+ if not self.header:
494
+ raise RuntimeError('Not initialized!')
495
+ return self.header.hash
496
+
497
+ def __eq__(self, Topic other):
498
+ if not self.header or not other.header:
499
+ raise RuntimeError('Not initialized!')
500
+ return self.value == other.value
501
+
502
+ def __repr__(self):
503
+ if not self.header:
504
+ return f'<{self.__class__.__name__} uninitialized>'
505
+ return f'<{self.__class__.__name__} {"Exact" if self.header.is_exact else "Generic"}>(value="{self.value}", n_parts={self.header.n})'
506
+
507
+ def __str__(self):
508
+ if not self.header:
509
+ raise RuntimeError('Not initialized!')
510
+ return self.value
511
+
512
+ def __iter__(self):
513
+ if not self.header:
514
+ raise RuntimeError('Not initialized!')
515
+
516
+ cdef evt_topic_part_variant* part = self.header.parts
517
+ while part:
518
+ yield TopicPart.c_from_header(part, False).c_cast()
519
+ part = part.header.next
520
+
521
+ def __getitem__(self, ssize_t idx):
522
+ if not self.header:
523
+ raise RuntimeError('Not initialized!')
524
+
525
+ cdef ssize_t n_parts = self.header.n
526
+ if idx < -n_parts:
527
+ raise IndexError(f'Index {idx} out of range!')
528
+ elif idx < 0:
529
+ idx += n_parts
530
+ elif idx >= n_parts:
531
+ raise IndexError(f'Index {idx} out of range!')
532
+
533
+ cdef ssize_t i = 0
534
+ cdef evt_topic_part_variant* part = self.header.parts
535
+
536
+ while part:
537
+ if i == idx:
538
+ return TopicPart.c_from_header(part, False).c_cast()
539
+ i += 1
540
+ part = part.header.next
541
+ raise IndexError('i')
542
+
543
+ def __add__(self, object topic):
544
+ if not self.header:
545
+ raise RuntimeError('Not initialized!')
546
+
547
+ cdef Topic aggregated = Topic.__new__(Topic)
548
+ cdef evt_topic_part_variant* other_part
549
+ cdef evt_topic_part_variant* tpart = self.header.parts
550
+ cdef size_t i, n
551
+ if isinstance(topic, Topic):
552
+ other_part = (<Topic> topic).header.parts
553
+ n = self.header.n
554
+ for i in range(n):
555
+ aggregated.c_append(<evt_topic_part_variant*> tpart + i)
556
+ n = (<Topic> topic).header.n
557
+ for i in range(n):
558
+ aggregated.c_append(<evt_topic_part_variant*> other_part + i)
559
+ elif isinstance(topic, TopicPart):
560
+ other_part = (<TopicPart> topic).header
561
+ n = self.header.n
562
+ for i in range(n):
563
+ aggregated.c_append(<evt_topic_part_variant*> tpart + i)
564
+ aggregated.c_append(other_part)
565
+ else:
566
+ raise TypeError(f'Can not add {topic} to {self}, expected ether a {Topic} or {TopicPart}')
567
+ aggregated.c_update_literal()
568
+ return aggregated
569
+
570
+ def __iadd__(self, object topic):
571
+ if not self.header:
572
+ raise RuntimeError('Not initialized!')
573
+
574
+ cdef evt_topic_part_variant* other_part
575
+ cdef size_t i, n
576
+ if isinstance(topic, Topic):
577
+ other_part = (<Topic> topic).header.parts
578
+ n = (<Topic> topic).header.n
579
+ for i in range(n):
580
+ self.c_append(<evt_topic_part_variant*> other_part + i)
581
+ elif isinstance(topic, TopicPart):
582
+ other_part = (<TopicPart> topic).header
583
+ self.c_append(other_part)
584
+ else:
585
+ raise TypeError(f'Can not add {topic} to {self}, expected ether a {Topic} or {TopicPart}')
586
+ self.c_update_literal()
587
+ return self
588
+
589
+ def __call__(self, **kwargs):
590
+ return self.format_map(kwargs, internalized=True, strict=False)
591
+
592
+ @classmethod
593
+ def from_parts(cls, topic_parts: Iterable[TopicPart]) -> Topic:
594
+ cdef Topic aggregated = Topic.__new__(Topic)
595
+ cdef TopicPart tpart
596
+ for tpart in topic_parts:
597
+ aggregated.c_append(tpart.header)
598
+ aggregated.c_update_literal()
599
+ return aggregated
600
+
601
+ @classmethod
602
+ def join(cls, topic_parts: Iterable[str]) -> Topic:
603
+ cdef Topic aggregated = Topic.__new__(Topic)
604
+ cdef str part
605
+ cdef Py_ssize_t literal_len
606
+ cdef const char* literal
607
+ cdef evt_topic* topic = aggregated.header
608
+
609
+ for tpart in topic_parts:
610
+ literal = PyUnicode_AsUTF8AndSize(tpart, &literal_len)
611
+ c_topic_append(topic, literal, literal_len, evt_topic_type.TOPIC_PART_EXACT)
612
+ c_topic_update_literal(topic)
613
+ return aggregated
614
+
615
+ cpdef Topic append(self, TopicPart topic_part):
616
+ if not self.header or not topic_part.header:
617
+ raise RuntimeError('Not initialized!')
618
+
619
+ cdef evt_topic_part_variant* part = topic_part.header
620
+ cdef evt_topic* topic = self.header
621
+ cdef evt_topic_part_variant* curr = topic.parts
622
+ cdef allocator_protocol* allocator = c_ap_protocol_from_ptr(self.header)
623
+ cdef char* literal
624
+ cdef size_t literal_len
625
+
626
+ # shortcut: just appending the buffer if it is owned and topic not using allocator.
627
+ if topic_part.owner and not allocator:
628
+ topic_part.owner = False
629
+ part = topic_part.header
630
+ part.header.next = NULL
631
+
632
+ if not curr:
633
+ topic.parts = part
634
+ else:
635
+ while curr.header.next:
636
+ curr = curr.header.next
637
+ curr.header.next = part
638
+ topic.n += 1
639
+ if part.header.ttype != evt_topic_type.TOPIC_PART_EXACT:
640
+ self.header.is_exact = 0
641
+ else:
642
+ self.c_append(part)
643
+ self.c_update_literal()
644
+ return self
645
+
646
+ cpdef TopicMatchResult match(self, Topic other):
647
+ if not self.header:
648
+ raise RuntimeError('Not initialized!')
649
+
650
+ cdef evt_topic_match* match_res = c_topic_match(self.header, other.header, NULL)
651
+ return TopicMatchResult.c_from_header(match_res, True)
652
+
653
+ def update_literal(self) -> Topic:
654
+ self.c_update_literal()
655
+ return self
656
+
657
+ cpdef Topic format_map(self, dict mapping, bint internalized=True, bint strict=False):
658
+ cdef evt_topic_part_variant* tpart = self.header.parts
659
+ cdef evt_topic_type ttype
660
+ cdef evt_topic* formatted = c_topic_new(NULL, 0, c_ap_protocol_from_ptr(self.header))
661
+ cdef str key
662
+ cdef const char* literal
663
+ cdef Py_ssize_t literal_len
664
+
665
+ while tpart:
666
+ ttype = tpart.header.ttype
667
+ # Case 1: For an exact part, simply append it.
668
+ if ttype == evt_topic_type.TOPIC_PART_EXACT:
669
+ c_topic_append(formatted, tpart.exact.part, tpart.exact.part_len, ttype)
670
+ # Case 2: For a named any part, check from the mapping dict.
671
+ elif ttype == evt_topic_type.TOPIC_PART_ANY:
672
+ key = PyUnicode_FromStringAndSize(tpart.any.name, tpart.any.name_len)
673
+ # Raise KeyError if not found.
674
+ if key not in mapping:
675
+ if strict:
676
+ c_topic_free(formatted)
677
+ raise KeyError(key)
678
+ else:
679
+ c_topic_append(formatted, tpart.any.name, tpart.any.name_len, evt_topic_type.TOPIC_PART_ANY)
680
+ else:
681
+ # Append the mapped value as an exact part.
682
+ literal = PyUnicode_AsUTF8AndSize(mapping[key], &literal_len)
683
+ c_topic_append(formatted, literal, literal_len, evt_topic_type.TOPIC_PART_EXACT)
684
+ else:
685
+ c_topic_free(formatted)
686
+ raise ValueError(f'Not supported topic type {TopicType(ttype)}')
687
+ tpart = tpart.header.next
688
+
689
+ c_topic_update_literal(formatted)
690
+ # If internalized, the TopicPart will not own the literal buffers.
691
+ # Requested or not, in any way, the topic literal must be added to the internal map.
692
+ return Topic.c_from_header(formatted, not internalized)
693
+
694
+ def format(self, **kwargs) -> Topic:
695
+ return self.format_map(kwargs, internalized=True, strict=False)
696
+
697
+ property value:
698
+ def __get__(self):
699
+ if not self.header:
700
+ raise RuntimeError('Not initialized!')
701
+
702
+ cdef str literal = PyUnicode_FromStringAndSize(self.header.key, self.header.key_len)
703
+ return literal
704
+
705
+ def __set__(self, str value):
706
+ if not self.header:
707
+ raise RuntimeError('Not initialized!')
708
+
709
+ cdef Py_ssize_t topic_length
710
+ cdef const char* topic_ptr = PyUnicode_AsUTF8AndSize(value, &topic_length)
711
+ cdef int assign_ret = c_topic_assign(self.header, topic_ptr, topic_length)
712
+ if assign_ret:
713
+ raise ValueError(f'Failed to assign topic "{value}", check if syntax is correct!')
714
+
715
+ property is_exact:
716
+ def __get__(self) -> bint:
717
+ if not self.header:
718
+ raise RuntimeError('Not initialized!')
719
+ return self.header.is_exact
720
+
721
+ property addr:
722
+ def __get__(self) -> uintptr_t:
723
+ if self.header:
724
+ return <uintptr_t> self.header
725
+ return 0