PyEventEngine 0.2.1__tar.gz → 0.3.0__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.
@@ -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 - 1
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
- # use c++ optimized module
90
- try:
91
- from ._Topic_c import Topic, RegularTopic, PatternTopic
92
- except Exception as _:
93
- from ._Topic import Topic, RegularTopic, PatternTopic
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
- from ._Event import EventHook, EventEngine
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 queue
5
- import threading
2
+ import enum
3
+ import inspect
6
4
  import time
7
5
  import traceback
8
- from typing import Iterable
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
- from . import LOGGER, LOG_LEVEL_EVENT, Topic
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, handler: list[callable] | callable | None = None):
29
+ def __init__(self, topic: Topic, logger: Logger = None, max_size: int = None):
21
30
  self.topic = topic
22
-
23
- if callable(handler):
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
- handler(*args, **kwargs)
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
- LOGGER.error(traceback.format_exc())
62
+ self.logger.error(traceback.format_exc())
63
63
 
64
- def add_handler(self, handler: callable):
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: callable):
68
- self.handlers.remove(handler)
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
- self.logger = LOGGER.getChild(f'EventHook.{topic}')
74
- super().__init__(topic=topic, handler=handler)
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
- try:
101
+ if with_topic:
88
102
  handler(topic=topic, *args, **kwargs)
89
- except TypeError:
103
+ else:
90
104
  handler(*args, **kwargs)
91
105
  except Exception as _:
92
106
  self.logger.error(traceback.format_exc())
93
107
 
94
- self.logger.log(LOG_LEVEL_EVENT, f'EventHook {self.topic} tasks triggered {len(self.handlers):,} handlers, complete in {(time.time() - ts) * 1000:.3f}ms')
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, max_size=0):
101
- self.lock = threading.Lock()
102
-
103
- self._max_size = max_size
104
- self._queue: queue.Queue = queue.Queue(maxsize=self._max_size)
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: threading.Thread = threading.Thread(target=self._run, name='EventEngine')
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._queue.get(block=True, timeout=1)
116
- topic = event_dict['topic']
117
- args = event_dict.get('args', ())
118
- kwargs = event_dict.get('kwargs', {})
119
- self._process(topic, *args, **kwargs)
120
- except queue.Empty:
121
- pass
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 _ in list(self._event_hooks):
128
- m = _.match(topic=topic)
129
- if m:
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
- LOGGER.warning('EventEngine already started!')
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
- LOGGER.warning('EventEngine already stopped!')
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._queue.put(event_dict, block=block, timeout=timeout)
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[callable] | callable) -> None:
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: callable) -> None:
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 max_size(self):
233
- return self._max_size
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, max_size=0):
245
- super().__init__(max_size=max_size)
246
- self.timer: dict[float | str, threading.Thread] = {}
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 = threading.Thread(target=self._second_timer, kwargs={'topic': topic})
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 = threading.Thread(target=self._minute_timer, kwargs={'topic': topic})
327
+ timer = Thread(target=self._minute_timer, kwargs={'topic': topic})
284
328
  else:
285
329
  topic = Topic(f'EventEngine.Internal.Timer.{interval}')
286
- timer = threading.Thread(target=self._run_timer, kwargs={'interval': interval, 'topic': topic, 'activate_time': activate_time})
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
- LOGGER.debug(f'Timer thread with interval [{datetime.timedelta(seconds=interval)}] already initialized! Argument [activate_time] takes no effect!')
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) -> Topic | None:
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: Topic | str | Enum, dtype=None) -> Topic:
41
- if isinstance(topic, Enum):
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
- if dtype is None:
47
- if re.search(r'{(.+?)}', topic):
48
- t = PatternTopic(pattern=topic)
49
- elif '*' in topic or '+' in topic or '|' in topic:
50
- re.compile(pattern=topic)
51
- t = RegularTopic(pattern=topic)
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 = Topic(topic=topic)
56
+ t = dtype(topic)
54
57
  else:
55
- t = dtype(topic)
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
- pattern = re.escape(pattern)
95
- regex = re.sub(r'\\{(.+?)\\}', r'(?P<_\1>.+)', pattern)
96
- match = re.match(regex, target)
97
- if match:
98
- values = list(match.groups())
99
- keys = re.findall(r'\\{(.+?)\\}', pattern)
100
- m = dict(zip(keys, values))
101
- return m
102
- else:
103
- raise Topic.Error(f'pattern {pattern} not in string {target} found!')
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
@@ -0,0 +1,3 @@
1
+ __version__ = '0.3.0'
2
+
3
+ from .Core import *
@@ -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
- #include <stdio.h>
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(const std::string& msg) : message(msg) {}
17
- const char* what() const noexcept override { return message.c_str(); }
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(const std::string& topic) : _value(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& topic) const {
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& pattern) : Topic(pattern) {}
41
+ explicit RegularTopic(const std::string &pattern) : Topic(pattern) {}
41
42
 
