mirror of
https://github.com/nonebot/nonebot2.git
synced 2024-11-24 00:55:07 +08:00
✨ Feature: 优化事件分发方法 (#2067)
This commit is contained in:
parent
3199fc454a
commit
f52abc8314
@ -80,7 +80,10 @@ RUN_POSTPCS_PARAMS = (
|
||||
|
||||
|
||||
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
|
||||
"""事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。"""
|
||||
"""事件预处理。
|
||||
|
||||
装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。
|
||||
"""
|
||||
_event_preprocessors.add(
|
||||
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
||||
)
|
||||
@ -88,7 +91,10 @@ def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
|
||||
|
||||
|
||||
def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
|
||||
"""事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。"""
|
||||
"""事件后处理。
|
||||
|
||||
装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。
|
||||
"""
|
||||
_event_postprocessors.add(
|
||||
Dependent[Any].parse(call=func, allow_types=EVENT_PCS_PARAMS)
|
||||
)
|
||||
@ -96,7 +102,10 @@ def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
|
||||
|
||||
|
||||
def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
|
||||
"""运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。"""
|
||||
"""运行预处理。
|
||||
|
||||
装饰一个函数,使它在每次事件响应器运行前执行。
|
||||
"""
|
||||
_run_preprocessors.add(
|
||||
Dependent[Any].parse(call=func, allow_types=RUN_PREPCS_PARAMS)
|
||||
)
|
||||
@ -104,55 +113,149 @@ def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
|
||||
|
||||
|
||||
def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
|
||||
"""运行后处理。装饰一个函数,使它在每次事件响应器运行后执行。"""
|
||||
"""运行后处理。
|
||||
|
||||
装饰一个函数,使它在每次事件响应器运行后执行。
|
||||
"""
|
||||
_run_postprocessors.add(
|
||||
Dependent[Any].parse(call=func, allow_types=RUN_POSTPCS_PARAMS)
|
||||
)
|
||||
return func
|
||||
|
||||
|
||||
async def _check_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
async def _apply_event_preprocessors(
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> None:
|
||||
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
|
||||
with contextlib.suppress(Exception):
|
||||
Matcher.destroy()
|
||||
return
|
||||
show_log: bool = True,
|
||||
) -> bool:
|
||||
"""运行事件预处理。
|
||||
|
||||
参数:
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
show_log: 是否显示日志
|
||||
|
||||
返回:
|
||||
是否继续处理事件
|
||||
"""
|
||||
if not _event_preprocessors:
|
||||
return True
|
||||
|
||||
if show_log:
|
||||
logger.debug("Running PreProcessors...")
|
||||
|
||||
try:
|
||||
if not await Matcher.check_perm(
|
||||
bot, event, stack, dependency_cache
|
||||
) or not await Matcher.check_rule(bot, event, state, stack, dependency_cache):
|
||||
return
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _event_preprocessors
|
||||
)
|
||||
)
|
||||
except IgnoredException as e:
|
||||
logger.opt(colors=True).info(
|
||||
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>"
|
||||
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
|
||||
"Event ignored!</bg #f8bbd0></r>"
|
||||
)
|
||||
return
|
||||
return False
|
||||
|
||||
if Matcher.temp:
|
||||
with contextlib.suppress(Exception):
|
||||
Matcher.destroy()
|
||||
await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
|
||||
return True
|
||||
|
||||
|
||||
async def _run_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
async def _apply_event_postprocessors(
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
show_log: bool = True,
|
||||
) -> None:
|
||||
logger.info(f"Event will be handled by {Matcher}")
|
||||
"""运行事件后处理。
|
||||
|
||||
matcher = Matcher()
|
||||
if coros := [
|
||||
参数:
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
show_log: 是否显示日志
|
||||
"""
|
||||
if not _event_postprocessors:
|
||||
return
|
||||
|
||||
if show_log:
|
||||
logger.debug("Running PostProcessors...")
|
||||
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _event_postprocessors
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
|
||||
async def _apply_run_preprocessors(
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
matcher: Matcher,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> bool:
|
||||
"""运行事件响应器运行前处理。
|
||||
|
||||
参数:
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
matcher: 事件响应器
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
|
||||
返回:
|
||||
是否继续处理事件
|
||||
"""
|
||||
if not _run_preprocessors:
|
||||
return True
|
||||
|
||||
# ensure matcher function can be correctly called
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
matcher=matcher,
|
||||
@ -165,33 +268,46 @@ async def _run_matcher(
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _run_preprocessors
|
||||
]:
|
||||
# ensure matcher function can be correctly called
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
)
|
||||
)
|
||||
except IgnoredException:
|
||||
logger.opt(colors=True).info(f"{matcher} running is <b>cancelled</b>")
|
||||
return
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running RunPreProcessors. Running cancelled!</bg #f8bbd0></r>"
|
||||
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def _apply_run_postprocessors(
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
matcher: Matcher,
|
||||
exception: Optional[Exception] = None,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> None:
|
||||
"""运行事件响应器运行后处理。
|
||||
|
||||
Args:
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
matcher: 事件响应器
|
||||
exception: 事件响应器运行异常
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
"""
|
||||
if not _run_postprocessors:
|
||||
return
|
||||
|
||||
exception = None
|
||||
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
logger.debug(f"Running {matcher}")
|
||||
await matcher.run(bot, event, state, stack, dependency_cache)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
|
||||
)
|
||||
exception = e
|
||||
|
||||
if coros := [
|
||||
await asyncio.gather(
|
||||
*(
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
matcher=matcher,
|
||||
@ -205,20 +321,158 @@ async def _run_matcher(
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _run_postprocessors
|
||||
]:
|
||||
# ensure matcher function can be correctly called
|
||||
with matcher.ensure_context(bot, event):
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running RunPostProcessors</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
|
||||
async def _check_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> bool:
|
||||
"""检查事件响应器是否符合运行条件。
|
||||
|
||||
请注意,过时的事件响应器将被**销毁**。对于未过时的事件响应器,将会一次检查其响应类型、权限和规则。
|
||||
|
||||
参数:
|
||||
Matcher: 要检查的事件响应器
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
|
||||
返回:
|
||||
bool: 是否符合运行条件
|
||||
"""
|
||||
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
|
||||
with contextlib.suppress(Exception):
|
||||
Matcher.destroy()
|
||||
return False
|
||||
|
||||
try:
|
||||
if not await Matcher.check_perm(
|
||||
bot, event, stack, dependency_cache
|
||||
) or not await Matcher.check_rule(bot, event, state, stack, dependency_cache):
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>"
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def _run_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> None:
|
||||
"""运行事件响应器。
|
||||
|
||||
临时事件响应器将在运行前被**销毁**。
|
||||
|
||||
参数:
|
||||
Matcher: 事件响应器
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
|
||||
异常:
|
||||
StopPropagation: 阻止事件继续传播
|
||||
"""
|
||||
logger.info(f"Event will be handled by {Matcher}")
|
||||
|
||||
if Matcher.temp:
|
||||
with contextlib.suppress(Exception):
|
||||
Matcher.destroy()
|
||||
|
||||
matcher = Matcher()
|
||||
|
||||
if not await _apply_run_preprocessors(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
matcher=matcher,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
):
|
||||
return
|
||||
|
||||
exception = None
|
||||
|
||||
try:
|
||||
logger.debug(f"Running {matcher}")
|
||||
await matcher.run(bot, event, state, stack, dependency_cache)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Running {matcher} failed.</bg #f8bbd0></r>"
|
||||
)
|
||||
exception = e
|
||||
|
||||
await _apply_run_postprocessors(
|
||||
bot=bot,
|
||||
event=event,
|
||||
matcher=matcher,
|
||||
exception=exception,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
|
||||
if matcher.block:
|
||||
raise StopPropagation
|
||||
|
||||
|
||||
async def check_and_run_matcher(
|
||||
Matcher: Type[Matcher],
|
||||
bot: "Bot",
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> None:
|
||||
"""检查并运行事件响应器。
|
||||
|
||||
参数:
|
||||
Matcher: 事件响应器
|
||||
bot: Bot 对象
|
||||
event: Event 对象
|
||||
state: 会话状态
|
||||
stack: 异步上下文栈
|
||||
dependency_cache: 依赖缓存
|
||||
"""
|
||||
if not await _check_matcher(
|
||||
Matcher=Matcher,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
):
|
||||
return
|
||||
|
||||
await _run_matcher(
|
||||
Matcher=Matcher,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
)
|
||||
|
||||
|
||||
async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
"""处理一个事件。调用该函数以实现分发事件。
|
||||
@ -245,34 +499,15 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
state: Dict[Any, Any] = {}
|
||||
dependency_cache: T_DependencyCache = {}
|
||||
|
||||
# create event scope context
|
||||
async with AsyncExitStack() as stack:
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
if not await _apply_event_preprocessors(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _event_preprocessors
|
||||
]:
|
||||
try:
|
||||
if show_log:
|
||||
logger.debug("Running PreProcessors...")
|
||||
await asyncio.gather(*coros)
|
||||
except IgnoredException as e:
|
||||
logger.opt(colors=True).info(
|
||||
f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
|
||||
)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
|
||||
"Event ignored!</bg #f8bbd0></r>"
|
||||
)
|
||||
):
|
||||
return
|
||||
|
||||
# Trie Match
|
||||
@ -284,6 +519,7 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
)
|
||||
|
||||
break_flag = False
|
||||
# iterate through all priority until stop propagation
|
||||
for priority in sorted(matchers.keys()):
|
||||
if break_flag:
|
||||
break
|
||||
@ -292,14 +528,12 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
logger.debug(f"Checking for matchers in priority {priority}...")
|
||||
|
||||
pending_tasks = [
|
||||
_check_matcher(
|
||||
check_and_run_matcher(
|
||||
matcher, bot, event, state.copy(), stack, dependency_cache
|
||||
)
|
||||
for matcher in matchers[priority]
|
||||
]
|
||||
|
||||
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
|
||||
|
||||
for result in results:
|
||||
if not isinstance(result, Exception):
|
||||
continue
|
||||
@ -314,24 +548,4 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
if show_log:
|
||||
logger.debug("Checking for matchers completed")
|
||||
|
||||
if coros := [
|
||||
run_coro_with_catch(
|
||||
proc(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
stack=stack,
|
||||
dependency_cache=dependency_cache,
|
||||
),
|
||||
(SkippedException,),
|
||||
)
|
||||
for proc in _event_postprocessors
|
||||
]:
|
||||
try:
|
||||
if show_log:
|
||||
logger.debug("Running PostProcessors...")
|
||||
await asyncio.gather(*coros)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
|
||||
)
|
||||
await _apply_event_postprocessors(bot, event, state, stack, dependency_cache)
|
||||
|
@ -113,8 +113,7 @@ def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]:
|
||||
loop = asyncio.get_running_loop()
|
||||
pfunc = partial(call, *args, **kwargs)
|
||||
context = copy_context()
|
||||
context_run = context.run
|
||||
result = await loop.run_in_executor(None, context_run, pfunc)
|
||||
result = await loop.run_in_executor(None, partial(context.run, pfunc))
|
||||
return result
|
||||
|
||||
return _wrapper
|
||||
@ -139,6 +138,7 @@ async def run_sync_ctx_manager(
|
||||
async def run_coro_with_catch(
|
||||
coro: Coroutine[Any, Any, T],
|
||||
exc: Tuple[Type[Exception], ...],
|
||||
return_on_err: None = None,
|
||||
) -> Union[T, None]:
|
||||
...
|
||||
|
||||
|
@ -25,6 +25,6 @@ def load_plugin(nonebug_init: None) -> Set["Plugin"]:
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def load_example(nonebug_init: None) -> Set["Plugin"]:
|
||||
# preload example plugins
|
||||
return nonebot.load_plugins(str(Path(__file__).parent / "examples"))
|
||||
def load_builtin_plugin(nonebug_init: None) -> Set["Plugin"]:
|
||||
# preload builtin plugins
|
||||
return nonebot.load_builtin_plugins("echo", "single_session")
|
||||
|
@ -1,29 +0,0 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.params import Arg, CommandArg, ArgPlainText
|
||||
|
||||
weather = on_command("weather", rule=to_me(), aliases={"天气", "天气预报"}, priority=5)
|
||||
|
||||
|
||||
@weather.handle()
|
||||
async def handle_first_receive(matcher: Matcher, args: Message = CommandArg()):
|
||||
plain_text = args.extract_plain_text() # 首次发送命令时跟随的参数,例:/天气 上海,则args为上海
|
||||
if plain_text:
|
||||
matcher.set_arg("city", args) # 如果用户发送了参数则直接赋值
|
||||
|
||||
|
||||
@weather.got("city", prompt="你想查询哪个城市的天气呢?")
|
||||
async def handle_city(city: Message = Arg(), city_name: str = ArgPlainText("city")):
|
||||
if city_name not in ["北京", "上海"]: # 如果参数不符合要求,则提示用户重新输入
|
||||
# 可以使用平台的 Message 类直接构造模板消息
|
||||
await weather.reject(city.template("你想查询的城市 {city} 暂不支持,请重新输入!"))
|
||||
|
||||
city_weather = await get_weather(city_name)
|
||||
await weather.finish(city_weather)
|
||||
|
||||
|
||||
# 在这里编写获取天气信息的函数
|
||||
async def get_weather(city: str) -> str:
|
||||
return f"{city}的天气是..."
|
388
tests/test_broadcast.py
Normal file
388
tests/test_broadcast.py
Normal file
@ -0,0 +1,388 @@
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
from nonebot import on_message
|
||||
import nonebot.message as message
|
||||
from utils import make_fake_event
|
||||
from nonebot.params import Depends
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.log import logger, default_filter, default_format
|
||||
from nonebot.message import (
|
||||
run_preprocessor,
|
||||
run_postprocessor,
|
||||
event_preprocessor,
|
||||
event_postprocessor,
|
||||
)
|
||||
|
||||
|
||||
async def _dependency() -> int:
|
||||
return 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_event_preprocessors", set())
|
||||
|
||||
runned = False
|
||||
|
||||
@event_preprocessor
|
||||
async def test_preprocessor(
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
sub: int = Depends(_dependency),
|
||||
default: int = 1,
|
||||
):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._event_preprocessors
|
||||
}
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
|
||||
assert runned, "event_preprocessor should runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_event_preprocessors", set())
|
||||
|
||||
@event_preprocessor
|
||||
async def test_preprocessor():
|
||||
raise IgnoredException("pass")
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._event_preprocessors
|
||||
}
|
||||
|
||||
runned = False
|
||||
|
||||
async def handler():
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message(handlers=[handler])
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
|
||||
assert not runned, "matcher should not runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_preprocessor_exception(
|
||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_event_preprocessors", set())
|
||||
|
||||
@event_preprocessor
|
||||
async def test_preprocessor():
|
||||
raise RuntimeError("test")
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._event_preprocessors
|
||||
}
|
||||
|
||||
runned = False
|
||||
|
||||
async def handler():
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
handler_id = logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
diagnose=False,
|
||||
filter=default_filter,
|
||||
format=default_format,
|
||||
)
|
||||
|
||||
try:
|
||||
with app.provider.context({}):
|
||||
matcher = on_message(handlers=[handler])
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
finally:
|
||||
logger.remove(handler_id)
|
||||
|
||||
assert not runned, "matcher should not runned"
|
||||
assert "RuntimeError: test" in capsys.readouterr().out
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_event_postprocessors", set())
|
||||
|
||||
runned = False
|
||||
|
||||
@event_postprocessor
|
||||
async def test_postprocessor(
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
sub: int = Depends(_dependency),
|
||||
default: int = 1,
|
||||
):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
assert test_postprocessor in {
|
||||
dependent.call for dependent in message._event_postprocessors
|
||||
}
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
|
||||
assert runned, "event_postprocessor should runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_event_postprocessor_exception(
|
||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_event_postprocessors", set())
|
||||
|
||||
@event_postprocessor
|
||||
async def test_postprocessor():
|
||||
raise RuntimeError("test")
|
||||
|
||||
assert test_postprocessor in {
|
||||
dependent.call for dependent in message._event_postprocessors
|
||||
}
|
||||
|
||||
handler_id = logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
diagnose=False,
|
||||
filter=default_filter,
|
||||
format=default_format,
|
||||
)
|
||||
|
||||
try:
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
finally:
|
||||
logger.remove(handler_id)
|
||||
|
||||
assert "RuntimeError: test" in capsys.readouterr().out
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_preprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_run_preprocessors", set())
|
||||
|
||||
runned = False
|
||||
|
||||
@run_preprocessor
|
||||
async def test_preprocessor(
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
matcher: Matcher,
|
||||
sub: int = Depends(_dependency),
|
||||
default: int = 1,
|
||||
):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
await matcher.send("test")
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._run_preprocessors
|
||||
}
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "test", True, bot)
|
||||
|
||||
assert runned, "run_preprocessor should runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_preprocessor_ignore(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_run_preprocessors", set())
|
||||
|
||||
@run_preprocessor
|
||||
async def test_preprocessor():
|
||||
raise IgnoredException("pass")
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._run_preprocessors
|
||||
}
|
||||
|
||||
runned = False
|
||||
|
||||
async def handler():
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message(handlers=[handler])
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
|
||||
assert not runned, "matcher should not runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_preprocessor_exception(
|
||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_run_preprocessors", set())
|
||||
|
||||
@run_preprocessor
|
||||
async def test_preprocessor():
|
||||
raise RuntimeError("test")
|
||||
|
||||
assert test_preprocessor in {
|
||||
dependent.call for dependent in message._run_preprocessors
|
||||
}
|
||||
|
||||
runned = False
|
||||
|
||||
async def handler():
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
handler_id = logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
diagnose=False,
|
||||
filter=default_filter,
|
||||
format=default_format,
|
||||
)
|
||||
|
||||
try:
|
||||
with app.provider.context({}):
|
||||
matcher = on_message(handlers=[handler])
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
finally:
|
||||
logger.remove(handler_id)
|
||||
|
||||
assert not runned, "matcher should not runned"
|
||||
assert "RuntimeError: test" in capsys.readouterr().out
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_postprocessor(app: App, monkeypatch: pytest.MonkeyPatch):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_run_postprocessors", set())
|
||||
|
||||
runned = False
|
||||
|
||||
@run_postprocessor
|
||||
async def test_postprocessor(
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
matcher: Matcher,
|
||||
exception: Optional[Exception],
|
||||
sub: int = Depends(_dependency),
|
||||
default: int = 1,
|
||||
):
|
||||
nonlocal runned
|
||||
runned = True
|
||||
|
||||
await matcher.send("test")
|
||||
|
||||
assert test_postprocessor in {
|
||||
dependent.call for dependent in message._run_postprocessors
|
||||
}
|
||||
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "test", True, bot)
|
||||
|
||||
assert runned, "run_postprocessor should runned"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_postprocessor_exception(
|
||||
app: App, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
):
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(message, "_run_postprocessors", set())
|
||||
|
||||
@run_postprocessor
|
||||
async def test_postprocessor():
|
||||
raise RuntimeError("test")
|
||||
|
||||
assert test_postprocessor in {
|
||||
dependent.call for dependent in message._run_postprocessors
|
||||
}
|
||||
|
||||
handler_id = logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
diagnose=False,
|
||||
filter=default_filter,
|
||||
format=default_format,
|
||||
)
|
||||
|
||||
try:
|
||||
with app.provider.context({}):
|
||||
matcher = on_message()
|
||||
|
||||
async with app.test_matcher(matcher) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
event = make_fake_event()()
|
||||
ctx.receive_event(bot, event)
|
||||
finally:
|
||||
logger.remove(handler_id)
|
||||
|
||||
assert "RuntimeError: test" in capsys.readouterr().out
|
@ -1,76 +0,0 @@
|
||||
import pytest
|
||||
from nonebug import App
|
||||
|
||||
from utils import make_fake_event, make_fake_message
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_weather(app: App):
|
||||
from examples.weather import weather
|
||||
|
||||
# 将此处的 make_fake_message() 替换为你要发送的平台消息 Message 类型
|
||||
# from nonebot.adapters.console import Message
|
||||
Message = make_fake_message()
|
||||
|
||||
async with app.test_matcher(weather) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
|
||||
msg = Message("/天气 上海")
|
||||
# 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
|
||||
# from nonebot.adapters.console import MessageEvent
|
||||
# event = MessageEvent(message=msg, to_me=True, ...)
|
||||
event = make_fake_event(_message=msg, _to_me=True)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "上海的天气是...", True)
|
||||
ctx.should_finished()
|
||||
|
||||
async with app.test_matcher(weather) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
|
||||
msg = Message("/天气 南京")
|
||||
# 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
|
||||
event = make_fake_event(_message=msg, _to_me=True)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(
|
||||
event,
|
||||
Message.template("你想查询的城市 {} 暂不支持,请重新输入!").format("南京"),
|
||||
True,
|
||||
)
|
||||
ctx.should_rejected()
|
||||
|
||||
msg = Message("北京")
|
||||
event = make_fake_event(_message=msg)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "北京的天气是...", True)
|
||||
ctx.should_finished()
|
||||
|
||||
async with app.test_matcher(weather) as ctx:
|
||||
bot = ctx.create_bot()
|
||||
|
||||
msg = Message("/天气")
|
||||
# 将此处的 make_fake_event() 替换为你要发送的平台事件 Event 类型
|
||||
event = make_fake_event(_message=msg, _to_me=True)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "你想查询哪个城市的天气呢?", True)
|
||||
|
||||
msg = Message("杭州")
|
||||
event = make_fake_event(_message=msg)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(
|
||||
event,
|
||||
Message.template("你想查询的城市 {} 暂不支持,请重新输入!").format("杭州"),
|
||||
True,
|
||||
)
|
||||
ctx.should_rejected()
|
||||
|
||||
msg = Message("北京")
|
||||
event = make_fake_event(_message=msg)()
|
||||
|
||||
ctx.receive_event(bot, event)
|
||||
ctx.should_call_send(event, "北京的天气是...", True)
|
||||
ctx.should_finished()
|
@ -2,8 +2,8 @@ import pytest
|
||||
from nonebug import App
|
||||
|
||||
from nonebot.permission import User
|
||||
from nonebot.message import _check_matcher
|
||||
from nonebot.matcher import Matcher, matchers
|
||||
from nonebot.message import check_and_run_matcher
|
||||
from utils import make_fake_event, make_fake_message
|
||||
|
||||
|
||||
@ -200,19 +200,19 @@ async def test_expire(app: App):
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert test_temp_matcher in matchers[test_temp_matcher.priority]
|
||||
await _check_matcher(test_temp_matcher, bot, event, {})
|
||||
await check_and_run_matcher(test_temp_matcher, bot, event, {})
|
||||
assert test_temp_matcher not in matchers[test_temp_matcher.priority]
|
||||
|
||||
event = make_fake_event()()
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert test_datetime_matcher in matchers[test_datetime_matcher.priority]
|
||||
await _check_matcher(test_datetime_matcher, bot, event, {})
|
||||
await check_and_run_matcher(test_datetime_matcher, bot, event, {})
|
||||
assert test_datetime_matcher not in matchers[test_datetime_matcher.priority]
|
||||
|
||||
event = make_fake_event()()
|
||||
async with app.test_api() as ctx:
|
||||
bot = ctx.create_bot()
|
||||
assert test_timedelta_matcher in matchers[test_timedelta_matcher.priority]
|
||||
await _check_matcher(test_timedelta_matcher, bot, event, {})
|
||||
await check_and_run_matcher(test_timedelta_matcher, bot, event, {})
|
||||
assert test_timedelta_matcher not in matchers[test_timedelta_matcher.priority]
|
||||
|
@ -22,11 +22,11 @@ async def test_load_plugin():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_plugins(load_plugin: Set[Plugin], load_example: Set[Plugin]):
|
||||
async def test_load_plugins(load_plugin: Set[Plugin], load_builtin_plugin: Set[Plugin]):
|
||||
loaded_plugins = {
|
||||
plugin for plugin in nonebot.get_loaded_plugins() if not plugin.parent_plugin
|
||||
}
|
||||
assert loaded_plugins >= load_plugin | load_example
|
||||
assert loaded_plugins >= load_plugin | load_builtin_plugin
|
||||
|
||||
# check simple plugin
|
||||
assert "plugins.export" in sys.modules
|
||||
|
Loading…
Reference in New Issue
Block a user