Feature: 优化事件分发方法 (#2067)

This commit is contained in:
Ju4tCode
2023-05-30 15:20:31 +08:00
committed by GitHub
parent 3199fc454a
commit f52abc8314
8 changed files with 728 additions and 231 deletions

View File

@ -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")

View File

@ -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
View 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

View File

@ -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()

View File

@ -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]

View File

@ -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