42
- Topic match(const std::string& topic) const {
43
- std::regex regex(_value);
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& pattern) : Topic(pattern) {}
66
+ explicit PatternTopic(const std::string &pattern) : Topic(pattern) {}
57
67
 
58
- Topic format_map(const std::unordered_map<std::string, std::string>& mapping) const {
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& content : contentVec) {
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& topic) const {
102
- try {
103
- std::unordered_map<std::string, std::string> keyword_dict = extract_mapping(topic, _value);
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& target, const std::string& pattern) {
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
- Topic* create_topic(const char* topic) {
172
- return new Topic(topic);
173
- }
177
+ Topic *create_topic(const char *topic) {
178
+ return new Topic(topic);
179
+ }
174
180
 
175
- void get_topic_value(const Topic* topic, char* buffer, size_t bufferSize) {
176
- std::string value = topic->value();
177
- strncpy(buffer, value.c_str(), bufferSize - 1);
178
- buffer[bufferSize - 1] = '\0';
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
- const char* get_topic_value_no_buffer(const Topic* topic) {
182
- return topic->_value.c_str();
183
- }
187
+ const char *get_topic_value_no_buffer(const Topic *topic) {
188
+ return topic->_value.c_str();
189
+ }
184
190
 
185
- void delete_topic(Topic* topic) {
186
- delete topic;
187
- }
191
+ void delete_topic(Topic *topic) {
192
+ delete topic;
193
+ }
188
194
 
189
- Topic* match_topic(const Topic* topic, const char* match_topic) {
190
- return new Topic(topic->match(match_topic));
191
- }
195
+ Topic *match_topic(const Topic *topic, const char *match_topic) {
196
+ return new Topic(topic->match(match_topic));
197
+ }
192
198
 
193
- RegularTopic* create_regular_topic(const char* pattern) {
194
- return new RegularTopic(pattern);
195
- }
199
+ RegularTopic *create_regular_topic(const char *pattern) {
200
+ return new RegularTopic(pattern);
201
+ }
196
202
 
197
- Topic* match_regular_topic(const RegularTopic* topic, const char* match_topic) {
198
- return new Topic(topic->match(match_topic));
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
- PatternTopic* create_pattern_topic(const char* pattern) {
202
- return new PatternTopic(pattern);
203
- }
207
+ PatternTopic *create_pattern_topic(const char *pattern) {
208
+ return new PatternTopic(pattern);
209
+ }
204
210
 
205
- Topic* match_pattern_topic(const PatternTopic* topic, const char* match_topic) {
206
- return new Topic(topic->match(match_topic));
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
- std::vector<std::string>* get_pattern_topic_keys(const PatternTopic* topic) {
210
- return new std::vector<std::string>(topic->keys());
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
- void extract_mapping(const char* target, const char* pattern, std::vector<std::string>* keys, std::vector<std::string>* values) {
214
- std::string target_str(target);
215
- std::string pattern_str(pattern);
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
- std::unordered_map<std::string, std::string> mapping = PatternTopic::extract_mapping(target_str, pattern_str);
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
- // Populate the keys and values vectors
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
- const char* get_vector_value(const std::vector<std::string>* vec, int index) {
227
- if (index >= 0 && index < static_cast<int>(vec->size()))
228
- return vec->at(index).c_str();
229
- else
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
- const int vector_size(const std::vector<std::string>* v){
234
- return v->size();
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
- void delete_vector(std::vector<std::string>* vec) {
238
- delete vec;
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
- std::cout << i << std::endl;
266
+ for (const std::string &i: patternTopic.keys())
267
+ std::cout << i << std::endl;
255
268
 
256
- Topic formatted = patternTopic.format_map({{"symbol", "AAPL"}, {"market", "NASDAQ"}, {"flag", "Realtime"}});
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& entry : match3) {
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& entry : match4) {
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.2.1
3
+ Version: 0.3.0
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.2.1
3
+ Version: 0.3.0
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))
@@ -1,3 +0,0 @@
1
- __version__ = '0.2.1'
2
-
3
- from .Core import *
File without changes
File without changes
File without changes