PyEventEngine 0.2.1__tar.gz → 0.3.0.post1__tar.gz
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.
- {PyEventEngine-0.2.1 → pyeventengine-0.3.0.post1}/EventEngine/Core/__init__.py +14 -12
- PyEventEngine-0.2.1/EventEngine/Core/_Event.py → pyeventengine-0.3.0.post1/EventEngine/Core/_event.py +122 -78
- PyEventEngine-0.2.1/EventEngine/Core/_Topic.py → pyeventengine-0.3.0.post1/EventEngine/Core/_topic.py +53 -27
- pyeventengine-0.3.0.post1/EventEngine/Core/_topic_c.py +94 -0
- pyeventengine-0.3.0.post1/EventEngine/__init__.py +3 -0
- {PyEventEngine-0.2.1 → pyeventengine-0.3.0.post1}/EventEngine/cpp/topic_api.cpp +97 -82
- {PyEventEngine-0.2.1/PyEventEngine.egg-info → pyeventengine-0.3.0.post1}/PKG-INFO +3 -1
- {PyEventEngine-0.2.1 → pyeventengine-0.3.0.post1/PyEventEngine.egg-info}/PKG-INFO +3 -1
- {PyEventEngine-0.2.1 → pyeventengine-0.3.0.post1}/PyEventEngine.egg-info/SOURCES.txt +3 -3
- {PyEventEngine-0.2.1 → pyeventengine-0.3.0.post1}/setup.py +2 -0
- PyEventEngine-0.2.1/EventEngine/Core/_Topic_c.py +0 -171
- PyEventEngine-0.2.1/EventEngine/__init__.py +0 -3
- {PyEventEngine-0.2.1 → pyeventengine-0.3.0.post1}/LICENSE +0 -0
- {PyEventEngine-0.2.1 → pyeventengine-0.3.0.post1}/PyEventEngine.egg-info/dependency_links.txt +0 -0
- {PyEventEngine-0.2.1 → pyeventengine-0.3.0.post1}/PyEventEngine.egg-info/top_level.txt +0 -0
- {PyEventEngine-0.2.1 → pyeventengine-0.3.0.post1}/README.md +0 -0
- {PyEventEngine-0.2.1 → pyeventengine-0.3.0.post1}/setup.cfg +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import logging
|
|
4
2
|
import sys
|
|
5
3
|
import time
|
|
6
4
|
|
|
7
|
-
__all__ = ['set_logger', 'LOG_LEVEL_EVENT', 'Topic', 'RegularTopic', 'PatternTopic', 'EventHook', 'EventEngine', 'LOGGER']
|
|
5
|
+
__all__ = ['set_logger', 'LOG_LEVEL_EVENT', 'Topic', 'RegularTopic', 'PatternTopic', 'EventHook', 'EventEngine', 'LOGGER', 'use_cpp_override']
|
|
8
6
|
LOGGER: logging.Logger | None = None
|
|
7
|
+
DEBUG = False
|
|
9
8
|
LOG_LEVEL = logging.INFO
|
|
10
|
-
LOG_LEVEL_EVENT = LOG_LEVEL -
|
|
9
|
+
LOG_LEVEL_EVENT = LOG_LEVEL - 5
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
class ColoredFormatter(logging.Formatter):
|
|
@@ -83,15 +82,18 @@ def set_logger(logger: logging.Logger):
|
|
|
83
82
|
global LOGGER
|
|
84
83
|
LOGGER = logger
|
|
85
84
|
|
|
85
|
+
_event.LOGGER = LOGGER.getChild('Event')
|
|
86
86
|
|
|
87
|
-
_ = get_logger()
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
try:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
def use_cpp_override():
|
|
89
|
+
try:
|
|
90
|
+
from ._topic_c import Topic, RegularTopic, PatternTopic
|
|
91
|
+
from . import _topic as _Topic_native
|
|
92
|
+
except Exception as _:
|
|
93
|
+
LOGGER.warning(_)
|
|
94
94
|
|
|
95
|
-
LOGGER.warning(_)
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
_ = get_logger()
|
|
97
|
+
|
|
98
|
+
from ._topic import Topic, RegularTopic, PatternTopic
|
|
99
|
+
from ._event import EventHook, EventEngine
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import datetime
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import enum
|
|
3
|
+
import inspect
|
|
6
4
|
import time
|
|
7
5
|
import traceback
|
|
8
|
-
from
|
|
6
|
+
from collections import deque
|
|
7
|
+
from logging import Logger
|
|
8
|
+
from threading import Thread, Semaphore
|
|
9
|
+
from typing import Iterable, TypedDict, NotRequired, Callable
|
|
10
|
+
|
|
11
|
+
from . import LOGGER, LOG_LEVEL_EVENT, Topic, DEBUG
|
|
12
|
+
|
|
13
|
+
LOGGER = LOGGER.getChild('Event')
|
|
14
|
+
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
class EventDict(TypedDict):
|
|
17
|
+
topic: str
|
|
18
|
+
args: NotRequired[tuple]
|
|
19
|
+
kwargs: NotRequired[dict]
|
|
11
20
|
|
|
12
21
|
|
|
13
22
|
class EventHookBase(object):
|
|
@@ -17,31 +26,19 @@ class EventHookBase(object):
|
|
|
17
26
|
and a list of handler to process data
|
|
18
27
|
"""
|
|
19
28
|
|
|
20
|
-
def __init__(self, topic: Topic,
|
|
29
|
+
def __init__(self, topic: Topic, logger: Logger = None, max_size: int = None):
|
|
21
30
|
self.topic = topic
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
self.handlers = [handler]
|
|
25
|
-
elif isinstance(handler, Iterable):
|
|
26
|
-
self.handlers = []
|
|
27
|
-
for hdl in handler:
|
|
28
|
-
if callable(hdl):
|
|
29
|
-
self.handlers.append(hdl)
|
|
30
|
-
else:
|
|
31
|
-
raise ValueError(f'invalid handler {hdl}')
|
|
32
|
-
elif handler is None:
|
|
33
|
-
self.handlers = []
|
|
34
|
-
else:
|
|
35
|
-
raise ValueError(f'Invalid handler {handler}')
|
|
31
|
+
self.logger = LOGGER.getChild(f'EventHook.{topic}') if logger is None else logger
|
|
32
|
+
self.handlers: deque[Callable] = deque(maxlen=max_size)
|
|
36
33
|
|
|
37
34
|
def __call__(self, *args, **kwargs):
|
|
38
35
|
self.trigger(topic=self.topic, args=args, kwargs=kwargs)
|
|
39
36
|
|
|
40
|
-
def __iadd__(self, handler):
|
|
37
|
+
def __iadd__(self, handler: Callable):
|
|
41
38
|
self.add_handler(handler)
|
|
42
39
|
return self
|
|
43
40
|
|
|
44
|
-
def __isub__(self, handler):
|
|
41
|
+
def __isub__(self, handler: Callable):
|
|
45
42
|
self.remove_handler(handler)
|
|
46
43
|
return self
|
|
47
44
|
|
|
@@ -56,88 +53,134 @@ class EventHookBase(object):
|
|
|
56
53
|
try:
|
|
57
54
|
try:
|
|
58
55
|
handler(topic=topic, *args, **kwargs)
|
|
59
|
-
except TypeError:
|
|
60
|
-
|
|
56
|
+
except TypeError as e:
|
|
57
|
+
if e.__str__().endswith("unexpected keyword argument 'topic'"):
|
|
58
|
+
handler(*args, **kwargs)
|
|
59
|
+
else:
|
|
60
|
+
raise e
|
|
61
61
|
except Exception as _:
|
|
62
|
-
|
|
62
|
+
self.logger.error(traceback.format_exc())
|
|
63
63
|
|
|
64
|
-
def add_handler(self, handler:
|
|
64
|
+
def add_handler(self, handler: Callable):
|
|
65
|
+
if handler in self.handlers:
|
|
66
|
+
LOGGER.warning(f'Handler {handler} already in {self}. This action might cause it to trigger twice.')
|
|
65
67
|
self.handlers.append(handler)
|
|
66
68
|
|
|
67
|
-
def remove_handler(self, handler:
|
|
68
|
-
|
|
69
|
+
def remove_handler(self, handler: Callable):
|
|
70
|
+
try:
|
|
71
|
+
self.handlers.remove(handler)
|
|
72
|
+
except ValueError as e:
|
|
73
|
+
self.logger.error(f'Handler {handler} not found in {self}.')
|
|
69
74
|
|
|
70
75
|
|
|
71
76
|
class EventHook(EventHookBase):
|
|
72
|
-
def __init__(self, topic, handler):
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
def __init__(self, topic: Topic, logger: Logger = None, max_size: int = None, handler: list[Callable] | Callable | None = None):
|
|
78
|
+
super().__init__(topic=topic, logger=logger, max_size=max_size)
|
|
79
|
+
self.with_topic: deque[bool] = deque()
|
|
80
|
+
|
|
81
|
+
if handler is None:
|
|
82
|
+
pass
|
|
83
|
+
elif callable(handler):
|
|
84
|
+
self.add_handler(handler)
|
|
85
|
+
elif isinstance(handler, Iterable):
|
|
86
|
+
for _handler in handler:
|
|
87
|
+
self.add_handler(handler=_handler)
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError(f'Invalid handler {handler}, expect a Callable or a list of Callable.')
|
|
75
90
|
|
|
76
91
|
def trigger(self, topic: Topic, args: tuple = None, kwargs: dict = None):
|
|
77
92
|
ts = time.time()
|
|
78
|
-
|
|
79
93
|
if args is None:
|
|
80
94
|
args = ()
|
|
81
95
|
|
|
82
96
|
if kwargs is None:
|
|
83
97
|
kwargs = {}
|
|
84
98
|
|
|
85
|
-
for handler in self.handlers:
|
|
99
|
+
for handler, with_topic in zip(self.handlers, self.with_topic):
|
|
86
100
|
try:
|
|
87
|
-
|
|
101
|
+
if with_topic:
|
|
88
102
|
handler(topic=topic, *args, **kwargs)
|
|
89
|
-
|
|
103
|
+
else:
|
|
90
104
|
handler(*args, **kwargs)
|
|
91
105
|
except Exception as _:
|
|
92
106
|
self.logger.error(traceback.format_exc())
|
|
93
107
|
|
|
94
|
-
|
|
108
|
+
if DEBUG:
|
|
109
|
+
self.logger.log(LOG_LEVEL_EVENT, f'EventHook {self.topic} tasks triggered {len(self.handlers):,} handlers, complete in {(time.time() - ts) * 1000:.3f}ms.')
|
|
110
|
+
|
|
111
|
+
def add_handler(self, handler: Callable, with_topic: bool = None):
|
|
112
|
+
sig = inspect.signature(handler)
|
|
113
|
+
|
|
114
|
+
if with_topic is None:
|
|
115
|
+
for param in sig.parameters.values():
|
|
116
|
+
if param.name == 'topic' or param.kind == param.VAR_KEYWORD:
|
|
117
|
+
with_topic = True
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
super().add_handler(handler=handler)
|
|
121
|
+
self.with_topic.append(with_topic)
|
|
122
|
+
|
|
123
|
+
def remove_handler(self, handler: Callable):
|
|
124
|
+
try:
|
|
125
|
+
idx = self.handlers.index(handler)
|
|
126
|
+
self.handlers.__delitem__(idx)
|
|
127
|
+
self.with_topic.__delitem__(idx)
|
|
128
|
+
except ValueError as e:
|
|
129
|
+
pass
|
|
95
130
|
|
|
96
131
|
|
|
97
132
|
class EventEngineBase(object):
|
|
98
133
|
EventHook = EventHook
|
|
99
134
|
|
|
100
|
-
def __init__(self,
|
|
101
|
-
self.
|
|
102
|
-
|
|
103
|
-
self.
|
|
104
|
-
self.
|
|
135
|
+
def __init__(self, logger: Logger = None, buffer_size: int = 0):
|
|
136
|
+
self.logger = LOGGER.getChild(f'EventEngine') if logger is None else logger
|
|
137
|
+
self._buffer_size = buffer_size
|
|
138
|
+
self._put_lock = Semaphore(self._buffer_size)
|
|
139
|
+
self._get_lock = Semaphore(0)
|
|
140
|
+
self._deque: deque[EventDict] = deque(maxlen=buffer_size if buffer_size else None)
|
|
105
141
|
self._active: bool = False
|
|
106
|
-
self._engine:
|
|
142
|
+
self._engine: Thread = Thread(target=self._run, name='EventEngine')
|
|
107
143
|
self._event_hooks: dict[Topic, EventHook] = {}
|
|
108
144
|
|
|
145
|
+
if buffer_size and buffer_size < 8:
|
|
146
|
+
self.logger.info(f'buffer_size={buffer_size} too small. This might cause a dead lock.')
|
|
147
|
+
|
|
109
148
|
def _run(self) -> None:
|
|
110
149
|
"""
|
|
111
150
|
Get event from queue and then process it.
|
|
112
151
|
"""
|
|
113
152
|
while self._active:
|
|
153
|
+
self._get_lock.acquire(blocking=True, timeout=None)
|
|
154
|
+
|
|
114
155
|
try:
|
|
115
|
-
event_dict = self.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
156
|
+
event_dict = self._deque.pop()
|
|
157
|
+
except IndexError as e:
|
|
158
|
+
if not self._active:
|
|
159
|
+
return
|
|
160
|
+
raise e
|
|
161
|
+
|
|
162
|
+
topic = event_dict['topic']
|
|
163
|
+
args = event_dict.get('args', ())
|
|
164
|
+
kwargs = event_dict.get('kwargs', {})
|
|
165
|
+
self._process(topic=topic, *args, **kwargs)
|
|
166
|
+
|
|
167
|
+
if self._buffer_size:
|
|
168
|
+
self._put_lock.release()
|
|
122
169
|
|
|
123
170
|
def _process(self, topic: str, *args, **kwargs) -> None:
|
|
124
171
|
"""
|
|
125
172
|
distribute data to registered event hook in the order of registration
|
|
126
173
|
"""
|
|
127
|
-
for
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
event_hook = self._event_hooks.get(_)
|
|
131
|
-
|
|
132
|
-
if event_hook is not None:
|
|
133
|
-
event_hook.trigger(topic=m, args=args, kwargs=kwargs)
|
|
174
|
+
for event_topic, event_hook in self._event_hooks.items():
|
|
175
|
+
if matched_topic := event_topic.match(topic=topic):
|
|
176
|
+
event_hook.trigger(topic=matched_topic, args=args, kwargs=kwargs)
|
|
134
177
|
|
|
135
178
|
def start(self) -> None:
|
|
136
179
|
"""
|
|
137
180
|
Start event engine to process events and generate timer events.
|
|
138
181
|
"""
|
|
139
182
|
if self._active:
|
|
140
|
-
|
|
183
|
+
self.logger.warning(f'{self} already started!')
|
|
141
184
|
return
|
|
142
185
|
|
|
143
186
|
self._active = True
|
|
@@ -148,10 +191,11 @@ class EventEngineBase(object):
|
|
|
148
191
|
Stop event engine.
|
|
149
192
|
"""
|
|
150
193
|
if not self._active:
|
|
151
|
-
|
|
194
|
+
self.logger.warning('EventEngine already stopped!')
|
|
152
195
|
return
|
|
153
196
|
|
|
154
197
|
self._active = False
|
|
198
|
+
self._get_lock.release()
|
|
155
199
|
self._engine.join()
|
|
156
200
|
|
|
157
201
|
def put(self, topic: str | Topic, block: bool = True, timeout: float = None, *args, **kwargs):
|
|
@@ -181,6 +225,9 @@ class EventEngineBase(object):
|
|
|
181
225
|
elif not isinstance(topic, str):
|
|
182
226
|
raise ValueError(f'Invalid topic {topic}')
|
|
183
227
|
|
|
228
|
+
if self._buffer_size:
|
|
229
|
+
self._put_lock.acquire()
|
|
230
|
+
|
|
184
231
|
event_dict = {'topic': topic}
|
|
185
232
|
|
|
186
233
|
if args is not None:
|
|
@@ -189,7 +236,9 @@ class EventEngineBase(object):
|
|
|
189
236
|
if kwargs is not None:
|
|
190
237
|
event_dict['kwargs'] = kwargs
|
|
191
238
|
|
|
192
|
-
self.
|
|
239
|
+
self._deque.append(event_dict)
|
|
240
|
+
|
|
241
|
+
self._get_lock.release()
|
|
193
242
|
|
|
194
243
|
def register_hook(self, hook: EventHook) -> None:
|
|
195
244
|
"""
|
|
@@ -208,7 +257,7 @@ class EventEngineBase(object):
|
|
|
208
257
|
if topic in self._event_hooks:
|
|
209
258
|
self._event_hooks.pop(topic)
|
|
210
259
|
|
|
211
|
-
def register_handler(self, topic: Topic, handler: Iterable[
|
|
260
|
+
def register_handler(self, topic: Topic, handler: Iterable[Callable] | Callable) -> None:
|
|
212
261
|
"""
|
|
213
262
|
Register one or more handler for a specific topic
|
|
214
263
|
"""
|
|
@@ -217,11 +266,11 @@ class EventEngineBase(object):
|
|
|
217
266
|
raise TypeError(f'Invalid topic {topic}')
|
|
218
267
|
|
|
219
268
|
if topic not in self._event_hooks:
|
|
220
|
-
self._event_hooks[topic] = self.EventHook(topic=topic, handler=handler)
|
|
269
|
+
self._event_hooks[topic] = self.EventHook(topic=topic, handler=handler, logger=self.logger.getChild(topic.value))
|
|
221
270
|
else:
|
|
222
271
|
self._event_hooks[topic].add_handler(handler)
|
|
223
272
|
|
|
224
|
-
def unregister_handler(self, topic: Topic, handler:
|
|
273
|
+
def unregister_handler(self, topic: Topic, handler: Callable) -> None:
|
|
225
274
|
"""
|
|
226
275
|
Unregister an existing handler function.
|
|
227
276
|
"""
|
|
@@ -229,23 +278,18 @@ class EventEngineBase(object):
|
|
|
229
278
|
self._event_hooks[topic].remove_handler(handler=handler)
|
|
230
279
|
|
|
231
280
|
@property
|
|
232
|
-
def
|
|
233
|
-
return self.
|
|
234
|
-
|
|
235
|
-
@max_size.setter
|
|
236
|
-
def max_size(self, size: int):
|
|
237
|
-
self._max_size = size
|
|
238
|
-
self._queue.maxsize = size
|
|
281
|
+
def buffer_size(self):
|
|
282
|
+
return self._buffer_size
|
|
239
283
|
|
|
240
284
|
|
|
241
285
|
class EventEngine(EventEngineBase):
|
|
242
286
|
EventHook = EventHook
|
|
243
287
|
|
|
244
|
-
def __init__(self,
|
|
245
|
-
super().__init__(
|
|
246
|
-
self.timer: dict[float | str,
|
|
288
|
+
def __init__(self, buffer_size=0):
|
|
289
|
+
super().__init__(buffer_size=buffer_size)
|
|
290
|
+
self.timer: dict[float | str, Thread] = {}
|
|
247
291
|
|
|
248
|
-
def register_handler(self, topic, handler):
|
|
292
|
+
def register_handler(self, topic: Topic | str | enum.Enum, handler: Callable):
|
|
249
293
|
topic = Topic.cast(topic)
|
|
250
294
|
super().register_handler(topic=topic, handler=handler)
|
|
251
295
|
|
|
@@ -277,20 +321,20 @@ class EventEngine(EventEngineBase):
|
|
|
277
321
|
|
|
278
322
|
if interval == 1:
|
|
279
323
|
topic = Topic('EventEngine.Internal.Timer.Second')
|
|
280
|
-
timer =
|
|
324
|
+
timer = Thread(target=self._second_timer, kwargs={'topic': topic})
|
|
281
325
|
elif interval == 60:
|
|
282
326
|
topic = Topic('EventEngine.Internal.Timer.Minute')
|
|
283
|
-
timer =
|
|
327
|
+
timer = Thread(target=self._minute_timer, kwargs={'topic': topic})
|
|
284
328
|
else:
|
|
285
329
|
topic = Topic(f'EventEngine.Internal.Timer.{interval}')
|
|
286
|
-
timer =
|
|
330
|
+
timer = Thread(target=self._run_timer, kwargs={'interval': interval, 'topic': topic, 'activate_time': activate_time})
|
|
287
331
|
|
|
288
332
|
if interval not in self.timer:
|
|
289
333
|
self.timer[interval] = timer
|
|
290
334
|
timer.start()
|
|
291
335
|
else:
|
|
292
336
|
if activate_time is not None:
|
|
293
|
-
|
|
337
|
+
self.logger.debug(f'Timer thread with interval [{datetime.timedelta(seconds=interval)}] already initialized! Argument [activate_time] takes no effect!')
|
|
294
338
|
|
|
295
339
|
return topic
|
|
296
340
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import re
|
|
4
2
|
from enum import Enum
|
|
5
3
|
from string import Formatter
|
|
4
|
+
from typing import Self, Type
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class Topic(dict):
|
|
@@ -30,29 +29,33 @@ class Topic(dict):
|
|
|
30
29
|
def __hash__(self):
|
|
31
30
|
return self.value.__hash__()
|
|
32
31
|
|
|
33
|
-
def match(self, topic: str) ->
|
|
32
|
+
def match(self, topic: str) -> Self | None:
|
|
34
33
|
if self._value == topic:
|
|
35
|
-
return self.__class__(topic=topic)
|
|
34
|
+
# return self.__class__(topic=topic)
|
|
35
|
+
return self
|
|
36
36
|
else:
|
|
37
37
|
return None
|
|
38
38
|
|
|
39
39
|
@classmethod
|
|
40
|
-
def cast(cls, topic:
|
|
41
|
-
if isinstance(topic,
|
|
42
|
-
topic = topic.value
|
|
43
|
-
elif isinstance(topic, Topic):
|
|
40
|
+
def cast(cls, topic: Self | str | Enum, dtype: Type[Self] = None) -> Self:
|
|
41
|
+
if isinstance(topic, cls):
|
|
44
42
|
return topic
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
re.
|
|
51
|
-
|
|
43
|
+
elif isinstance(topic, Enum):
|
|
44
|
+
t = topic.value
|
|
45
|
+
return cls.cast(t)
|
|
46
|
+
elif isinstance(topic, str):
|
|
47
|
+
if dtype is None:
|
|
48
|
+
if re.search(r'{(.+?)}', topic):
|
|
49
|
+
t = PatternTopic(pattern=topic)
|
|
50
|
+
elif '*' in topic or '+' in topic or '|' in topic:
|
|
51
|
+
re.compile(pattern=topic)
|
|
52
|
+
t = RegularTopic(pattern=topic)
|
|
53
|
+
else:
|
|
54
|
+
t = Topic(topic=topic)
|
|
52
55
|
else:
|
|
53
|
-
t =
|
|
56
|
+
t = dtype(topic)
|
|
54
57
|
else:
|
|
55
|
-
|
|
58
|
+
raise NotImplementedError(f'Can not cast {topic} into {cls}.')
|
|
56
59
|
|
|
57
60
|
return t
|
|
58
61
|
|
|
@@ -89,18 +92,41 @@ class PatternTopic(Topic):
|
|
|
89
92
|
def __call__(self, **kwargs):
|
|
90
93
|
return self.format_map(kwargs)
|
|
91
94
|
|
|
95
|
+
# @classmethod
|
|
96
|
+
# def extract_mapping(cls, target: str, pattern: str):
|
|
97
|
+
# pattern = re.escape(pattern)
|
|
98
|
+
# regex = re.sub(r'\\{(.+?)\\}', r'(?P<_\1>.+)', pattern)
|
|
99
|
+
# match = re.match(regex, target)
|
|
100
|
+
# if match:
|
|
101
|
+
# values = list(match.groups())
|
|
102
|
+
# keys = re.findall(r'\\{(.+?)\\}', pattern)
|
|
103
|
+
# m = dict(zip(keys, values))
|
|
104
|
+
# return m
|
|
105
|
+
# else:
|
|
106
|
+
# raise Topic.Error(f'pattern {pattern} not in string {target} found!')
|
|
107
|
+
|
|
92
108
|
@classmethod
|
|
93
109
|
def extract_mapping(cls, target: str, pattern: str):
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
dictionary = {}
|
|
111
|
+
|
|
112
|
+
result_parts = target.split('.')
|
|
113
|
+
pattern_parts = pattern.split('.')
|
|
114
|
+
|
|
115
|
+
# Check if the number of parts in result and pattern are the same
|
|
116
|
+
if len(result_parts) != len(pattern_parts):
|
|
117
|
+
return dictionary
|
|
118
|
+
|
|
119
|
+
# Generate the mapping dictionary
|
|
120
|
+
for result_part, pattern_part in zip(result_parts, pattern_parts): # type: str
|
|
121
|
+
if pattern_part[0] == '{' and pattern_part[-1] == '}':
|
|
122
|
+
content = pattern_part[1:-1]
|
|
123
|
+
dictionary[content] = result_part
|
|
124
|
+
else:
|
|
125
|
+
if result_part != pattern_part:
|
|
126
|
+
dictionary.clear()
|
|
127
|
+
break
|
|
128
|
+
|
|
129
|
+
return dictionary
|
|
104
130
|
|
|
105
131
|
def format_map(self, mapping: dict) -> Topic:
|
|
106
132
|
for key in self.keys():
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import pathlib
|
|
5
|
+
import platform
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from ._topic import Topic, RegularTopic as RegularTopicBase, PatternTopic as PatternTopicBase
|
|
9
|
+
|
|
10
|
+
__all__ = ['Topic', 'RegularTopic', 'PatternTopic']
|
|
11
|
+
|
|
12
|
+
topic_lib = None
|
|
13
|
+
|
|
14
|
+
if platform.system() == 'Windows':
|
|
15
|
+
package_name = 'topic_api.(.*).pyd'
|
|
16
|
+
elif platform.system() == 'Darwin':
|
|
17
|
+
package_name = '^topic_api(.*).so$'
|
|
18
|
+
else:
|
|
19
|
+
package_name = '^topic_api(.*).so$'
|
|
20
|
+
|
|
21
|
+
ROOT_DIR = pathlib.Path(__file__).parent.parent
|
|
22
|
+
ENCODING = 'utf-8'
|
|
23
|
+
|
|
24
|
+
for _ in ROOT_DIR.iterdir():
|
|
25
|
+
if lib_path := re.search(package_name, _.name):
|
|
26
|
+
topic_lib = ctypes.CDLL(str(_))
|
|
27
|
+
break
|
|
28
|
+
|
|
29
|
+
# Load the shared library
|
|
30
|
+
if topic_lib is None:
|
|
31
|
+
raise ImportError(f'EventEngine.Topic c extension {package_name} not found in {ROOT_DIR}! Fallback to native lib!')
|
|
32
|
+
|
|
33
|
+
# Function prototypes
|
|
34
|
+
# topic_lib.delete_vector.restype = [ctypes.POINTER]
|
|
35
|
+
topic_lib.get_vector_value.restype = ctypes.c_char_p
|
|
36
|
+
topic_lib.is_regular_match.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
|
|
37
|
+
topic_lib.is_regular_match.restype = ctypes.c_int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Define the RegularTopic class in Python
|
|
41
|
+
class RegularTopic(RegularTopicBase):
|
|
42
|
+
"""
|
|
43
|
+
topic in regular expression. e.g. "TickData.(.+).((SZ)|(SH)).((Realtime)|(History))"
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def match(self, topic: str):
|
|
47
|
+
match = topic_lib.is_regular_match(topic.encode(ENCODING), self._value.encode(ENCODING))
|
|
48
|
+
|
|
49
|
+
if match:
|
|
50
|
+
return Topic(topic=topic, pattern=self.value)
|
|
51
|
+
else:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Define the PatternTopic class in Python
|
|
56
|
+
class PatternTopic(PatternTopicBase):
|
|
57
|
+
"""
|
|
58
|
+
topic for event hook. e.g. "TickData.{symbol}.{market}.{flag}"
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def match(self, topic: str):
|
|
62
|
+
mapping = self.extract_mapping(target=topic, pattern=self._value, encoding=ENCODING)
|
|
63
|
+
|
|
64
|
+
if mapping:
|
|
65
|
+
return Topic(topic, pattern=self._value, **mapping)
|
|
66
|
+
else:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def extract_mapping(cls, target: str, pattern: str, encoding: str = ENCODING) -> dict[str, str]:
|
|
71
|
+
# noinspection PyArgumentList
|
|
72
|
+
keys_ptr, values_ptr = ctypes.POINTER(ctypes.c_void_p)(), ctypes.POINTER(ctypes.c_void_p)()
|
|
73
|
+
mapping = {}
|
|
74
|
+
|
|
75
|
+
topic_lib.extract_mapping(
|
|
76
|
+
target.encode(encoding),
|
|
77
|
+
pattern.encode(encoding),
|
|
78
|
+
ctypes.byref(keys_ptr),
|
|
79
|
+
ctypes.byref(values_ptr)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
for i in range(topic_lib.vector_size(ctypes.byref(keys_ptr))):
|
|
83
|
+
key = topic_lib.get_vector_value(ctypes.byref(keys_ptr), i)
|
|
84
|
+
value = topic_lib.get_vector_value(ctypes.byref(values_ptr), i)
|
|
85
|
+
# print(key, value)
|
|
86
|
+
mapping[key.decode(encoding)] = value.decode(encoding)
|
|
87
|
+
|
|
88
|
+
# topic_lib.delete_vector(ctypes.byref(keys_ptr))
|
|
89
|
+
# topic_lib.delete_vector(ctypes.byref(values_ptr))
|
|
90
|
+
|
|
91
|
+
del keys_ptr
|
|
92
|
+
del values_ptr
|
|
93
|
+
|
|
94
|
+
return mapping
|
|
@@ -2,29 +2,30 @@
|
|
|
2
2
|
#include <regex>
|
|
3
3
|
#include <string>
|
|
4
4
|
#include <unordered_map>
|
|
5
|
+
#include <utility>
|
|
5
6
|
#include <vector>
|
|
6
7
|
|
|
7
8
|
extern "C" {
|
|
8
|
-
|
|
9
|
-
#include <stdlib.h>
|
|
9
|
+
#include <stdlib.h>
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
class Topic : public std::unordered_map<std::string, std::string> {
|
|
13
13
|
public:
|
|
14
14
|
class Error : public std::exception {
|
|
15
15
|
public:
|
|
16
|
-
explicit Error(
|
|
17
|
-
|
|
16
|
+
explicit Error(std::string msg) : message(std::move(msg)) {}
|
|
17
|
+
|
|
18
|
+
[[nodiscard]] const char *what() const noexcept override { return message.c_str(); }
|
|
18
19
|
|
|
19
20
|
private:
|
|
20
21
|
std::string message;
|
|
21
22
|
};
|
|
22
23
|
|
|
23
|
-
explicit Topic(
|
|
24
|
+
explicit Topic(std::string topic) : _value(std::move(topic)) {}
|
|
24
25
|
|
|
25
26
|
std::string value() const { return _value; }
|
|
26
27
|
|
|
27
|
-
Topic match(const std::string&
|
|
28
|
+
virtual Topic match(const std::string &topic) const {
|
|
28
29
|
if (_value == topic) {
|
|
29
30
|
return Topic(topic);
|
|
30
31
|
} else {
|
|
@@ -37,11 +38,20 @@ public:
|
|
|
37
38
|
|
|
38
39
|
class RegularTopic : public Topic {
|
|
39
40
|
public:
|
|
40
|
-
explicit RegularTopic(const std::string&
|
|
41
|
+
explicit RegularTopic(const std::string &pattern) : Topic(pattern) {}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
std::regex regex(
|
|
43
|
+
static int is_match(const std::string &topic, const std::string &pattern) {
|
|
44
|
+
std::regex regex(pattern);
|
|
44
45
|
if (std::regex_match(topic, regex)) {
|
|
46
|
+
return 1;
|
|
47
|
+
} else {
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Topic match(const std::string &topic) const override {
|
|
53
|
+
int result = is_match(topic, _value);
|
|
54
|
+
if (result == 1) {
|
|
45
55
|
Topic match(topic);
|
|
46
56
|
match["pattern"] = _value;
|
|
47
57
|
return match;
|
|
@@ -53,14 +63,14 @@ public:
|
|
|
53
63
|
|
|
54
64
|
class PatternTopic : public Topic {
|
|
55
65
|
public:
|
|
56
|
-
explicit PatternTopic(const std::string&
|
|
66
|
+
explicit PatternTopic(const std::string &pattern) : Topic(pattern) {}
|
|
57
67
|
|
|
58
|
-
Topic format_map(const std::unordered_map<std::string, std::string
|
|
68
|
+
Topic format_map(const std::unordered_map<std::string, std::string> &mapping) const {
|
|
59
69
|
std::string result = _value;
|
|
60
70
|
|
|
61
71
|
std::vector<std::string> contentVec = keys();
|
|
62
72
|
|
|
63
|
-
for (const auto&
|
|
73
|
+
for (const auto &content: contentVec) {
|
|
64
74
|
auto it = mapping.find(content);
|
|
65
75
|
if (it != mapping.end()) {
|
|
66
76
|
std::string replacement = it->second;
|
|
@@ -79,7 +89,6 @@ public:
|
|
|
79
89
|
std::vector<std::string> keys() const {
|
|
80
90
|
std::vector<std::string> contentVec;
|
|
81
91
|
std::vector<std::string> keys;
|
|
82
|
-
std::string pattern = _value;
|
|
83
92
|
|
|
84
93
|
size_t startPos = _value.find('{');
|
|
85
94
|
size_t endPos;
|
|
@@ -98,18 +107,18 @@ public:
|
|
|
98
107
|
return keys;
|
|
99
108
|
}
|
|
100
109
|
|
|
101
|
-
Topic match(const std::string&
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Topic match(topic);
|
|
105
|
-
match.insert(keyword_dict.begin(), keyword_dict.end());
|
|
106
|
-
return match;
|
|
107
|
-
} catch (const Error&) {
|
|
110
|
+
Topic match(const std::string &topic) const override {
|
|
111
|
+
std::unordered_map<std::string, std::string> keyword_dict = extract_mapping(topic, _value);
|
|
112
|
+
if (keyword_dict.empty()) {
|
|
108
113
|
return Topic("");
|
|
109
114
|
}
|
|
115
|
+
|
|
116
|
+
Topic match(topic);
|
|
117
|
+
match.insert(keyword_dict.begin(), keyword_dict.end());
|
|
118
|
+
return match;
|
|
110
119
|
}
|
|
111
120
|
|
|
112
|
-
static std::unordered_map<std::string, std::string> extract_mapping(const std::string&
|
|
121
|
+
static std::unordered_map<std::string, std::string> extract_mapping(const std::string &target, const std::string &pattern) {
|
|
113
122
|
std::unordered_map<std::string, std::string> dictionary;
|
|
114
123
|
|
|
115
124
|
std::vector<std::string> resultParts;
|
|
@@ -141,7 +150,6 @@ public:
|
|
|
141
150
|
|
|
142
151
|
// Check if the number of parts in result and pattern are the same
|
|
143
152
|
if (resultParts.size() != patternParts.size()) {
|
|
144
|
-
throw Error("Pattern not match");
|
|
145
153
|
return dictionary;
|
|
146
154
|
}
|
|
147
155
|
|
|
@@ -157,86 +165,91 @@ public:
|
|
|
157
165
|
} else {
|
|
158
166
|
if (resultPart != patternPart) {
|
|
159
167
|
dictionary.clear();
|
|
160
|
-
throw Error("Pattern not match");
|
|
161
168
|
return dictionary;
|
|
162
169
|
}
|
|
163
170
|
}
|
|
164
171
|
}
|
|
165
|
-
|
|
166
172
|
return dictionary;
|
|
167
173
|
}
|
|
168
174
|
};
|
|
169
175
|
|
|
170
176
|
extern "C" {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
177
|
+
Topic *create_topic(const char *topic) {
|
|
178
|
+
return new Topic(topic);
|
|
179
|
+
}
|
|
174
180
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
181
|
+
void get_topic_value(const Topic *topic, char *buffer, size_t bufferSize) {
|
|
182
|
+
std::string value = topic->value();
|
|
183
|
+
strncpy(buffer, value.c_str(), bufferSize - 1);
|
|
184
|
+
buffer[bufferSize - 1] = '\0';
|
|
185
|
+
}
|
|
180
186
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
187
|
+
const char *get_topic_value_no_buffer(const Topic *topic) {
|
|
188
|
+
return topic->_value.c_str();
|
|
189
|
+
}
|
|
184
190
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
191
|
+
void delete_topic(Topic *topic) {
|
|
192
|
+
delete topic;
|
|
193
|
+
}
|
|
188
194
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
195
|
+
Topic *match_topic(const Topic *topic, const char *match_topic) {
|
|
196
|
+
return new Topic(topic->match(match_topic));
|
|
197
|
+
}
|
|
192
198
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
RegularTopic *create_regular_topic(const char *pattern) {
|
|
200
|
+
return new RegularTopic(pattern);
|
|
201
|
+
}
|
|
196
202
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
203
|
+
Topic *match_regular_topic(const RegularTopic *topic, const char *match_topic) {
|
|
204
|
+
return new Topic(topic->match(match_topic));
|
|
205
|
+
}
|
|
200
206
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
207
|
+
PatternTopic *create_pattern_topic(const char *pattern) {
|
|
208
|
+
return new PatternTopic(pattern);
|
|
209
|
+
}
|
|
204
210
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
211
|
+
Topic *match_pattern_topic(const PatternTopic *topic, const char *match_topic) {
|
|
212
|
+
return new Topic(topic->match(match_topic));
|
|
213
|
+
}
|
|
208
214
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
std::vector<std::string> *get_pattern_topic_keys(const PatternTopic *topic) {
|
|
216
|
+
return new std::vector<std::string>(topic->keys());
|
|
217
|
+
}
|
|
212
218
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
int is_regular_match(const char *topic, const char *pattern) {
|
|
220
|
+
std::string topicStr(topic);
|
|
221
|
+
std::string patternStr(pattern);
|
|
222
|
+
int result = RegularTopic::is_match(topicStr, patternStr);
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
216
225
|
|
|
217
|
-
|
|
226
|
+
void extract_mapping(const char *target, const char *pattern, std::vector<std::string> *keys, std::vector<std::string> *values) {
|
|
227
|
+
std::string target_str(target);
|
|
228
|
+
std::string pattern_str(pattern);
|
|
218
229
|
|
|
219
|
-
|
|
220
|
-
for (const auto& entry : mapping) {
|
|
221
|
-
keys->push_back(entry.first);
|
|
222
|
-
values->push_back(entry.second);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
230
|
+
std::unordered_map<std::string, std::string> mapping = PatternTopic::extract_mapping(target_str, pattern_str);
|
|
225
231
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return "";
|
|
232
|
+
// Populate the keys and values vectors
|
|
233
|
+
for (const auto &entry: mapping) {
|
|
234
|
+
keys->push_back(entry.first);
|
|
235
|
+
values->push_back(entry.second);
|
|
231
236
|
}
|
|
237
|
+
}
|
|
232
238
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
239
|
+
const char *get_vector_value(const std::vector<std::string> *vec, int index) {
|
|
240
|
+
if (index >= 0 && index < static_cast<int>(vec->size()))
|
|
241
|
+
return vec->at(index).c_str();
|
|
242
|
+
else
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
236
245
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
246
|
+
int vector_size(const std::vector<std::string> *vec) {
|
|
247
|
+
return vec->size();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
void delete_vector(const std::vector<std::string> *vec) {
|
|
251
|
+
delete vec;
|
|
252
|
+
}
|
|
240
253
|
}
|
|
241
254
|
|
|
242
255
|
int main() {
|
|
@@ -250,22 +263,24 @@ int main() {
|
|
|
250
263
|
std::cout << match2.value() << std::endl;
|
|
251
264
|
|
|
252
265
|
PatternTopic patternTopic("TickData.{symbol}.{market}.{flag}");
|
|
253
|
-
for (std::string i: patternTopic.keys())
|
|
254
|
-
|
|
266
|
+
for (const std::string &i: patternTopic.keys())
|
|
267
|
+
std::cout << i << std::endl;
|
|
255
268
|
|
|
256
|
-
Topic formatted = patternTopic.format_map({{"symbol", "AAPL"},
|
|
269
|
+
Topic formatted = patternTopic.format_map({{"symbol", "AAPL"},
|
|
270
|
+
{"market", "NASDAQ"},
|
|
271
|
+
{"flag", "Realtime"}});
|
|
257
272
|
std::cout << formatted.value() << std::endl;
|
|
258
273
|
|
|
259
274
|
Topic match3 = patternTopic.match("TickData.ABC.NYSE.Realtime");
|
|
260
275
|
Topic match4 = patternTopic.match("OtherData.XYZ.LSE.History");
|
|
261
276
|
|
|
262
277
|
std::cout << "topic: " << match3.value() << std::endl;
|
|
263
|
-
for (const auto&
|
|
278
|
+
for (const auto &entry: match3) {
|
|
264
279
|
std::cout << entry.first << " : " << entry.second << std::endl;
|
|
265
280
|
}
|
|
266
281
|
|
|
267
282
|
std::cout << "topic: " << match4.value() << std::endl;
|
|
268
|
-
for (const auto&
|
|
283
|
+
for (const auto &entry: match4) {
|
|
269
284
|
std::cout << entry.first << " : " << entry.second << std::endl;
|
|
270
285
|
}
|
|
271
286
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PyEventEngine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0.post1
|
|
4
4
|
Summary: Basic event engine
|
|
5
5
|
Home-page: https://github.com/BolunHan/PyEventEngine.git
|
|
6
6
|
Author: Bolun.Han
|
|
@@ -9,6 +9,8 @@ License: MIT
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.8
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.9
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
14
|
Classifier: Operating System :: OS Independent
|
|
13
15
|
Requires-Python: >=3.8
|
|
14
16
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PyEventEngine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0.post1
|
|
4
4
|
Summary: Basic event engine
|
|
5
5
|
Home-page: https://github.com/BolunHan/PyEventEngine.git
|
|
6
6
|
Author: Bolun.Han
|
|
@@ -9,6 +9,8 @@ License: MIT
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.8
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.9
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
14
|
Classifier: Operating System :: OS Independent
|
|
13
15
|
Requires-Python: >=3.8
|
|
14
16
|
Description-Content-Type: text/markdown
|
|
@@ -3,10 +3,10 @@ README.md
|
|
|
3
3
|
setup.cfg
|
|
4
4
|
setup.py
|
|
5
5
|
EventEngine/__init__.py
|
|
6
|
-
EventEngine/Core/_Event.py
|
|
7
|
-
EventEngine/Core/_Topic.py
|
|
8
|
-
EventEngine/Core/_Topic_c.py
|
|
9
6
|
EventEngine/Core/__init__.py
|
|
7
|
+
EventEngine/Core/_event.py
|
|
8
|
+
EventEngine/Core/_topic.py
|
|
9
|
+
EventEngine/Core/_topic_c.py
|
|
10
10
|
EventEngine/cpp/topic_api.cpp
|
|
11
11
|
PyEventEngine.egg-info/PKG-INFO
|
|
12
12
|
PyEventEngine.egg-info/SOURCES.txt
|
|
@@ -40,6 +40,8 @@ setuptools.setup(
|
|
|
40
40
|
"Programming Language :: Python :: 3.8",
|
|
41
41
|
"Programming Language :: Python :: 3.9",
|
|
42
42
|
"Programming Language :: Python :: 3.10",
|
|
43
|
+
"Programming Language :: Python :: 3.11",
|
|
44
|
+
"Programming Language :: Python :: 3.12",
|
|
43
45
|
"Operating System :: OS Independent",
|
|
44
46
|
],
|
|
45
47
|
python_requires='>=3.8',
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import ctypes
|
|
4
|
-
import pathlib
|
|
5
|
-
import re
|
|
6
|
-
|
|
7
|
-
__all__ = ['Topic', 'RegularTopic', 'PatternTopic']
|
|
8
|
-
|
|
9
|
-
topic_lib = None
|
|
10
|
-
package_name = '^topic_api(.*).so$'
|
|
11
|
-
root_dir = pathlib.Path(__file__).parent.parent
|
|
12
|
-
|
|
13
|
-
for _ in root_dir.iterdir():
|
|
14
|
-
if lib_path := re.search(package_name, _.name):
|
|
15
|
-
topic_lib = ctypes.CDLL(str(_))
|
|
16
|
-
break
|
|
17
|
-
|
|
18
|
-
# Load the shared library
|
|
19
|
-
if topic_lib is None:
|
|
20
|
-
raise ImportError(f'EventEngine.Topic c extension {package_name} not found in {root_dir}! Fallback to native lib!')
|
|
21
|
-
|
|
22
|
-
# Function prototypes
|
|
23
|
-
handler = ctypes.POINTER(ctypes.c_void_p)
|
|
24
|
-
|
|
25
|
-
topic_lib.create_topic.restype = handler
|
|
26
|
-
topic_lib.create_regular_topic.restype = handler
|
|
27
|
-
topic_lib.create_pattern_topic.restype = handler
|
|
28
|
-
|
|
29
|
-
topic_lib.get_topic_value.restype = ctypes.c_void_p
|
|
30
|
-
topic_lib.get_topic_value_no_buffer.restype = ctypes.POINTER(ctypes.c_char_p)
|
|
31
|
-
topic_lib.get_vector_value.restype = ctypes.c_char_p
|
|
32
|
-
|
|
33
|
-
topic_lib.match_topic.restype = handler
|
|
34
|
-
topic_lib.match_regular_topic.restype = handler
|
|
35
|
-
topic_lib.match_pattern_topic.restype = handler
|
|
36
|
-
|
|
37
|
-
topic_lib.get_pattern_topic_keys.restype = handler
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class Topic(dict):
|
|
41
|
-
"""
|
|
42
|
-
topic for event hook. e.g. "TickData.002410.SZ.Realtime"
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
def __init__(self, topic: str, encoding: str = 'utf-8', buffer_size: int = 0, *args, **kwargs):
|
|
46
|
-
super().__init__(*args, **kwargs)
|
|
47
|
-
self.encoding = encoding
|
|
48
|
-
self.buffer_size = buffer_size
|
|
49
|
-
self.topic = topic_lib.create_topic(topic.encode(self.encoding))
|
|
50
|
-
|
|
51
|
-
def __repr__(self):
|
|
52
|
-
return f'<{self.__class__.__name__}>({self.value}){super().__repr__()}'
|
|
53
|
-
|
|
54
|
-
def __str__(self):
|
|
55
|
-
return self.value
|
|
56
|
-
|
|
57
|
-
def __bool__(self):
|
|
58
|
-
if self.value:
|
|
59
|
-
return True
|
|
60
|
-
else:
|
|
61
|
-
return False
|
|
62
|
-
|
|
63
|
-
def __hash__(self):
|
|
64
|
-
return self.value.__hash__()
|
|
65
|
-
|
|
66
|
-
@staticmethod
|
|
67
|
-
def _get_topic_value(topic: handler | int, buffer_size: int = 1024, encoding: str = 'utf-8'):
|
|
68
|
-
if buffer_size:
|
|
69
|
-
buffer = ctypes.create_string_buffer(buffer_size)
|
|
70
|
-
topic_lib.get_topic_value(topic, buffer, buffer_size)
|
|
71
|
-
topic_value = buffer.value.decode(encoding)
|
|
72
|
-
else:
|
|
73
|
-
topic_ptr = topic_lib.get_topic_value_no_buffer(topic)
|
|
74
|
-
topic_value = ctypes.string_at(topic_ptr).decode(encoding)
|
|
75
|
-
|
|
76
|
-
return topic_value
|
|
77
|
-
|
|
78
|
-
@property
|
|
79
|
-
def value(self) -> str:
|
|
80
|
-
return self._get_topic_value(topic=self.topic, buffer_size=self.buffer_size, encoding=self.encoding)
|
|
81
|
-
|
|
82
|
-
def match(self, topic: str):
|
|
83
|
-
match = topic_lib.match_topic(self.topic, topic.encode(self.encoding))
|
|
84
|
-
|
|
85
|
-
if self._get_topic_value(match):
|
|
86
|
-
return Topic(topic)
|
|
87
|
-
else:
|
|
88
|
-
return None
|
|
89
|
-
|
|
90
|
-
def __del__(self):
|
|
91
|
-
topic_lib.delete_topic(self.topic)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# Define the RegularTopic class in Python
|
|
95
|
-
class RegularTopic(Topic):
|
|
96
|
-
"""
|
|
97
|
-
topic in regular expression. e.g. "TickData.(.+).((SZ)|(SH)).((Realtime)|(History))"
|
|
98
|
-
"""
|
|
99
|
-
|
|
100
|
-
def __init__(self, pattern: str, encoding: str = 'utf-8', buffer_size: int = 0, *args, **kwargs):
|
|
101
|
-
dict.__init__(self, *args, **kwargs)
|
|
102
|
-
self.encoding = encoding
|
|
103
|
-
self.buffer_size = buffer_size
|
|
104
|
-
self.topic = topic_lib.create_pattern_topic(pattern.encode(self.encoding))
|
|
105
|
-
|
|
106
|
-
def match(self, topic: str):
|
|
107
|
-
match = topic_lib.match_regular_topic(self.topic, topic.encode(self.encoding))
|
|
108
|
-
|
|
109
|
-
if _ := self._get_topic_value(match):
|
|
110
|
-
return Topic(_, pattern=self.value)
|
|
111
|
-
else:
|
|
112
|
-
return None
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# Define the PatternTopic class in Python
|
|
116
|
-
class PatternTopic(Topic):
|
|
117
|
-
"""
|
|
118
|
-
topic for event hook. e.g. "TickData.{symbol}.{market}.{flag}"
|
|
119
|
-
"""
|
|
120
|
-
|
|
121
|
-
def __init__(self, pattern: str, encoding: str = 'utf-8', buffer_size: int = 0, *args, **kwargs):
|
|
122
|
-
dict.__init__(self, *args, **kwargs)
|
|
123
|
-
self.encoding = encoding
|
|
124
|
-
self.buffer_size = buffer_size
|
|
125
|
-
self.topic = topic_lib.create_regular_topic(pattern.encode(self.encoding))
|
|
126
|
-
|
|
127
|
-
def __call__(self, **kwargs):
|
|
128
|
-
return self.format_map(kwargs)
|
|
129
|
-
|
|
130
|
-
def match(self, topic: str):
|
|
131
|
-
match = topic_lib.match_pattern_topic(self.topic, topic.encode(self.encoding))
|
|
132
|
-
|
|
133
|
-
if _ := self._get_topic_value(match):
|
|
134
|
-
return Topic(_, pattern=self.value, **self.extract_mapping(target=topic, pattern=self.value, encoding=self.encoding))
|
|
135
|
-
else:
|
|
136
|
-
return None
|
|
137
|
-
|
|
138
|
-
@classmethod
|
|
139
|
-
def extract_mapping(cls, target: str, pattern: str, encoding: str = 'utf-8') -> dict[str, str]:
|
|
140
|
-
# noinspection PyArgumentList
|
|
141
|
-
keys_ptr, values_ptr = ctypes.POINTER(ctypes.c_void_p)(), ctypes.POINTER(ctypes.c_void_p)()
|
|
142
|
-
mapping = {}
|
|
143
|
-
|
|
144
|
-
topic_lib.extract_mapping(
|
|
145
|
-
target.encode(encoding),
|
|
146
|
-
pattern.encode(encoding),
|
|
147
|
-
ctypes.byref(keys_ptr),
|
|
148
|
-
ctypes.byref(values_ptr)
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
for i in range(topic_lib.vector_size(ctypes.byref(keys_ptr))):
|
|
152
|
-
mapping[topic_lib.get_vector_value(ctypes.byref(keys_ptr), i).decode(encoding)] = topic_lib.get_vector_value(ctypes.byref(values_ptr), i).decode(encoding)
|
|
153
|
-
|
|
154
|
-
return mapping
|
|
155
|
-
|
|
156
|
-
def keys(self) -> list[str]:
|
|
157
|
-
keys = []
|
|
158
|
-
keys_ptr = topic_lib.get_pattern_topic_keys(self.topic)
|
|
159
|
-
|
|
160
|
-
for i in range(topic_lib.vector_size(keys_ptr)):
|
|
161
|
-
keys.append(topic_lib.get_vector_value(keys_ptr, i).decode(self.encoding))
|
|
162
|
-
|
|
163
|
-
topic_lib.delete_vector(keys_ptr)
|
|
164
|
-
return keys
|
|
165
|
-
|
|
166
|
-
def format_map(self, mapping: dict) -> Topic:
|
|
167
|
-
for key in self.keys():
|
|
168
|
-
if key not in mapping:
|
|
169
|
-
mapping[key] = f'{{{key}}}'
|
|
170
|
-
|
|
171
|
-
return Topic(self.value.format_map(mapping))
|
|
File without changes
|
{PyEventEngine-0.2.1 → pyeventengine-0.3.0.post1}/PyEventEngine.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|