⚗️ add handler class

This commit is contained in:
yanyongyu 2021-03-18 19:16:18 +08:00
parent 62135dc117
commit 6bd12b7f2c
3 changed files with 208 additions and 92 deletions

View File

@ -7,7 +7,6 @@
import abc
from copy import copy
from typing_extensions import Literal
from functools import reduce, partial
from dataclasses import dataclass, field
from typing import Any, Dict, Union, TypeVar, Mapping, Optional, Callable, Iterable, Iterator, Awaitable, TYPE_CHECKING

161
nonebot/handler.py Normal file
View File

@ -0,0 +1,161 @@
"""
事件处理函数
===========
该模块实现事件处理函数的封装以实现动态参数等功能
"""
import inspect
from typing_extensions import Literal
from typing import Any, List, Dict, Type, Union, Optional, TYPE_CHECKING
from typing import ForwardRef, _eval_type # type: ignore
from nonebot.log import logger
from nonebot.typing import T_Handler, T_State
if TYPE_CHECKING:
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event
class HandlerMeta(type):
...
class Handler(metaclass=HandlerMeta):
"""事件处理函数类"""
def __init__(self, func: T_Handler, module: Optional[str] = None):
self.func: T_Handler = func
"""
:类型: ``T_Handler``
:说明: 事件处理函数
"""
self.module: Optional[str] = module
"""
:类型: ``Optional[str]``
:说明: 事件处理函数所在模块名称
"""
self.signature: inspect.Signature = self.get_signature()
"""
:类型: ``inspect.Signature``
:说明: 事件处理函数签名
"""
async def __call__(self, matcher: "Matcher", bot: "Bot", event: "Event",
state: T_State):
params = {
param.name: param.annotation
for param in self.signature.parameters.values()
}
BotType = ((params["bot"] is not inspect.Parameter.empty) and
inspect.isclass(params["bot"]) and params["bot"])
if BotType and not isinstance(bot, BotType):
logger.debug(
f"Matcher {matcher} bot type {type(bot)} not match annotation {BotType}, ignored"
)
return
EventType = ((params["event"] is not inspect.Parameter.empty) and
inspect.isclass(params["event"]) and params["event"])
if EventType and not isinstance(event, EventType):
logger.debug(
f"Matcher {matcher} event type {type(event)} not match annotation {EventType}, ignored"
)
return
args = {"bot": bot, "event": event, "state": state, "matcher": matcher}
await self.func(
**{k: v for k, v in args.items() if params[k] is not None})
@property
def bot_type(self) -> Type["Bot"]:
return self.signature.parameters["bot"].annotation
@property
def event_type(self) -> Optional[Type["Event"]]:
if "event" not in self.signature:
return None
return self.signature.parameters["event"].annotation
@property
def state_type(self) -> Optional[T_State]:
if "state" not in self.signature:
return None
return self.signature.parameters["state"].annotation
@property
def matcher_type(self) -> Optional[Type["Matcher"]]:
if "matcher" not in self.signature:
return None
return self.signature.parameters["matcher"].annotation
def get_signature(self) -> inspect.Signature:
wrapped_signature = self._get_typed_signature()
signature = self._get_typed_signature(False)
self._check_params(signature)
self._check_bot_param(signature)
self._check_bot_param(wrapped_signature)
signature.parameters["bot"].annotation = wrapped_signature.parameters[
"bot"].annotation
if "event" in wrapped_signature.parameters and "event" in signature.parameters:
signature.parameters[
"event"].annotation = wrapped_signature.parameters[
"event"].annotation
return signature
def update_signature(
self, **kwargs: Union[None, Type["Bot"], Type["Event"], Type["Matcher"],
T_State]
) -> None:
params: List[inspect.Parameter] = []
for param in ["bot", "event", "state", "matcher"]:
sig = self.signature.parameters.get(param, None)
if param in kwargs:
sig = inspect.Parameter(param,
inspect.Parameter.POSITIONAL_OR_KEYWORD,
annotation=kwargs[param])
if sig:
params.append(sig)
self.signature = inspect.Signature(params)
def _get_typed_signature(self,
follow_wrapped: bool = True) -> inspect.Signature:
signature = inspect.signature(self.func, follow_wrapped=follow_wrapped)
globalns = getattr(self, "__globals__", {})
typed_params = [
inspect.Parameter(
name=param.name,
kind=param.kind,
default=param.default,
annotation=param.annotation if follow_wrapped else
self._get_typed_annotation(param, globalns),
) for param in signature.parameters.values()
]
typed_signature = inspect.Signature(typed_params)
return typed_signature
def _get_typed_annotation(self, param: inspect.Parameter,
globalns: Dict[str, Any]) -> Any:
try:
if isinstance(param.annotation, str):
return _eval_type(ForwardRef(param.annotation), globalns,
globalns)
else:
return param.annotation
except Exception:
return param.annotation
def _check_params(self, signature: inspect.Signature):
if not set(signature.parameters.keys()) < {
"bot", "event", "state", "matcher"
}:
raise ValueError(
"Handler param names must in `bot`/`event`/`state`/`matcher`")
def _check_bot_param(self, signature: inspect.Signature):
if not any(
param.name == "bot" for param in signature.parameters.values()):
raise ValueError("Handler missing parameter 'bot'")

