From 6b3e896dc6d7e3fb210580313304418496244e11 Mon Sep 17 00:00:00 2001 From: Cam <yulinfeng000@gmail.com> Date: Tue, 21 Jun 2022 10:33:40 +0800 Subject: [PATCH] add global error handle, and update README, add new example for error handle, update msg.py for add new property for SuperChatMsg --- README.md | 23 ++++++++++++++ blive/framework.py | 29 ++++++++++++++++++ blive/msg.py | 13 ++++++++ example/error_handler.py | 66 ++++++++++++++++++++++++++++++++++++++++ example/multi_room.py | 2 +- 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 example/error_handler.py diff --git a/README.md b/README.md index 7941b6f..8067e5b 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,26 @@ async def show(): return list(BLIVER_POOL.keys()) ``` + +## 全局异常处理 + +全局异常处理分为两个共享级别,分别为类级别和实例级别,在类上注册的异常处理为所有类实例共享,实例级别的异常处理只有实例自身拥有 + +```python + +app = BLiver(510) + +@app.catch(ZeroDivisionError) +def err_handler(e, app: BLiver): + print(f"{app.uname} catch ZeroDivisionError", e) + +@app.on(Events.DANMU_MSG) +async def danmu_handler(ctx): + 1 / 0 # will raise ZeroDivisionError + +azi.run() +``` + ## 项目简介 - blive 文件夹为框架代码 @@ -175,6 +195,9 @@ async def show(): - example/with_fastapi.py 与fastapi 配合使用的例子 +- example/error_handler.py + 错误处理的例子 + ## TODO - 更多的消息操作类(欢迎各位提pr) diff --git a/blive/framework.py b/blive/framework.py index 08e40a1..05881e6 100644 --- a/blive/framework.py +++ b/blive/framework.py @@ -1,3 +1,4 @@ +from socket import gaierror import sys import json import asyncio @@ -74,6 +75,25 @@ class Processor: class BLiver: + _global_catches = {} + + def catch(self, err_type): + def _err_handler_wrapper(fn): + self.register_error_handler(err_type, fn) + + return _err_handler_wrapper + + @classmethod + def register_global_error_handler(cls, err_type, fn): + err_handlers = cls._global_catches.get(err_type, []) + err_handlers.append(fn) + cls._global_catches[err_type] = err_handlers + + def register_error_handler(self, err_type, fn): + err_handlers = self._catches.get(err_type, []) + err_handlers.append(fn) + self._catches[err_type] = err_handlers + def __init__(self, roomid, uid=0, logger=None, log_level="INFO"): self.roomid = roomid self.uid = uid @@ -84,6 +104,7 @@ class BLiver: self.logger.add(sys.stderr, level=log_level) else: self.logger = logger + self._catches = {} # to handle errors self._ws: ClientWebSocketResponse = None self.packman = BWS_MsgPackage() self.scheduler = AsyncIOScheduler(timezone="Asia/ShangHai") @@ -162,6 +183,7 @@ class BLiver: except ( aiohttp.ClientConnectionError, asyncio.TimeoutError, + ConnectionError, ConnectionResetError, ): self.logger.warning("send heartbeat error, will reconnect ws") @@ -185,6 +207,7 @@ class BLiver: aiohttp.ClientConnectionError, asyncio.TimeoutError, ConnectionError, + ConnectionResetError, ): self.logger.warning( "connect failed, will retry {}, current: {}", retries, i + 1 @@ -226,6 +249,12 @@ class BLiver: self.logger.warning("ws conn will reconnect") await self.connect() + # to handler errors + except tuple(self._catches.keys()) as e: + [eh(e,self) for eh in self._catches.get(type(e), [])] + except tuple(BLiver._global_catches.keys()) as e: + [eh(e,self) for eh in BLiver._global_catches.get(type(e), [])] + async def graceful_close(self): await self._ws.close() await self.aio_session.close() diff --git a/blive/msg.py b/blive/msg.py index 0aaf04b..a891ac7 100644 --- a/blive/msg.py +++ b/blive/msg.py @@ -201,6 +201,19 @@ class SuperChatMsg(BaseMsg): @property def time(self): return dict_chain_get(self.body, "data.time") + + @property # 头像 + def avatar_url(self): + return dict_chain_get(self.body, "data.user_info.face") + + @property + def anchor_uname(self): + return dict_chain_get(self.body, "data.medal_info.anchor_uname") + + @property # 背景色 + def color(self): + return dict_chain_get(self.body, "data.background_bottom_color") + class EntryEffectMsg(BaseMsg): diff --git a/example/error_handler.py b/example/error_handler.py new file mode 100644 index 0000000..196fc3e --- /dev/null +++ b/example/error_handler.py @@ -0,0 +1,66 @@ +"""监听多个直播间的例子""" + +import asyncio +from blive import BLiver, Events, BLiverCtx +from blive.msg import DanMuMsg + +# 多个对象共用的全局异常处理 +# 首先定义全局异常处理handler + + +def global_error_handler(e, app: BLiver): + print(f"{app.uname} 全局异常捕获", e) + + +# 调用类方法注册异常以及其处理函数,需在实例化之前注册,注册后所有BLiver共同拥有该异常处理 +BLiver.register_global_error_handler(ZeroDivisionError, global_error_handler) + + +# 定义弹幕事件handler,为了演示异常处理直接在方法中抛出异常 +async def azi_timeout_error(ctx: BLiverCtx): + raise TimeoutError + + +async def ke_type_error(ctx): + raise TypeError + + +async def zero_division_error(ctx): + 1 / 0 + + +# 两个直播间 +ke = BLiver(21716679) +azi = BLiver(7983476) + +# 注册handler +ke.register_handler(Events.INTERACT_WORD, zero_division_error) +azi.register_handler(Events.INTERACT_WORD, zero_division_error) +ke.register_handler(Events.DANMU_MSG, ke_type_error) +azi.register_handler(Events.DANMU_MSG, azi_timeout_error) + + +# 类实例级别的异常处理,实例与实例之间不共享 +ke.register_error_handler( + TypeError, lambda e, app: print(f"{app.uname} catch TypeError", e) +) + +# 实例级别的异常处理可以用注解方式进行注册 +@azi.catch(TimeoutError) +def azi_handler(e, app): + print(f"{app.uname} catch TimeoutError", e) + + +async def main(): + + # 以异步task的形式运行 + task1 = ke.run_as_task() + task2 = azi.run_as_task() + + # await 两个任务 + await asyncio.gather(*[task1, task2]) + + +if __name__ == "__main__": + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) diff --git a/example/multi_room.py b/example/multi_room.py index d936e39..97136d8 100644 --- a/example/multi_room.py +++ b/example/multi_room.py @@ -9,7 +9,7 @@ from blive.msg import DanMuMsg async def listen(ctx: BLiverCtx): danmu = DanMuMsg(ctx.body) print( - f'\n{danmu.sender.name} ({danmu.sender.medal.medal_name}:{danmu.sender.medal.medal_level}): "{danmu.content}"\n' + f'\n【{ctx.bliver.uname}】{danmu.sender.name} ({danmu.sender.medal.medal_name}:{danmu.sender.medal.medal_level}): "{danmu.content}"\n' )