PyEventEngine 0.6.0.post3__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- event_engine/__infra__.pxd +2 -0
- event_engine/__init__.pxd +2 -0
- event_engine/__init__.py +37 -0
- event_engine/base/__infra__.pxd +9 -0
- event_engine/base/__init__.pxd +9 -0
- event_engine/base/__init__.py +82 -0
- event_engine/base/c_allocator_protocol.c +10817 -0
- event_engine/base/c_allocator_protocol.cp313-win_amd64.pyd +0 -0
- event_engine/base/c_allocator_protocol.h +23 -0
- event_engine/base/c_allocator_protocol.pxd +31 -0
- event_engine/base/c_allocator_protocol.pyx +99 -0
- event_engine/capi/__infra__.pxd +189 -0
- event_engine/capi/__init__.pxd +189 -0
- event_engine/capi/__init__.py +84 -0
- event_engine/capi/c_engine.c +28711 -0
- event_engine/capi/c_engine.cp313-win_amd64.pyd +0 -0
- event_engine/capi/c_engine.h +393 -0
- event_engine/capi/c_engine.pxd +84 -0
- event_engine/capi/c_engine.pyi +424 -0
- event_engine/capi/c_engine.pyx +560 -0
- event_engine/capi/c_event.c +19921 -0
- event_engine/capi/c_event.cp313-win_amd64.pyd +0 -0
- event_engine/capi/c_event.h +342 -0
- event_engine/capi/c_event.pxd +159 -0
- event_engine/capi/c_event.pyi +226 -0
- event_engine/capi/c_event.pyx +387 -0
- event_engine/capi/c_topic.c +30978 -0
- event_engine/capi/c_topic.cp313-win_amd64.pyd +0 -0
- event_engine/capi/c_topic.h +1007 -0
- event_engine/capi/c_topic.pxd +147 -0
- event_engine/capi/c_topic.pyi +580 -0
- event_engine/capi/c_topic.pyx +725 -0
- event_engine/capi/fallback_engine.py +670 -0
- event_engine/includes/event_engine/base/c_allocator_protocol.c +10817 -0
- event_engine/includes/event_engine/base/c_allocator_protocol.h +23 -0
- event_engine/includes/event_engine/capi/c_engine.c +28711 -0
- event_engine/includes/event_engine/capi/c_engine.h +393 -0
- event_engine/includes/event_engine/capi/c_event.c +19921 -0
- event_engine/includes/event_engine/capi/c_event.h +342 -0
- event_engine/includes/event_engine/capi/c_topic.c +30978 -0
- event_engine/includes/event_engine/capi/c_topic.h +1007 -0
- event_engine/native/__init__.py +60 -0
- event_engine/native/_event.py +420 -0
- event_engine/native/_topic.py +171 -0
- event_engine/native/engine.py +746 -0
- event_engine/native/event.py +467 -0
- event_engine/native/topic.py +945 -0
- pyeventengine-0.6.0.post3.dist-info/METADATA +189 -0
- pyeventengine-0.6.0.post3.dist-info/RECORD +52 -0
- pyeventengine-0.6.0.post3.dist-info/WHEEL +5 -0
- pyeventengine-0.6.0.post3.dist-info/licenses/LICENSE +21 -0
- 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
|