diff --git a/appPublic/README.md b/appPublic/README.md new file mode 100644 index 0000000..e4c8dfb --- /dev/null +++ b/appPublic/README.md @@ -0,0 +1,58 @@ +# EventDispatcher + +生产级异步事件调度器。 + +## 特性 + +- 支持普通函数 +- 支持 async 协程 +- 支持实例方法 +- 弱引用自动GC +- 异常隔离 +- 超时控制 +- 自定义错误处理 + +--- + +# 使用示例 + +```python +import asyncio + +from event_dispatcher import EventDispatcher + + +def on_login(data): + print(data) + + +async def main(): + + dispatcher = EventDispatcher() + + dispatcher.bind( + "login", + on_login + ) + + await dispatcher.dispatch( + "login", + { + "user": "张三" + } + ) + + +asyncio.run(main()) +``` + +--- + +# 项目结构 + +```text +event_dispatcher_project/ +├── event_dispatcher.py +└── README.md +``` + diff --git a/appPublic/__pycache__/Singleton.cpython-310.pyc b/appPublic/__pycache__/Singleton.cpython-310.pyc new file mode 100644 index 0000000..f6d8d27 Binary files /dev/null and b/appPublic/__pycache__/Singleton.cpython-310.pyc differ diff --git a/appPublic/__pycache__/__init__.cpython-310.pyc b/appPublic/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..315428c Binary files /dev/null and b/appPublic/__pycache__/__init__.cpython-310.pyc differ diff --git a/appPublic/__pycache__/aes.cpython-310.pyc b/appPublic/__pycache__/aes.cpython-310.pyc new file mode 100644 index 0000000..13c65bf Binary files /dev/null and b/appPublic/__pycache__/aes.cpython-310.pyc differ diff --git a/appPublic/__pycache__/argsConvert.cpython-310.pyc b/appPublic/__pycache__/argsConvert.cpython-310.pyc new file mode 100644 index 0000000..03a9cdc Binary files /dev/null and b/appPublic/__pycache__/argsConvert.cpython-310.pyc differ diff --git a/appPublic/__pycache__/base64_to_file.cpython-310.pyc b/appPublic/__pycache__/base64_to_file.cpython-310.pyc new file mode 100644 index 0000000..516babc Binary files /dev/null and b/appPublic/__pycache__/base64_to_file.cpython-310.pyc differ diff --git a/appPublic/__pycache__/dictObject.cpython-310.pyc b/appPublic/__pycache__/dictObject.cpython-310.pyc new file mode 100644 index 0000000..74ca849 Binary files /dev/null and b/appPublic/__pycache__/dictObject.cpython-310.pyc differ diff --git a/appPublic/__pycache__/event_dispatcher.cpython-310.pyc b/appPublic/__pycache__/event_dispatcher.cpython-310.pyc new file mode 100644 index 0000000..200803c Binary files /dev/null and b/appPublic/__pycache__/event_dispatcher.cpython-310.pyc differ diff --git a/appPublic/__pycache__/folderUtils.cpython-310.pyc b/appPublic/__pycache__/folderUtils.cpython-310.pyc new file mode 100644 index 0000000..2641174 Binary files /dev/null and b/appPublic/__pycache__/folderUtils.cpython-310.pyc differ diff --git a/appPublic/__pycache__/jsonConfig.cpython-310.pyc b/appPublic/__pycache__/jsonConfig.cpython-310.pyc new file mode 100644 index 0000000..0763bef Binary files /dev/null and b/appPublic/__pycache__/jsonConfig.cpython-310.pyc differ diff --git a/appPublic/__pycache__/log.cpython-310.pyc b/appPublic/__pycache__/log.cpython-310.pyc new file mode 100644 index 0000000..926c485 Binary files /dev/null and b/appPublic/__pycache__/log.cpython-310.pyc differ diff --git a/appPublic/__pycache__/myImport.cpython-310.pyc b/appPublic/__pycache__/myImport.cpython-310.pyc new file mode 100644 index 0000000..001ed96 Binary files /dev/null and b/appPublic/__pycache__/myImport.cpython-310.pyc differ diff --git a/appPublic/__pycache__/myTE.cpython-310.pyc b/appPublic/__pycache__/myTE.cpython-310.pyc new file mode 100644 index 0000000..1ac496b Binary files /dev/null and b/appPublic/__pycache__/myTE.cpython-310.pyc differ diff --git a/appPublic/__pycache__/myjson.cpython-310.pyc b/appPublic/__pycache__/myjson.cpython-310.pyc new file mode 100644 index 0000000..086026b Binary files /dev/null and b/appPublic/__pycache__/myjson.cpython-310.pyc differ diff --git a/appPublic/__pycache__/objectAction.cpython-310.pyc b/appPublic/__pycache__/objectAction.cpython-310.pyc new file mode 100644 index 0000000..4620aeb Binary files /dev/null and b/appPublic/__pycache__/objectAction.cpython-310.pyc differ diff --git a/appPublic/__pycache__/rc4.cpython-310.pyc b/appPublic/__pycache__/rc4.cpython-310.pyc new file mode 100644 index 0000000..9480b88 Binary files /dev/null and b/appPublic/__pycache__/rc4.cpython-310.pyc differ diff --git a/appPublic/__pycache__/registerfunction.cpython-310.pyc b/appPublic/__pycache__/registerfunction.cpython-310.pyc new file mode 100644 index 0000000..fe57002 Binary files /dev/null and b/appPublic/__pycache__/registerfunction.cpython-310.pyc differ diff --git a/appPublic/__pycache__/streamhttpclient.cpython-310.pyc b/appPublic/__pycache__/streamhttpclient.cpython-310.pyc new file mode 100644 index 0000000..888e7ad Binary files /dev/null and b/appPublic/__pycache__/streamhttpclient.cpython-310.pyc differ diff --git a/appPublic/__pycache__/timeUtils.cpython-310.pyc b/appPublic/__pycache__/timeUtils.cpython-310.pyc new file mode 100644 index 0000000..1c73ede Binary files /dev/null and b/appPublic/__pycache__/timeUtils.cpython-310.pyc differ diff --git a/appPublic/__pycache__/unicoding.cpython-310.pyc b/appPublic/__pycache__/unicoding.cpython-310.pyc new file mode 100644 index 0000000..d8f75bd Binary files /dev/null and b/appPublic/__pycache__/unicoding.cpython-310.pyc differ diff --git a/appPublic/__pycache__/uniqueID.cpython-310.pyc b/appPublic/__pycache__/uniqueID.cpython-310.pyc new file mode 100644 index 0000000..8a1d8d7 Binary files /dev/null and b/appPublic/__pycache__/uniqueID.cpython-310.pyc differ diff --git a/appPublic/__pycache__/version.cpython-310.pyc b/appPublic/__pycache__/version.cpython-310.pyc new file mode 100644 index 0000000..d4f8880 Binary files /dev/null and b/appPublic/__pycache__/version.cpython-310.pyc differ diff --git a/appPublic/__pycache__/worker.cpython-310.pyc b/appPublic/__pycache__/worker.cpython-310.pyc new file mode 100644 index 0000000..f05fa9e Binary files /dev/null and b/appPublic/__pycache__/worker.cpython-310.pyc differ diff --git a/appPublic/k b/appPublic/k new file mode 100644 index 0000000..764a070 --- /dev/null +++ b/appPublic/k @@ -0,0 +1,289 @@ +#!/usr/bin/env bash + +set -e + +PROJECT_DIR="event_dispatcher_project" + +mkdir -p "${PROJECT_DIR}" + +BT='```' + +# ========================================================= +# event_dispatcher.py +# ========================================================= + +cat > "${PROJECT_DIR}/event_dispatcher.py" <<'PYEOF' +import asyncio +import inspect +import traceback +import weakref +from typing import Callable, Any + + +class WeakCallback: + + def __init__(self, func: Callable): + + self._is_coroutine = inspect.iscoroutinefunction(func) + + if inspect.ismethod(func): + self._ref = weakref.WeakMethod(func) + else: + self._ref = weakref.ref(func) + + self._hash = hash(func) + + @property + def is_coroutine(self): + return self._is_coroutine + + def get(self): + return self._ref() + + def __eq__(self, other): + return isinstance(other, WeakCallback) and self._hash == other._hash + + def __hash__(self): + return self._hash + + +class EventDispatcher: + + def __init__( + self, + *, + continue_on_error=True, + log_traceback=True, + handler_timeout=None, + error_handler=None, + ): + + self._events = {} + + self.continue_on_error = continue_on_error + self.log_traceback = log_traceback + self.handler_timeout = handler_timeout + self.error_handler = error_handler + + def bind(self, event_name: str, func: Callable): + + if event_name not in self._events: + self._events[event_name] = set() + + self._events[event_name].add(WeakCallback(func)) + + def unbind(self, event_name: str, func: Callable): + + if event_name not in self._events: + return + + target = WeakCallback(func) + + self._events[event_name] = { + cb for cb in self._events[event_name] + if cb != target + } + + if not self._events[event_name]: + del self._events[event_name] + + async def _run_error_handler( + self, + event_name, + func, + exc, + ): + + if not self.error_handler: + return + + try: + + if inspect.iscoroutinefunction(self.error_handler): + await self.error_handler( + event_name, + func, + exc, + ) + else: + self.error_handler( + event_name, + func, + exc, + ) + + except Exception as e: + print(f"[EventDispatcher] error_handler failed: {e}") + + async def _execute_handler( + self, + cb, + event_name, + data, + ): + + func = cb.get() + + if func is None: + return False + + try: + + if cb.is_coroutine: + + coro = func(data) + + if self.handler_timeout: + await asyncio.wait_for( + coro, + timeout=self.handler_timeout, + ) + else: + await coro + + else: + + if self.handler_timeout: + + await asyncio.wait_for( + asyncio.to_thread(func, data), + timeout=self.handler_timeout, + ) + + else: + func(data) + + return True + + except Exception as e: + + print( + f"[EventDispatcher] " + f"handler failed: " + f"event={event_name}, " + f"handler={func}" + ) + + if self.log_traceback: + traceback.print_exc() + + await self._run_error_handler( + event_name, + func, + e, + ) + + if not self.continue_on_error: + raise + + return True + + async def dispatch( + self, + event_name: str, + data: Any = None, + ): + + if event_name not in self._events: + return + + dead_callbacks = [] + + for cb in list(self._events[event_name]): + + func = cb.get() + + if func is None: + dead_callbacks.append(cb) + continue + + await self._execute_handler( + cb, + event_name, + data, + ) + + for cb in dead_callbacks: + self._events[event_name].discard(cb) + + if ( + event_name in self._events + and not self._events[event_name] + ): + del self._events[event_name] +PYEOF + +# ========================================================= +# README.md +# ========================================================= + +cat > "${PROJECT_DIR}/README.md" <= 0 or item.find('[critical]') >= 0: + fh.flush() + except queue.Empty: + # Periodic flush to prevent data loss on crash + if fh is not None: + try: + fh.flush() + except Exception: + pass + except Exception: + pass + def log(self, levelname, message, frame_info): caller_frame = frame_info.f_back filename = inspect.getframeinfo(caller_frame).filename lineno = inspect.getframeinfo(caller_frame).lineno level = self.levels.get(levelname) if level > self.level: - # print(f'{level=},{self.level=}') return data = { - 'timestamp':timestampstr(), - 'name':self.name, - 'levelname':levelname, - 'message':message, - 'filename':filename, - 'lineno':lineno + 'timestamp': timestampstr(), + 'name': self.name, + 'levelname': levelname, + 'message': message, + 'filename': filename, + 'lineno': lineno } - self.open_logger() s = self.formater % data - self.logger.write(s) - self.logger.flush() - self.close_logger() - + try: + self._q.put_nowait(s) + except queue.Full: + # Queue full: drop oldest (non-blocking, never stall the event loop) + try: + self._q.get_nowait() + self._q.put_nowait(s) + except Exception: + pass + def clientinfo(message): frame_info = inspect.currentframe() logger = MyLogger('Test') @@ -97,8 +137,7 @@ def critical(message): def exception(message): frame_info = inspect.currentframe() - tb_msg = format_exc() + tb_msg = format_exc() msg = f'{message}\n{tb_msg}' logger = MyLogger('exception') logger.log('exception', msg, frame_info) -