View File

@ -2,9 +2,10 @@
事件响应器
==========
该模块实现事件响应器的创建与运行并提供一些快捷方法来帮助用户更好的与机器人进行 对话
该模块实现事件响应器的创建与运行并提供一些快捷方法来帮助用户更好的与机器人进行对话
"""
import sys
import inspect
from functools import wraps
from datetime import datetime
@ -14,6 +15,7 @@ from typing import Type, List, Dict, Union, Mapping, Iterable, Callable, Optiona
from nonebot.rule import Rule
from nonebot.log import logger
from nonebot.handler import Handler
from nonebot.permission import Permission, USER
from nonebot.typing import T_State, T_StateFactory, T_Handler, T_ArgsParser, T_TypeUpdater, T_PermissionUpdater
from nonebot.exception import PausedException, RejectedException, FinishedException, StopPropagation
@ -31,11 +33,21 @@ current_event: ContextVar = ContextVar("current_event")
class MatcherMeta(type):
if TYPE_CHECKING:
module: Optional[str]
type: str
rule: Rule
permission: Permission
handlers: List[T_Handler]
priority: int
block: bool
temp: bool
expire_time: Optional[datetime]
def __repr__(self) -> str:
return (f"<Matcher from {self.module or 'unknow'}, " # type: ignore
f"type={self.type}, priority={self.priority}, " # type: ignore
f"temp={self.temp}>") # type: ignore
return (f"<Matcher from {self.module or 'unknow'}, "
f"type={self.type}, priority={self.priority}, "
f"temp={self.temp}>")
def __str__(self) -> str:
return repr(self)
@ -64,9 +76,9 @@ class Matcher(metaclass=MatcherMeta):
:类型: ``Permission``
:说明: 事件响应器触发权限
"""
handlers: List[T_Handler] = []
handlers: List[Handler] = []
"""
:类型: ``List[T_Handler]``
:类型: ``List[Handler]``
:说明: 事件响应器拥有的事件处理函数列表
"""
priority: int = 1
@ -134,7 +146,7 @@ class Matcher(metaclass=MatcherMeta):
type_: str = "",
rule: Optional[Rule] = None,
permission: Optional[Permission] = None,
handlers: Optional[List[T_Handler]] = None,
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
@ -178,7 +190,9 @@ class Matcher(metaclass=MatcherMeta):
"permission":
permission or Permission(),
"handlers": [
cls.process_handler(handler) for handler in handlers
handler
if isinstance(handler, Handler) else Handler(handler)
for handler in handlers
] if handlers else [],
"temp":
temp,
@ -284,27 +298,13 @@ class Matcher(metaclass=MatcherMeta):
cls._default_permission_updater = func
return func
@staticmethod
def process_handler(handler: T_Handler) -> T_Handler:
signature = inspect.signature(handler, follow_wrapped=False)
bot = signature.parameters.get("bot")
event = signature.parameters.get("event")
state = signature.parameters.get("state")
matcher = signature.parameters.get("matcher")
if not bot:
raise ValueError("Handler missing parameter 'bot'")
handler.__params__ = {
"bot": bot.annotation,
"event": event.annotation if event else None,
"state": T_State if state else None,
"matcher": matcher.annotation if matcher else None
}
return handler
@classmethod
def append_handler(cls, handler: T_Handler) -> None:
# Process handler first
cls.handlers.append(cls.process_handler(handler))
def append_handler(cls,
handler: T_Handler,
module: Optional[str] = None) -> Handler:
handler_ = Handler(handler, module)
cls.handlers.append(handler_)
return handler_
@classmethod
def handle(cls) -> Callable[[T_Handler], T_Handler]:
@ -319,6 +319,9 @@ class Matcher(metaclass=MatcherMeta):
"""
def _decorator(func: T_Handler) -> T_Handler:
print(
sys._getframe(1).f_code.co_filename,
sys._getframe(1).f_code.co_name)
cls.append_handler(func)
return func
@ -339,23 +342,17 @@ class Matcher(metaclass=MatcherMeta):
async def _receive(bot: "Bot", event: "Event") -> NoReturn:
raise PausedException
cls.process_handler(_receive)
if cls.handlers:
# 已有前置handlers则接受一条新的消息否则视为接收初始消息
cls.append_handler(_receive)
receive_handler = cls.append_handler(_receive)
else:
receive_handler = None
def _decorator(func: T_Handler) -> T_Handler:
cls.process_handler(func)
if not cls.handlers or cls.handlers[-1] is not func:
cls.append_handler(func)
_receive.__params__.update({
"bot":
func.__params__["bot"],
"event":
func.__params__["event"] or _receive.__params__["event"]
})
func_handler = cls.append_handler(func)
receive_handler.update_signature(bot=func_handler.bot_type,
event=func_handler.event_type)
return func
@ -416,42 +413,27 @@ class Matcher(metaclass=MatcherMeta):
else:
state[state["_current_key"]] = str(event.get_message())
cls.append_handler(_key_getter)
cls.append_handler(_key_parser)
getter_handler = cls.append_handler(_key_getter)
parser_handler = cls.append_handler(_key_parser)
def _decorator(func: T_Handler) -> T_Handler:
if not hasattr(cls.handlers[-1], "__wrapped__"):
cls.process_handler(func)
parser = cls.handlers.pop()
@wraps(func)
async def wrapper(bot: "Bot", event: "Event", state: T_State,
matcher: Matcher):
await matcher.run_handler(parser, bot, event, state)
await matcher.run_handler(func, bot, event, state)
await parser(matcher, bot, event, state)
await func_handler(matcher, bot, event, state)
if "_current_key" in state:
del state["_current_key"]
cls.append_handler(wrapper)
func_handler = cls.append_handler(wrapper)
wrapper.__params__.update({
"bot":
func.__params__["bot"],
"event":
func.__params__["event"] or wrapper.__params__["event"]
})
_key_getter.__params__.update({
"bot":
func.__params__["bot"],
"event":
func.__params__["event"] or wrapper.__params__["event"]
})
_key_parser.__params__.update({
"bot":
func.__params__["bot"],
"event":
func.__params__["event"] or wrapper.__params__["event"]
})
getter_handler.update_signature(bot=func_handler.bot_type,
event=func_handler.event_type)
parser_handler.update_signature(bot=func_handler.bot_type,
event=func_handler.event_type)
return func
@ -545,32 +527,6 @@ class Matcher(metaclass=MatcherMeta):
"""
self.block = True
async def run_handler(self, handler: T_Handler, bot: "Bot", event: "Event",
state: T_State):
if not hasattr(handler, "__params__"):
self.process_handler(handler)
params = getattr(handler, "__params__")
BotType = ((params["bot"] is not inspect.Parameter.empty) and
inspect.isclass(params["bot"]) and params["bot"])
if BotType and not isinstance(bot, BotType):
logger.debug(
f"Matcher {self} bot type {type(bot)} not match annotation {BotType}, ignored"
)
return
EventType = ((params["event"] is not inspect.Parameter.empty) and
inspect.isclass(params["event"]) and params["event"])
if EventType and not isinstance(event, EventType):
logger.debug(
f"Matcher {self} event type {type(event)} not match annotation {EventType}, ignored"
)
return
args = {"bot": bot, "event": event, "state": state, "matcher": self}
await handler(
**{k: v for k, v in args.items() if params[k] is not None})
# 运行handlers
async def run(self, bot: "Bot", event: "Event", state: T_State):
b_t = current_bot.set(bot)
@ -583,7 +539,7 @@ class Matcher(metaclass=MatcherMeta):
for _ in range(len(self.handlers)):
handler = self.handlers.pop(0)
await self.run_handler(handler, bot, event, state_)
await handler(self, bot, event, state_)
except RejectedException:
self.handlers.insert(0, handler) # type: ignore