From c454cf08743dead351ee8edb94391cdc60afcabd Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Fri, 12 Nov 2021 18:10:40 +0800 Subject: [PATCH 01/21] :construction: process handler dependency --- nonebot/dependencies/__init__.py | 2 + nonebot/dependencies/models.py | 89 +++++++++++++++++ nonebot/dependencies/utils.py | 150 ++++++++++++++++++++++++++++ nonebot/handler.py | 161 ++----------------------------- nonebot/typing.py | 13 +-- nonebot/utils.py | 7 ++ 6 files changed, 256 insertions(+), 166 deletions(-) create mode 100644 nonebot/dependencies/__init__.py create mode 100644 nonebot/dependencies/models.py create mode 100644 nonebot/dependencies/utils.py diff --git a/nonebot/dependencies/__init__.py b/nonebot/dependencies/__init__.py new file mode 100644 index 00000000..95e6a040 --- /dev/null +++ b/nonebot/dependencies/__init__.py @@ -0,0 +1,2 @@ +from .models import Depends as Depends +from .utils import get_dependent as get_dependent diff --git a/nonebot/dependencies/models.py b/nonebot/dependencies/models.py new file mode 100644 index 00000000..4232ccec --- /dev/null +++ b/nonebot/dependencies/models.py @@ -0,0 +1,89 @@ +from enum import Enum +from typing import Any, List, Callable, Optional + +from pydantic.fields import Required, FieldInfo, ModelField + +from nonebot.utils import get_name + + +class Depends: + + def __init__(self, + dependency: Optional[Callable[..., Any]] = None, + *, + use_cache: bool = True) -> None: + self.dependency = dependency + self.use_cache = use_cache + + def __repr__(self) -> str: + dep = get_name(self.dependency) + cache = "" if self.use_cache else ", use_cache=False" + return f"{self.__class__.__name__}({dep}{cache})" + + +class Dependent: + + def __init__(self, + *, + func: Optional[Callable[..., Any]] = None, + name: Optional[str] = None, + bot_param: Optional[ModelField] = None, + event_param: Optional[ModelField] = None, + state_param: Optional[ModelField] = None, + matcher_param: Optional[ModelField] = None, + simple_params: Optional[List[ModelField]] = None, + dependencies: Optional[List["Dependent"]] = None, + use_cache: bool = True) -> None: + self.func = func + self.name = name + self.bot_param = bot_param + self.event_param = event_param + self.state_param = state_param + self.matcher_param = matcher_param + self.simple_params = simple_params or [] + self.dependencies = dependencies or [] + self.use_cache = use_cache + self.cache_key = (self.func,) + + +class ParamTypes(Enum): + BOT = "bot" + EVENT = "event" + STATE = "state" + MATCHER = "matcher" + SIMPLE = "simple" + + +class Param(FieldInfo): + in_: ParamTypes + + def __init__(self, default: Any): + super().__init__(default=default) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}" + + +class BotParam(Param): + in_ = ParamTypes.BOT + + +class EventParam(Param): + in_ = ParamTypes.EVENT + + +class StateParam(Param): + in_ = ParamTypes.STATE + + +class MatcherParam(Param): + in_ = ParamTypes.MATCHER + + +class SimpleParam(Param): + in_ = ParamTypes.SIMPLE + + def __init__(self, default: Any): + if default is Required: + raise ValueError("SimpleParam should be given a default value") + super().__init__(default) diff --git a/nonebot/dependencies/utils.py b/nonebot/dependencies/utils.py new file mode 100644 index 00000000..9503fbf1 --- /dev/null +++ b/nonebot/dependencies/utils.py @@ -0,0 +1,150 @@ +import inspect +from typing import Any, Dict, Type, Union, Callable, Optional, ForwardRef + +from pydantic import BaseConfig +from pydantic.class_validators import Validator +from pydantic.typing import evaluate_forwardref +from pydantic.schema import get_annotation_from_field_info +from pydantic.fields import Required, FieldInfo, ModelField, UndefinedType + +from .models import Param, Depends, Dependent, ParamTypes, SimpleParam + + +def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: + signature = inspect.signature(call) + globalns = getattr(call, "__globals__", {}) + typed_params = [ + inspect.Parameter( + name=param.name, + kind=param.kind, + default=param.default, + annotation=get_typed_annotation(param, globalns), + ) for param in signature.parameters.values() + ] + typed_signature = inspect.Signature(typed_params) + return typed_signature + + +def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, + Any]) -> Any: + annotation = param.annotation + if isinstance(annotation, str): + annotation = ForwardRef(annotation) + annotation = evaluate_forwardref(annotation, globalns, globalns) + return annotation + + +def get_param_sub_dependent(*, param: inspect.Parameter) -> Dependent: + depends: Depends = param.default + if depends.dependency: + dependency = depends.dependency + else: + dependency = param.annotation + return get_sub_dependant( + depends=depends, + dependency=dependency, + name=param.name, + ) + + +def get_parameterless_sub_dependant(*, depends: Depends) -> Dependent: + assert callable( + depends.dependency + ), "A parameter-less dependency must have a callable dependency" + return get_sub_dependant(depends=depends, dependency=depends.dependency) + + +def get_sub_dependant( + *, + depends: Depends, + dependency: Callable[..., Any], + name: Optional[str] = None, +) -> Dependent: + sub_dependant = get_dependent( + func=dependency, + name=name, + use_cache=depends.use_cache, + ) + return sub_dependant + + +def get_dependent(*, + func: Callable[..., Any], + name: Optional[str] = None, + use_cache: bool = True) -> Dependent: + signature = get_typed_signature(func) + params = signature.parameters + dependent = Dependent(func=func, name=name, use_cache=use_cache) + for param_name, param in params.items(): + if isinstance(param.default, Depends): + sub_dependent = get_param_sub_dependent(param=param) + dependent.dependencies.append(sub_dependent) + continue + param_field = get_param_field(param=param, + param_name=param_name, + default_field_info=SimpleParam) + + return dependent + + +def get_param_field(*, + param: inspect.Parameter, + param_name: str, + default_field_info: Type[Param] = Param, + force_type: Optional[ParamTypes] = None, + ignore_default: bool = False) -> ModelField: + default_value = Required + if param.default != param.empty and not ignore_default: + default_value = param.default + if isinstance(default_value, FieldInfo): + field_info = default_value + default_value = field_info.default + if (isinstance(field_info, Param) and + getattr(field_info, "in_", None) is None): + field_info.in_ = default_field_info.in_ + if force_type: + field_info.in_ = force_type # type: ignore + else: + field_info = default_field_info(default_value) + required: bool = default_value == Required + annotation: Any = Any + if param.annotation != param.empty: + annotation = param.annotation + annotation = get_annotation_from_field_info(annotation, field_info, + param_name) + if not field_info.alias and getattr(field_info, "convert_underscores", + None): + alias = param.name.replace("_", "-") + else: + alias = field_info.alias or param.name + field = create_field( + name=param.name, + type_=annotation, + default=None if required else default_value, + alias=alias, + required=required, + field_info=field_info, + ) + # field.required = required + + return field + + +def create_field(name: str, + type_: Type[Any], + class_validators: Optional[Dict[str, Validator]] = None, + default: Optional[Any] = None, + required: Union[bool, UndefinedType] = False, + model_config: Type[BaseConfig] = BaseConfig, + field_info: Optional[FieldInfo] = None, + alias: Optional[str] = None) -> ModelField: + class_validators = class_validators or {} + field_info = field_info or FieldInfo(None) + return ModelField(name=name, + type_=type_, + class_validators=class_validators, + model_config=model_config, + default=default, + required=required, + alias=alias, + field_info=field_info) diff --git a/nonebot/handler.py b/nonebot/handler.py index 055e6f88..ee31d4cf 100644 --- a/nonebot/handler.py +++ b/nonebot/handler.py @@ -6,171 +6,22 @@ """ import inspect -from typing import _eval_type # type: ignore -from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Optional, - ForwardRef) +from typing import Optional -from nonebot.log import logger -from nonebot.typing import T_State, T_Handler +from pydantic.typing import evaluate_forwardref -if TYPE_CHECKING: - from nonebot.matcher import Matcher - from nonebot.adapters import Bot, Event +from nonebot.utils import get_name +from nonebot.typing import T_Handler class Handler: """事件处理函数类""" - def __init__(self, func: T_Handler): + def __init__(self, func: T_Handler, *, name: Optional[str] = None): """装饰事件处理函数以便根据动态参数运行""" self.func: T_Handler = func """ :类型: ``T_Handler`` :说明: 事件处理函数 """ - self.signature: inspect.Signature = self.get_signature() - """ - :类型: ``inspect.Signature`` - :说明: 事件处理函数签名 - """ - - def __repr__(self) -> str: - return (f"") - - def __str__(self) -> str: - return repr(self) - - async def __call__(self, matcher: "Matcher", bot: "Bot", event: "Event", - state: T_State): - BotType = ((self.bot_type is not inspect.Parameter.empty) and - inspect.isclass(self.bot_type) and self.bot_type) - if BotType and not isinstance(bot, BotType): - logger.debug( - f"Matcher {matcher} bot type {type(bot)} not match annotation {BotType}, ignored" - ) - return - - EventType = ((self.event_type is not inspect.Parameter.empty) and - inspect.isclass(self.event_type) and self.event_type) - 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 self.signature.parameters.get(k, None) is not None - }) - - @property - def bot_type(self) -> Union[Type["Bot"], inspect.Parameter.empty]: - """ - :类型: ``Union[Type["Bot"], inspect.Parameter.empty]`` - :说明: 事件处理函数接受的 Bot 对象类型""" - return self.signature.parameters["bot"].annotation - - @property - def event_type( - self) -> Optional[Union[Type["Event"], inspect.Parameter.empty]]: - """ - :类型: ``Optional[Union[Type[Event], inspect.Parameter.empty]]`` - :说明: 事件处理函数接受的 event 类型 / 不需要 event 参数 - """ - if "event" not in self.signature.parameters: - return None - return self.signature.parameters["event"].annotation - - @property - def state_type(self) -> Optional[Union[T_State, inspect.Parameter.empty]]: - """ - :类型: ``Optional[Union[T_State, inspect.Parameter.empty]]`` - :说明: 事件处理函数是否接受 state 参数 - """ - if "state" not in self.signature.parameters: - return None - return self.signature.parameters["state"].annotation - - @property - def matcher_type( - self) -> Optional[Union[Type["Matcher"], inspect.Parameter.empty]]: - """ - :类型: ``Optional[Union[Type["Matcher"], inspect.Parameter.empty]]`` - :说明: 事件处理函数是否接受 matcher 参数 - """ - if "matcher" not in self.signature.parameters: - 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"].replace( - annotation=wrapped_signature.parameters["bot"].annotation) - if "event" in wrapped_signature.parameters and "event" in signature.parameters: - signature.parameters["event"].replace( - annotation=wrapped_signature.parameters["event"].annotation) - return signature - - def update_signature( - self, **kwargs: Union[None, Type["Bot"], Type["Event"], Type["Matcher"], - T_State, inspect.Parameter.empty] - ) -> 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.func, "__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'") + self.name = get_name(func) if name is None else name diff --git a/nonebot/typing.py b/nonebot/typing.py index 273661eb..970fbcc7 100644 --- a/nonebot/typing.py +++ b/nonebot/typing.py @@ -143,20 +143,11 @@ T_PermissionChecker = Callable[["Bot", "Event"], Union[bool, Awaitable[bool]]] RuleChecker 即判断是否响应消息的处理函数。 """ -T_Handler = Union[Callable[[Any, Any, Any, Any], Union[Awaitable[None], - Awaitable[NoReturn]]], - Callable[[Any, Any, Any], Union[Awaitable[None], - Awaitable[NoReturn]]], - Callable[[Any, Any], Union[Awaitable[None], - Awaitable[NoReturn]]], - Callable[[Any], Union[Awaitable[None], Awaitable[NoReturn]]]] +T_Handler = Callable[..., Union[Awaitable[None], Awaitable[NoReturn]]] """ :类型: - * ``Callable[[Bot, Event, T_State], Union[Awaitable[None], Awaitable[NoReturn]]]`` - * ``Callable[[Bot, Event], Union[Awaitable[None], Awaitable[NoReturn]]]`` - * ``Callable[[Bot, T_State], Union[Awaitable[None], Awaitable[NoReturn]]]`` - * ``Callable[[Bot], Union[Awaitable[None], Awaitable[NoReturn]]]`` + * ``Callable[..., Union[Awaitable[None], Awaitable[NoReturn]]]`` :说明: diff --git a/nonebot/utils.py b/nonebot/utils.py index 8183986d..71df0d4b 100644 --- a/nonebot/utils.py +++ b/nonebot/utils.py @@ -1,6 +1,7 @@ import re import json import asyncio +import inspect import dataclasses from functools import wraps, partial from typing import Any, Callable, Optional, Awaitable @@ -51,6 +52,12 @@ def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: return _wrapper +def get_name(obj: Any) -> str: + if inspect.isfunction(obj) or inspect.isclass(obj): + return obj.__name__ + return obj.__class__.__name__ + + class DataclassEncoder(json.JSONEncoder): """ :说明: From f7eadb48b56e8424fff782251f633e989f54e688 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Fri, 12 Nov 2021 20:55:59 +0800 Subject: [PATCH 02/21] :construction: create processor --- nonebot/dependencies/__init__.py | 2 - nonebot/dependencies/models.py | 89 ----------------- nonebot/dependencies/utils.py | 150 ----------------------------- nonebot/handler.py | 27 ------ nonebot/processor/__init__.py | 77 +++++++++++++++ nonebot/processor/handler.py | 43 +++++++++ nonebot/{ => processor}/matcher.py | 2 +- nonebot/processor/models.py | 41 ++++++++ nonebot/processor/utils.py | 43 +++++++++ 9 files changed, 205 insertions(+), 269 deletions(-) delete mode 100644 nonebot/dependencies/__init__.py delete mode 100644 nonebot/dependencies/models.py delete mode 100644 nonebot/dependencies/utils.py delete mode 100644 nonebot/handler.py create mode 100644 nonebot/processor/__init__.py create mode 100644 nonebot/processor/handler.py rename nonebot/{ => processor}/matcher.py (99%) create mode 100644 nonebot/processor/models.py create mode 100644 nonebot/processor/utils.py diff --git a/nonebot/dependencies/__init__.py b/nonebot/dependencies/__init__.py deleted file mode 100644 index 95e6a040..00000000 --- a/nonebot/dependencies/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .models import Depends as Depends -from .utils import get_dependent as get_dependent diff --git a/nonebot/dependencies/models.py b/nonebot/dependencies/models.py deleted file mode 100644 index 4232ccec..00000000 --- a/nonebot/dependencies/models.py +++ /dev/null @@ -1,89 +0,0 @@ -from enum import Enum -from typing import Any, List, Callable, Optional - -from pydantic.fields import Required, FieldInfo, ModelField - -from nonebot.utils import get_name - - -class Depends: - - def __init__(self, - dependency: Optional[Callable[..., Any]] = None, - *, - use_cache: bool = True) -> None: - self.dependency = dependency - self.use_cache = use_cache - - def __repr__(self) -> str: - dep = get_name(self.dependency) - cache = "" if self.use_cache else ", use_cache=False" - return f"{self.__class__.__name__}({dep}{cache})" - - -class Dependent: - - def __init__(self, - *, - func: Optional[Callable[..., Any]] = None, - name: Optional[str] = None, - bot_param: Optional[ModelField] = None, - event_param: Optional[ModelField] = None, - state_param: Optional[ModelField] = None, - matcher_param: Optional[ModelField] = None, - simple_params: Optional[List[ModelField]] = None, - dependencies: Optional[List["Dependent"]] = None, - use_cache: bool = True) -> None: - self.func = func - self.name = name - self.bot_param = bot_param - self.event_param = event_param - self.state_param = state_param - self.matcher_param = matcher_param - self.simple_params = simple_params or [] - self.dependencies = dependencies or [] - self.use_cache = use_cache - self.cache_key = (self.func,) - - -class ParamTypes(Enum): - BOT = "bot" - EVENT = "event" - STATE = "state" - MATCHER = "matcher" - SIMPLE = "simple" - - -class Param(FieldInfo): - in_: ParamTypes - - def __init__(self, default: Any): - super().__init__(default=default) - - def __repr__(self) -> str: - return f"{self.__class__.__name__}" - - -class BotParam(Param): - in_ = ParamTypes.BOT - - -class EventParam(Param): - in_ = ParamTypes.EVENT - - -class StateParam(Param): - in_ = ParamTypes.STATE - - -class MatcherParam(Param): - in_ = ParamTypes.MATCHER - - -class SimpleParam(Param): - in_ = ParamTypes.SIMPLE - - def __init__(self, default: Any): - if default is Required: - raise ValueError("SimpleParam should be given a default value") - super().__init__(default) diff --git a/nonebot/dependencies/utils.py b/nonebot/dependencies/utils.py deleted file mode 100644 index 9503fbf1..00000000 --- a/nonebot/dependencies/utils.py +++ /dev/null @@ -1,150 +0,0 @@ -import inspect -from typing import Any, Dict, Type, Union, Callable, Optional, ForwardRef - -from pydantic import BaseConfig -from pydantic.class_validators import Validator -from pydantic.typing import evaluate_forwardref -from pydantic.schema import get_annotation_from_field_info -from pydantic.fields import Required, FieldInfo, ModelField, UndefinedType - -from .models import Param, Depends, Dependent, ParamTypes, SimpleParam - - -def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: - signature = inspect.signature(call) - globalns = getattr(call, "__globals__", {}) - typed_params = [ - inspect.Parameter( - name=param.name, - kind=param.kind, - default=param.default, - annotation=get_typed_annotation(param, globalns), - ) for param in signature.parameters.values() - ] - typed_signature = inspect.Signature(typed_params) - return typed_signature - - -def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, - Any]) -> Any: - annotation = param.annotation - if isinstance(annotation, str): - annotation = ForwardRef(annotation) - annotation = evaluate_forwardref(annotation, globalns, globalns) - return annotation - - -def get_param_sub_dependent(*, param: inspect.Parameter) -> Dependent: - depends: Depends = param.default - if depends.dependency: - dependency = depends.dependency - else: - dependency = param.annotation - return get_sub_dependant( - depends=depends, - dependency=dependency, - name=param.name, - ) - - -def get_parameterless_sub_dependant(*, depends: Depends) -> Dependent: - assert callable( - depends.dependency - ), "A parameter-less dependency must have a callable dependency" - return get_sub_dependant(depends=depends, dependency=depends.dependency) - - -def get_sub_dependant( - *, - depends: Depends, - dependency: Callable[..., Any], - name: Optional[str] = None, -) -> Dependent: - sub_dependant = get_dependent( - func=dependency, - name=name, - use_cache=depends.use_cache, - ) - return sub_dependant - - -def get_dependent(*, - func: Callable[..., Any], - name: Optional[str] = None, - use_cache: bool = True) -> Dependent: - signature = get_typed_signature(func) - params = signature.parameters - dependent = Dependent(func=func, name=name, use_cache=use_cache) - for param_name, param in params.items(): - if isinstance(param.default, Depends): - sub_dependent = get_param_sub_dependent(param=param) - dependent.dependencies.append(sub_dependent) - continue - param_field = get_param_field(param=param, - param_name=param_name, - default_field_info=SimpleParam) - - return dependent - - -def get_param_field(*, - param: inspect.Parameter, - param_name: str, - default_field_info: Type[Param] = Param, - force_type: Optional[ParamTypes] = None, - ignore_default: bool = False) -> ModelField: - default_value = Required - if param.default != param.empty and not ignore_default: - default_value = param.default - if isinstance(default_value, FieldInfo): - field_info = default_value - default_value = field_info.default - if (isinstance(field_info, Param) and - getattr(field_info, "in_", None) is None): - field_info.in_ = default_field_info.in_ - if force_type: - field_info.in_ = force_type # type: ignore - else: - field_info = default_field_info(default_value) - required: bool = default_value == Required - annotation: Any = Any - if param.annotation != param.empty: - annotation = param.annotation - annotation = get_annotation_from_field_info(annotation, field_info, - param_name) - if not field_info.alias and getattr(field_info, "convert_underscores", - None): - alias = param.name.replace("_", "-") - else: - alias = field_info.alias or param.name - field = create_field( - name=param.name, - type_=annotation, - default=None if required else default_value, - alias=alias, - required=required, - field_info=field_info, - ) - # field.required = required - - return field - - -def create_field(name: str, - type_: Type[Any], - class_validators: Optional[Dict[str, Validator]] = None, - default: Optional[Any] = None, - required: Union[bool, UndefinedType] = False, - model_config: Type[BaseConfig] = BaseConfig, - field_info: Optional[FieldInfo] = None, - alias: Optional[str] = None) -> ModelField: - class_validators = class_validators or {} - field_info = field_info or FieldInfo(None) - return ModelField(name=name, - type_=type_, - class_validators=class_validators, - model_config=model_config, - default=default, - required=required, - alias=alias, - field_info=field_info) diff --git a/nonebot/handler.py b/nonebot/handler.py deleted file mode 100644 index ee31d4cf..00000000 --- a/nonebot/handler.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -事件处理函数 -============ - -该模块实现事件处理函数的封装,以实现动态参数等功能。 -""" - -import inspect -from typing import Optional - -from pydantic.typing import evaluate_forwardref - -from nonebot.utils import get_name -from nonebot.typing import T_Handler - - -class Handler: - """事件处理函数类""" - - def __init__(self, func: T_Handler, *, name: Optional[str] = None): - """装饰事件处理函数以便根据动态参数运行""" - self.func: T_Handler = func - """ - :类型: ``T_Handler`` - :说明: 事件处理函数 - """ - self.name = get_name(func) if name is None else name diff --git a/nonebot/processor/__init__.py b/nonebot/processor/__init__.py new file mode 100644 index 00000000..73b86bbc --- /dev/null +++ b/nonebot/processor/__init__.py @@ -0,0 +1,77 @@ +import inspect +from typing import Any, Callable, Optional + +from .models import Dependent +from .models import Depends as Depends +from nonebot.adapters import Bot, Event +from .utils import get_typed_signature, generic_check_issubclass + + +def get_param_sub_dependent(*, param: inspect.Parameter) -> Dependent: + depends: Depends = param.default + if depends.dependency: + dependency = depends.dependency + else: + dependency = param.annotation + return get_sub_dependant( + depends=depends, + dependency=dependency, + name=param.name, + ) + + +def get_parameterless_sub_dependant(*, depends: Depends) -> Dependent: + assert callable( + depends.dependency + ), "A parameter-less dependency must have a callable dependency" + return get_sub_dependant(depends=depends, dependency=depends.dependency) + + +def get_sub_dependant( + *, + depends: Depends, + dependency: Callable[..., Any], + name: Optional[str] = None, +) -> Dependent: + sub_dependant = get_dependent( + func=dependency, + name=name, + use_cache=depends.use_cache, + ) + return sub_dependant + + +def get_dependent(*, + func: Callable[..., Any], + name: Optional[str] = None, + use_cache: bool = True) -> Dependent: + signature = get_typed_signature(func) + params = signature.parameters + dependent = Dependent(func=func, name=name, use_cache=use_cache) + for param_name, param in params.items(): + if isinstance(param.default, Depends): + sub_dependent = get_param_sub_dependent(param=param) + dependent.dependencies.append(sub_dependent) + continue + + if generic_check_issubclass(param.annotation, Bot): + dependent.bot_param_name = param_name + continue + elif generic_check_issubclass(param.annotation, Event): + dependent.event_param_name = param_name + continue + elif generic_check_issubclass(param.annotation, dict): + dependent.state_param_name = param_name + continue + elif generic_check_issubclass(param.annotation, Matcher): + dependent.matcher_param_name = param_name + continue + + raise ValueError( + f"Unknown parameter {param_name} with type {param.annotation}") + + return dependent + + +from .handler import Handler as Handler +from .matcher import Matcher as Matcher diff --git a/nonebot/processor/handler.py b/nonebot/processor/handler.py new file mode 100644 index 00000000..66366cb7 --- /dev/null +++ b/nonebot/processor/handler.py @@ -0,0 +1,43 @@ +""" +事件处理函数 +============ + +该模块实现事件处理函数的封装,以实现动态参数等功能。 +""" +from typing import TYPE_CHECKING, List, Optional + +from .models import Depends +from nonebot.utils import get_name +from nonebot.typing import T_State, T_Handler +from . import get_dependent, get_parameterless_sub_dependant + +if TYPE_CHECKING: + from .matcher import Matcher + from nonebot.adapters import Bot, Event + + +class Handler: + """事件处理函数类""" + + def __init__(self, + func: T_Handler, + *, + name: Optional[str] = None, + dependencies: Optional[List[Depends]] = None): + """装饰事件处理函数以便根据动态参数运行""" + self.func: T_Handler = func + """ + :类型: ``T_Handler`` + :说明: 事件处理函数 + """ + self.name = get_name(func) if name is None else name + + self.dependencies = dependencies or [] + self.dependent = get_dependent(func=func) + for depends in self.dependencies[::-1]: + self.dependent.dependencies.insert( + 0, get_parameterless_sub_dependant(depends=depends)) + + def __call__(self, bot: Bot, event: Event, state: T_State, + matcher: "Matcher"): + ... diff --git a/nonebot/matcher.py b/nonebot/processor/matcher.py similarity index 99% rename from nonebot/matcher.py rename to nonebot/processor/matcher.py index b8a874f2..256c787d 100644 --- a/nonebot/matcher.py +++ b/nonebot/processor/matcher.py @@ -13,9 +13,9 @@ from collections import defaultdict from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Callable, NoReturn, Optional) +from .handler import Handler from nonebot.rule import Rule from nonebot.log import logger -from nonebot.handler import Handler from nonebot.adapters import MessageTemplate from nonebot.permission import USER, Permission from nonebot.exception import (PausedException, StopPropagation, diff --git a/nonebot/processor/models.py b/nonebot/processor/models.py new file mode 100644 index 00000000..14af1699 --- /dev/null +++ b/nonebot/processor/models.py @@ -0,0 +1,41 @@ +from typing import Any, List, Callable, Optional + +from nonebot.utils import get_name + + +class Depends: + + def __init__(self, + dependency: Optional[Callable[..., Any]] = None, + *, + use_cache: bool = True) -> None: + self.dependency = dependency + self.use_cache = use_cache + + def __repr__(self) -> str: + dep = get_name(self.dependency) + cache = "" if self.use_cache else ", use_cache=False" + return f"{self.__class__.__name__}({dep}{cache})" + + +class Dependent: + + def __init__(self, + *, + func: Optional[Callable[..., Any]] = None, + name: Optional[str] = None, + bot_param_name: Optional[str] = None, + event_param_name: Optional[str] = None, + state_param_name: Optional[str] = None, + matcher_param_name: Optional[str] = None, + dependencies: Optional[List["Dependent"]] = None, + use_cache: bool = True) -> None: + self.func = func + self.name = name + self.bot_param_name = bot_param_name + self.event_param_name = event_param_name + self.state_param_name = state_param_name + self.matcher_param_name = matcher_param_name + self.dependencies = dependencies or [] + self.use_cache = use_cache + self.cache_key = (self.func,) diff --git a/nonebot/processor/utils.py b/nonebot/processor/utils.py new file mode 100644 index 00000000..7c3729da --- /dev/null +++ b/nonebot/processor/utils.py @@ -0,0 +1,43 @@ +import inspect +from typing import Any, Dict, Type, Tuple, Union, Callable + +from pydantic.typing import (ForwardRef, GenericAlias, get_args, get_origin, + evaluate_forwardref) + + +def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: + signature = inspect.signature(call) + globalns = getattr(call, "__globals__", {}) + typed_params = [ + inspect.Parameter( + name=param.name, + kind=param.kind, + default=param.default, + annotation=get_typed_annotation(param, globalns), + ) for param in signature.parameters.values() + ] + typed_signature = inspect.Signature(typed_params) + return typed_signature + + +def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, + Any]) -> Any: + annotation = param.annotation + if isinstance(annotation, str): + annotation = ForwardRef(annotation) + annotation = evaluate_forwardref(annotation, globalns, globalns) + return annotation + + +def generic_check_issubclass( + cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], + ...]]) -> bool: + try: + return isinstance(cls, type) and issubclass(cls, class_or_tuple) + except TypeError: + if get_origin(cls) is Union: + for type_ in get_args(cls): + if not generic_check_issubclass(type_, class_or_tuple): + return False + return True + raise From 9d708a6723752a66269ebde93f5960ff62052b6c Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sat, 13 Nov 2021 19:38:01 +0800 Subject: [PATCH 03/21] :construction: process handler call --- nonebot/message.py | 2 +- nonebot/plugin/on.py | 3 +- nonebot/plugin/on.pyi | 3 +- nonebot/plugin/plugin.py | 2 +- nonebot/plugins/single_session.py | 2 +- nonebot/processor/__init__.py | 127 ++++++++++++++++++++++++--- nonebot/processor/handler.py | 71 ++++++++++++--- nonebot/processor/matcher.py | 118 ++++++++++++------------- nonebot/processor/models.py | 9 +- nonebot/processor/utils.py | 8 +- nonebot/typing.py | 2 +- nonebot/utils.py | 23 +++-- poetry.lock | 2 +- pyproject.toml | 1 + tests/test_plugins/test_processor.py | 4 +- 15 files changed, 270 insertions(+), 107 deletions(-) diff --git a/nonebot/message.py b/nonebot/message.py index b853e481..f03a7c6d 100644 --- a/nonebot/message.py +++ b/nonebot/message.py @@ -12,7 +12,7 @@ from typing import TYPE_CHECKING, Set, Type, Optional from nonebot.log import logger from nonebot.rule import TrieRule from nonebot.utils import escape_tag -from nonebot.matcher import Matcher, matchers +from nonebot.processor import Matcher, matchers from nonebot.exception import NoLogException, StopPropagation, IgnoredException from nonebot.typing import (T_State, T_RunPreProcessor, T_RunPostProcessor, T_EventPreProcessor, T_EventPostProcessor) diff --git a/nonebot/plugin/on.py b/nonebot/plugin/on.py index e4da8345..46a5ecfe 100644 --- a/nonebot/plugin/on.py +++ b/nonebot/plugin/on.py @@ -5,10 +5,9 @@ from types import ModuleType from typing import (TYPE_CHECKING, Any, Set, Dict, List, Type, Tuple, Union, Optional) -from nonebot.handler import Handler -from nonebot.matcher import Matcher from .manager import _current_plugin from nonebot.permission import Permission +from nonebot.processor import Handler, Matcher from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_StateFactory from nonebot.rule import (Rule, ArgumentParser, regex, command, keyword, endswith, startswith, shell_command) diff --git a/nonebot/plugin/on.pyi b/nonebot/plugin/on.pyi index 68e8ad62..fbbf9c90 100644 --- a/nonebot/plugin/on.pyi +++ b/nonebot/plugin/on.pyi @@ -1,10 +1,9 @@ import re from typing import Set, List, Type, Tuple, Union, Optional -from nonebot.handler import Handler -from nonebot.matcher import Matcher from nonebot.permission import Permission from nonebot.rule import Rule, ArgumentParser +from nonebot.processor import Handler, Matcher from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_StateFactory diff --git a/nonebot/plugin/plugin.py b/nonebot/plugin/plugin.py index ee5a7c51..bf7773ac 100644 --- a/nonebot/plugin/plugin.py +++ b/nonebot/plugin/plugin.py @@ -3,7 +3,7 @@ from dataclasses import field, dataclass from typing import Set, Dict, Type, Optional from .export import Export -from nonebot.matcher import Matcher +from nonebot.processor import Matcher plugins: Dict[str, "Plugin"] = {} """ diff --git a/nonebot/plugins/single_session.py b/nonebot/plugins/single_session.py index 8a8b3cfb..1b6d4116 100644 --- a/nonebot/plugins/single_session.py +++ b/nonebot/plugins/single_session.py @@ -1,7 +1,7 @@ from typing import Dict, Optional from nonebot.typing import T_State -from nonebot.matcher import Matcher +from nonebot.processor import Matcher from nonebot.adapters import Bot, Event from nonebot.message import (IgnoredException, run_preprocessor, run_postprocessor) diff --git a/nonebot/processor/__init__.py b/nonebot/processor/__init__.py index 73b86bbc..e4f0a09c 100644 --- a/nonebot/processor/__init__.py +++ b/nonebot/processor/__init__.py @@ -1,14 +1,18 @@ import inspect -from typing import Any, Callable, Optional +from itertools import chain +from typing import Any, Dict, List, Tuple, Callable, Optional, cast from .models import Dependent -from .models import Depends as Depends +from nonebot.typing import T_State from nonebot.adapters import Bot, Event -from .utils import get_typed_signature, generic_check_issubclass +from .models import Depends as DependsClass +from nonebot.utils import run_sync, is_coroutine_callable +from .utils import (generic_get_types, get_typed_signature, + generic_check_issubclass) def get_param_sub_dependent(*, param: inspect.Parameter) -> Dependent: - depends: Depends = param.default + depends: DependsClass = param.default if depends.dependency: dependency = depends.dependency else: @@ -20,7 +24,7 @@ def get_param_sub_dependent(*, param: inspect.Parameter) -> Dependent: ) -def get_parameterless_sub_dependant(*, depends: Depends) -> Dependent: +def get_parameterless_sub_dependant(*, depends: DependsClass) -> Dependent: assert callable( depends.dependency ), "A parameter-less dependency must have a callable dependency" @@ -29,7 +33,7 @@ def get_parameterless_sub_dependant(*, depends: Depends) -> Dependent: def get_sub_dependant( *, - depends: Depends, + depends: DependsClass, dependency: Callable[..., Any], name: Optional[str] = None, ) -> Dependent: @@ -49,29 +53,124 @@ def get_dependent(*, params = signature.parameters dependent = Dependent(func=func, name=name, use_cache=use_cache) for param_name, param in params.items(): - if isinstance(param.default, Depends): + if isinstance(param.default, DependsClass): sub_dependent = get_param_sub_dependent(param=param) dependent.dependencies.append(sub_dependent) continue if generic_check_issubclass(param.annotation, Bot): + if dependent.bot_param_name is not None: + raise ValueError(f"{func} has more than one Bot parameter: " + f"{dependent.bot_param_name} / {param_name}") dependent.bot_param_name = param_name - continue + dependent.bot_param_type = generic_get_types(param.annotation) elif generic_check_issubclass(param.annotation, Event): + if dependent.event_param_name is not None: + raise ValueError(f"{func} has more than one Event parameter: " + f"{dependent.event_param_name} / {param_name}") dependent.event_param_name = param_name - continue + dependent.event_param_type = generic_get_types(param.annotation) elif generic_check_issubclass(param.annotation, dict): + if dependent.state_param_name is not None: + raise ValueError(f"{func} has more than one State parameter: " + f"{dependent.state_param_name} / {param_name}") dependent.state_param_name = param_name - continue elif generic_check_issubclass(param.annotation, Matcher): + if dependent.matcher_param_name is not None: + raise ValueError( + f"{func} has more than one Matcher parameter: " + f"{dependent.matcher_param_name} / {param_name}") dependent.matcher_param_name = param_name - continue - - raise ValueError( - f"Unknown parameter {param_name} with type {param.annotation}") + else: + raise ValueError( + f"Unknown parameter {param_name} with type {param.annotation}") return dependent +async def solve_dependencies( + *, + dependent: Dependent, + bot: Bot, + event: Event, + state: T_State, + matcher: "Matcher", + sub_dependents: Optional[List[Dependent]] = None, + dependency_overrides_provider: Optional[Any] = None, + dependency_cache: Optional[Dict[Tuple[Callable[..., Any]], Any]] = None, +) -> Tuple[Dict[str, Any], Dict[Tuple[Callable[..., Any]], Any]]: + values: Dict[str, Any] = {} + dependency_cache = dependency_cache or {} + + # solve sub dependencies + sub_dependant: Dependent + for sub_dependant in chain(sub_dependents or tuple(), + dependent.dependencies): + sub_dependant.func = cast(Callable[..., Any], sub_dependant.func) + sub_dependant.cache_key = cast(Tuple[Callable[..., Any]], + sub_dependant.cache_key) + func = sub_dependant.func + + # dependency overrides + use_sub_dependant = sub_dependant + if (dependency_overrides_provider and + hasattr(dependency_overrides_provider, "dependency_overrides")): + original_call = sub_dependant.func + func = getattr(dependency_overrides_provider, + "dependency_overrides", + {}).get(original_call, original_call) + use_sub_dependant = get_dependent( + func=func, + name=sub_dependant.name, + ) + + # solve sub dependency with current cache + solved_result = await solve_dependencies( + dependent=use_sub_dependant, + bot=bot, + event=event, + state=state, + matcher=matcher, + dependency_overrides_provider=dependency_overrides_provider, + dependency_cache=dependency_cache, + ) + sub_values, sub_dependency_cache = solved_result + # update cache? + dependency_cache.update(sub_dependency_cache) + + # run dependency function + if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache: + solved = dependency_cache[sub_dependant.cache_key] + elif is_coroutine_callable(func): + solved = await func(**sub_values) + else: + solved = await run_sync(func)(**sub_values) + + # parameter dependency + if sub_dependant.name is not None: + values[sub_dependant.name] = solved + # save current dependency to cache + if sub_dependant.cache_key not in dependency_cache: + dependency_cache[sub_dependant.cache_key] = solved + + # usual dependency + if dependent.bot_param_name is not None: + values[dependent.bot_param_name] = bot + if dependent.event_param_name is not None: + values[dependent.event_param_name] = event + if dependent.state_param_name is not None: + values[dependent.state_param_name] = state + if dependent.matcher_param_name is not None: + values[dependent.matcher_param_name] = matcher + return values, dependency_cache + + +def Depends(dependency: Optional[Callable[..., Any]] = None, + *, + use_cache: bool = True) -> Any: + return DependsClass(dependency=dependency, use_cache=use_cache) + + from .handler import Handler as Handler from .matcher import Matcher as Matcher +from .matcher import matchers as matchers diff --git a/nonebot/processor/handler.py b/nonebot/processor/handler.py index 66366cb7..03e8685b 100644 --- a/nonebot/processor/handler.py +++ b/nonebot/processor/handler.py @@ -4,12 +4,14 @@ 该模块实现事件处理函数的封装,以实现动态参数等功能。 """ -from typing import TYPE_CHECKING, List, Optional -from .models import Depends -from nonebot.utils import get_name +import asyncio +from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Callable, Optional + +from .models import Depends, Dependent +from nonebot.utils import get_name, run_sync from nonebot.typing import T_State, T_Handler -from . import get_dependent, get_parameterless_sub_dependant +from . import get_dependent, solve_dependencies, get_parameterless_sub_dependant if TYPE_CHECKING: from .matcher import Matcher @@ -23,7 +25,8 @@ class Handler: func: T_Handler, *, name: Optional[str] = None, - dependencies: Optional[List[Depends]] = None): + dependencies: Optional[List[Depends]] = None, + dependency_overrides_provider: Optional[Any] = None): """装饰事件处理函数以便根据动态参数运行""" self.func: T_Handler = func """ @@ -33,11 +36,57 @@ class Handler: self.name = get_name(func) if name is None else name self.dependencies = dependencies or [] + self.sub_dependents: Dict[Tuple[Callable[..., Any]], Dependent] = {} + if dependencies: + for depends in dependencies: + if not depends.dependency: + raise ValueError(f"{depends} has no dependency") + if (depends.dependency,) in self.sub_dependents: + raise ValueError(f"{depends} is already in dependencies") + sub_dependant = get_parameterless_sub_dependant(depends=depends) + self.sub_dependents[(depends.dependency,)] = sub_dependant + self.dependency_overrides_provider = dependency_overrides_provider self.dependent = get_dependent(func=func) - for depends in self.dependencies[::-1]: - self.dependent.dependencies.insert( - 0, get_parameterless_sub_dependant(depends=depends)) - def __call__(self, bot: Bot, event: Event, state: T_State, - matcher: "Matcher"): - ... + async def __call__(self, matcher: "Matcher", bot: Bot, event: Event, + state: T_State): + values, _ = await solve_dependencies( + dependent=self.dependent, + bot=bot, + event=event, + state=state, + matcher=matcher, + sub_dependents=[ + self.sub_dependents[(dependency.dependency,)] # type: ignore + for dependency in self.dependencies + ], + dependency_overrides_provider=self.dependency_overrides_provider) + + if asyncio.iscoroutinefunction(self.func): + await self.func(**values) + else: + await run_sync(self.func)(**values) + + def cache_dependent(self, dependency: Depends): + if not dependency.dependency: + raise ValueError(f"{dependency} has no dependency") + if (dependency.dependency,) in self.sub_dependents: + raise ValueError(f"{dependency} is already in dependencies") + sub_dependant = get_parameterless_sub_dependant(depends=dependency) + self.sub_dependents[(dependency.dependency,)] = sub_dependant + + def prepend_dependency(self, dependency: Depends): + self.cache_dependent(dependency) + self.dependencies.insert(0, dependency) + + def append_dependency(self, dependency: Depends): + self.cache_dependent(dependency) + self.dependencies.append(dependency) + + def remove_dependency(self, dependency: Depends): + if not dependency.dependency: + raise ValueError(f"{dependency} has no dependency") + if (dependency.dependency,) in self.sub_dependents: + del self.sub_dependents[(dependency.dependency,)] + if dependency in self.dependencies: + self.dependencies.remove(dependency) diff --git a/nonebot/processor/matcher.py b/nonebot/processor/matcher.py index 256c787d..612b6d36 100644 --- a/nonebot/processor/matcher.py +++ b/nonebot/processor/matcher.py @@ -5,7 +5,6 @@ 该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话 。 """ -from functools import wraps from types import ModuleType from datetime import datetime from contextvars import ContextVar @@ -13,8 +12,10 @@ from collections import defaultdict from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Callable, NoReturn, Optional) +from .models import Depends from .handler import Handler from nonebot.rule import Rule +from nonebot import get_driver from nonebot.log import logger from nonebot.adapters import MessageTemplate from nonebot.permission import USER, Permission @@ -228,8 +229,8 @@ class Matcher(metaclass=MatcherMeta): "permission": permission or Permission(), "handlers": [ - handler - if isinstance(handler, Handler) else Handler(handler) + handler if isinstance(handler, Handler) else Handler( + handler, dependency_overrides_provider=get_driver()) for handler in handlers ] if handlers else [], "temp": @@ -343,8 +344,12 @@ class Matcher(metaclass=MatcherMeta): return func @classmethod - def append_handler(cls, handler: T_Handler) -> Handler: - handler_ = Handler(handler) + def append_handler(cls, + handler: T_Handler, + dependencies: Optional[List[Depends]] = None) -> Handler: + handler_ = Handler(handler, + dependencies=dependencies, + dependency_overrides_provider=get_driver()) cls.handlers.append(handler_) return handler_ @@ -378,22 +383,19 @@ class Matcher(metaclass=MatcherMeta): * 无 """ - async def _receive(bot: "Bot", event: "Event") -> NoReturn: - raise PausedException - - if cls.handlers: - # 已有前置handlers则接受一条新的消息,否则视为接收初始消息 - receive_handler = cls.append_handler(_receive) - else: - receive_handler = None - def _decorator(func: T_Handler) -> T_Handler: - if not cls.handlers or cls.handlers[-1] is not func: - func_handler = cls.append_handler(func) - if receive_handler: - receive_handler.update_signature( - bot=func_handler.bot_type, - event=func_handler.event_type) + + async def _receive() -> NoReturn: + func_handler.remove_dependency(depend) + raise PausedException + + depend = Depends(_receive) + if cls.handlers and cls.handlers[-1].func is func: + func_handler = cls.handlers[-1] + func_handler.prepend_dependency(depend) + else: + func_handler = cls.append_handler( + func, dependencies=[depend] if cls.handlers else []) return func @@ -419,54 +421,42 @@ class Matcher(metaclass=MatcherMeta): * ``args_parser: Optional[T_ArgsParser]``: 可选参数解析函数,空则使用默认解析函数 """ - async def _key_getter(bot: "Bot", event: "Event", state: T_State): - state["_current_key"] = key - if key not in state: - if prompt is not None: - if isinstance(prompt, MessageTemplate): - _prompt = prompt.format(**state) - else: - _prompt = prompt - await bot.send(event=event, message=_prompt) - raise PausedException - else: - state["_skip_key"] = True - - async def _key_parser(bot: "Bot", event: "Event", state: T_State): - if key in state and state.get("_skip_key"): - del state["_skip_key"] - return - parser = args_parser or cls._default_parser - if parser: - # parser = cast(T_ArgsParser["Bot", "Event"], parser) - await parser(bot, event, state) - else: - state[state["_current_key"]] = str(event.get_message()) - - 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].func, "__wrapped__"): - parser = cls.handlers.pop() - func_handler = Handler(func) - @wraps(func) - async def wrapper(bot: "Bot", event: "Event", state: T_State, - matcher: Matcher): - await parser(matcher, bot, event, state) - await func_handler(matcher, bot, event, state) - if "_current_key" in state: - del state["_current_key"] + async def _key_getter(bot: "Bot", event: "Event", state: T_State): + func_handler.remove_dependency(get_depend) + state["_current_key"] = key + if key not in state: + if prompt is not None: + if isinstance(prompt, MessageTemplate): + _prompt = prompt.format(**state) + else: + _prompt = prompt + await bot.send(event=event, message=_prompt) + raise PausedException + else: + state["_skip_key"] = True - wrapper_handler = cls.append_handler(wrapper) + async def _key_parser(bot: "Bot", event: "Event", state: T_State): + if key in state and state.get("_skip_key"): + del state["_skip_key"] + return + parser = args_parser or cls._default_parser + if parser: + await parser(bot, event, state) + else: + state[state["_current_key"]] = str(event.get_message()) - getter_handler.update_signature( - bot=wrapper_handler.bot_type, - event=wrapper_handler.event_type) - parser_handler.update_signature( - bot=wrapper_handler.bot_type, - event=wrapper_handler.event_type) + get_depend = Depends(_key_getter) + parser_depend = Depends(_key_parser) + + if cls.handlers and cls.handlers[-1].func is func: + func_handler = cls.handlers[-1] + func_handler.prepend_dependency(parser_depend) + func_handler.prepend_dependency(get_depend) + else: + func_handler = cls.append_handler( + func, dependencies=[get_depend, parser_depend]) return func diff --git a/nonebot/processor/models.py b/nonebot/processor/models.py index 14af1699..996c3be7 100644 --- a/nonebot/processor/models.py +++ b/nonebot/processor/models.py @@ -1,7 +1,10 @@ -from typing import Any, List, Callable, Optional +from typing import TYPE_CHECKING, Any, List, Tuple, Callable, Optional from nonebot.utils import get_name +if TYPE_CHECKING: + from nonebot.adapters import Bot, Event + class Depends: @@ -25,7 +28,9 @@ class Dependent: func: Optional[Callable[..., Any]] = None, name: Optional[str] = None, bot_param_name: Optional[str] = None, + bot_param_type: Optional[Tuple["Bot", ...]] = None, event_param_name: Optional[str] = None, + event_param_type: Optional[Tuple["Event", ...]] = None, state_param_name: Optional[str] = None, matcher_param_name: Optional[str] = None, dependencies: Optional[List["Dependent"]] = None, @@ -33,7 +38,9 @@ class Dependent: self.func = func self.name = name self.bot_param_name = bot_param_name + self.bot_param_type = bot_param_type self.event_param_name = event_param_name + self.event_param_type = event_param_type self.state_param_name = state_param_name self.matcher_param_name = matcher_param_name self.dependencies = dependencies or [] diff --git a/nonebot/processor/utils.py b/nonebot/processor/utils.py index 7c3729da..bdae3aea 100644 --- a/nonebot/processor/utils.py +++ b/nonebot/processor/utils.py @@ -1,7 +1,7 @@ import inspect from typing import Any, Dict, Type, Tuple, Union, Callable -from pydantic.typing import (ForwardRef, GenericAlias, get_args, get_origin, +from pydantic.typing import (ForwardRef, get_args, get_origin, evaluate_forwardref) @@ -41,3 +41,9 @@ def generic_check_issubclass( return False return True raise + + +def generic_get_types(cls: Any) -> Tuple[Type[Any], ...]: + if get_origin(cls) is Union: + return get_args(cls) + return (cls,) diff --git a/nonebot/typing.py b/nonebot/typing.py index 970fbcc7..053fcff7 100644 --- a/nonebot/typing.py +++ b/nonebot/typing.py @@ -22,7 +22,7 @@ from typing import (TYPE_CHECKING, Any, Dict, Union, TypeVar, Callable, NoReturn, Optional, Awaitable) if TYPE_CHECKING: - from nonebot.matcher import Matcher + from nonebot.processor import Matcher from nonebot.adapters import Bot, Event from nonebot.permission import Permission diff --git a/nonebot/utils.py b/nonebot/utils.py index 71df0d4b..f7d78457 100644 --- a/nonebot/utils.py +++ b/nonebot/utils.py @@ -4,11 +4,15 @@ import asyncio import inspect import dataclasses from functools import wraps, partial -from typing import Any, Callable, Optional, Awaitable +from typing_extensions import ParamSpec +from typing import Any, TypeVar, Callable, Optional, Awaitable from nonebot.log import logger from nonebot.typing import overrides +P = ParamSpec("P") +R = TypeVar("R") + def escape_tag(s: str) -> str: """ @@ -27,7 +31,16 @@ def escape_tag(s: str) -> str: return re.sub(r"\s]*)>", r"\\\g<0>", s) -def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: +def is_coroutine_callable(func: Callable[..., Any]) -> bool: + if inspect.isroutine(func): + return inspect.iscoroutinefunction(func) + if inspect.isclass(func): + return False + func_ = getattr(func, "__call__", None) + return inspect.iscoroutinefunction(func_) + + +def run_sync(func: Callable[P, R]) -> Callable[P, Awaitable[R]]: """ :说明: @@ -35,15 +48,15 @@ def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: :参数: - * ``func: Callable[..., Any]``: 被装饰的同步函数 + * ``func: Callable[P, R]``: 被装饰的同步函数 :返回: - - ``Callable[..., Awaitable[Any]]`` + - ``Callable[P, Awaitable[R]]`` """ @wraps(func) - async def _wrapper(*args: Any, **kwargs: Any) -> Any: + async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R: loop = asyncio.get_running_loop() pfunc = partial(func, *args, **kwargs) result = await loop.run_in_executor(None, pfunc) diff --git a/poetry.lock b/poetry.lock index 13e3204d..91c140fc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1099,7 +1099,7 @@ quart = ["Quart"] [metadata] lock-version = "1.1" python-versions = "^3.7.3" -content-hash = "51f4f0ce5ced234a65cae790c4f57486e42d7120972657a3f51e733cb4e7c639" +content-hash = "81edd95f4289e55d7cfe632664c930846bde723cb8fa0359fa1e18474853f454" [metadata.files] aiocache = [ diff --git a/pyproject.toml b/pyproject.toml index 6d9a8330..4153931a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ pygtrie = "^2.4.1" tomlkit = "^0.7.0" fastapi = "^0.70.0" websockets = ">=9.1" +typing-extensions = "^3.10.0" Quart = { version = "^0.15.0", optional = true } httpx = { version = ">=0.20.0, <1.0.0", extras = ["http2"] } pydantic = { version = "~1.8.0", extras = ["dotenv"] } diff --git a/tests/test_plugins/test_processor.py b/tests/test_plugins/test_processor.py index b5aaa298..5eb6b70e 100644 --- a/tests/test_plugins/test_processor.py +++ b/tests/test_plugins/test_processor.py @@ -1,7 +1,7 @@ from nonebot.typing import T_State -from nonebot.matcher import Matcher +from nonebot.processor import Matcher from nonebot.adapters import Bot, Event -from nonebot.message import event_preprocessor, run_preprocessor +from nonebot.message import run_preprocessor, event_preprocessor @event_preprocessor From 7495fee2a22183a54f9b03044827976877515303 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 14 Nov 2021 01:34:25 +0800 Subject: [PATCH 04/21] :sparkles: add bot event type check --- nonebot/processor/__init__.py | 46 +++++++++++++++++++++-------------- nonebot/processor/handler.py | 13 +++++++++- nonebot/processor/models.py | 6 ++--- 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/nonebot/processor/__init__.py b/nonebot/processor/__init__.py index e4f0a09c..1ef0def8 100644 --- a/nonebot/processor/__init__.py +++ b/nonebot/processor/__init__.py @@ -98,30 +98,38 @@ async def solve_dependencies( sub_dependents: Optional[List[Dependent]] = None, dependency_overrides_provider: Optional[Any] = None, dependency_cache: Optional[Dict[Tuple[Callable[..., Any]], Any]] = None, -) -> Tuple[Dict[str, Any], Dict[Tuple[Callable[..., Any]], Any]]: +) -> Tuple[Dict[str, Any], Dict[Tuple[Callable[..., Any]], Any], bool]: values: Dict[str, Any] = {} dependency_cache = dependency_cache or {} # solve sub dependencies - sub_dependant: Dependent - for sub_dependant in chain(sub_dependents or tuple(), + sub_dependent: Dependent + for sub_dependent in chain(sub_dependents or tuple(), dependent.dependencies): - sub_dependant.func = cast(Callable[..., Any], sub_dependant.func) - sub_dependant.cache_key = cast(Tuple[Callable[..., Any]], - sub_dependant.cache_key) - func = sub_dependant.func + sub_dependent.func = cast(Callable[..., Any], sub_dependent.func) + sub_dependent.cache_key = cast(Tuple[Callable[..., Any]], + sub_dependent.cache_key) + func = sub_dependent.func + + # check bot and event type + if sub_dependent.bot_param_type and not isinstance( + bot, sub_dependent.bot_param_type): + return values, dependency_cache, True + elif sub_dependent.event_param_type and not isinstance( + event, sub_dependent.event_param_type): + return values, dependency_cache, True # dependency overrides - use_sub_dependant = sub_dependant + use_sub_dependant = sub_dependent if (dependency_overrides_provider and hasattr(dependency_overrides_provider, "dependency_overrides")): - original_call = sub_dependant.func + original_call = sub_dependent.func func = getattr(dependency_overrides_provider, "dependency_overrides", {}).get(original_call, original_call) use_sub_dependant = get_dependent( func=func, - name=sub_dependant.name, + name=sub_dependent.name, ) # solve sub dependency with current cache @@ -134,24 +142,26 @@ async def solve_dependencies( dependency_overrides_provider=dependency_overrides_provider, dependency_cache=dependency_cache, ) - sub_values, sub_dependency_cache = solved_result + sub_values, sub_dependency_cache, ignored = solved_result + if ignored: + return values, dependency_cache, True # update cache? dependency_cache.update(sub_dependency_cache) # run dependency function - if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache: - solved = dependency_cache[sub_dependant.cache_key] + if sub_dependent.use_cache and sub_dependent.cache_key in dependency_cache: + solved = dependency_cache[sub_dependent.cache_key] elif is_coroutine_callable(func): solved = await func(**sub_values) else: solved = await run_sync(func)(**sub_values) # parameter dependency - if sub_dependant.name is not None: - values[sub_dependant.name] = solved + if sub_dependent.name is not None: + values[sub_dependent.name] = solved # save current dependency to cache - if sub_dependant.cache_key not in dependency_cache: - dependency_cache[sub_dependant.cache_key] = solved + if sub_dependent.cache_key not in dependency_cache: + dependency_cache[sub_dependent.cache_key] = solved # usual dependency if dependent.bot_param_name is not None: @@ -162,7 +172,7 @@ async def solve_dependencies( values[dependent.state_param_name] = state if dependent.matcher_param_name is not None: values[dependent.matcher_param_name] = matcher - return values, dependency_cache + return values, dependency_cache, False def Depends(dependency: Optional[Callable[..., Any]] = None, diff --git a/nonebot/processor/handler.py b/nonebot/processor/handler.py index 03e8685b..f21a9b37 100644 --- a/nonebot/processor/handler.py +++ b/nonebot/processor/handler.py @@ -50,7 +50,7 @@ class Handler: async def __call__(self, matcher: "Matcher", bot: Bot, event: Event, state: T_State): - values, _ = await solve_dependencies( + values, _, ignored = await solve_dependencies( dependent=self.dependent, bot=bot, event=event, @@ -62,6 +62,17 @@ class Handler: ], dependency_overrides_provider=self.dependency_overrides_provider) + if ignored: + return + + # check bot and event type + if self.dependent.bot_param_type and not isinstance( + bot, self.dependent.bot_param_type): + return + elif self.dependent.event_param_type and not isinstance( + event, self.dependent.event_param_type): + return + if asyncio.iscoroutinefunction(self.func): await self.func(**values) else: diff --git a/nonebot/processor/models.py b/nonebot/processor/models.py index 996c3be7..0e3cd7e4 100644 --- a/nonebot/processor/models.py +++ b/nonebot/processor/models.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, List, Tuple, Callable, Optional +from typing import TYPE_CHECKING, Any, List, Type, Tuple, Callable, Optional from nonebot.utils import get_name @@ -28,9 +28,9 @@ class Dependent: func: Optional[Callable[..., Any]] = None, name: Optional[str] = None, bot_param_name: Optional[str] = None, - bot_param_type: Optional[Tuple["Bot", ...]] = None, + bot_param_type: Optional[Tuple[Type["Bot"], ...]] = None, event_param_name: Optional[str] = None, - event_param_type: Optional[Tuple["Event", ...]] = None, + event_param_type: Optional[Tuple[Type["Event"], ...]] = None, state_param_name: Optional[str] = None, matcher_param_name: Optional[str] = None, dependencies: Optional[List["Dependent"]] = None, From 0a1ae75b702d53834a256e45314f565923936fac Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 14 Nov 2021 18:51:23 +0800 Subject: [PATCH 05/21] :sparkles: finish matcher process --- nonebot/permission.py | 25 ++++---- nonebot/plugin/on.py | 11 ++-- nonebot/processor/__init__.py | 9 ++- nonebot/processor/handler.py | 19 +++++- nonebot/processor/matcher.py | 98 ++++++++++++++++-------------- nonebot/processor/utils.py | 18 ++++-- nonebot/rule.py | 32 +++++----- tests/test_plugins/test_depends.py | 17 ++++++ 8 files changed, 139 insertions(+), 90 deletions(-) create mode 100644 tests/test_plugins/test_depends.py diff --git a/nonebot/permission.py b/nonebot/permission.py index 675f546d..7564bb13 100644 --- a/nonebot/permission.py +++ b/nonebot/permission.py @@ -10,14 +10,12 @@ r""" """ import asyncio -from typing import TYPE_CHECKING, Union, Callable, NoReturn, Optional, Awaitable +from typing import Union, Callable, NoReturn, Optional, Awaitable from nonebot.utils import run_sync +from nonebot.adapters import Bot, Event from nonebot.typing import T_PermissionChecker -if TYPE_CHECKING: - from nonebot.adapters import Bot, Event - class Permission: """ @@ -36,9 +34,8 @@ class Permission: """ __slots__ = ("checkers",) - def __init__( - self, *checkers: Callable[["Bot", "Event"], - Awaitable[bool]]) -> None: + def __init__(self, *checkers: Callable[[Bot, Event], + Awaitable[bool]]) -> None: """ :参数: @@ -55,7 +52,7 @@ class Permission: * ``Set[Callable[[Bot, Event], Awaitable[bool]]]`` """ - async def __call__(self, bot: "Bot", event: "Event") -> bool: + async def __call__(self, bot: Bot, event: Event) -> bool: """ :说明: @@ -94,19 +91,19 @@ class Permission: return Permission(*checkers) -async def _message(bot: "Bot", event: "Event") -> bool: +async def _message(bot: Bot, event: Event) -> bool: return event.get_type() == "message" -async def _notice(bot: "Bot", event: "Event") -> bool: +async def _notice(bot: Bot, event: Event) -> bool: return event.get_type() == "notice" -async def _request(bot: "Bot", event: "Event") -> bool: +async def _request(bot: Bot, event: Event) -> bool: return event.get_type() == "request" -async def _metaevent(bot: "Bot", event: "Event") -> bool: +async def _metaevent(bot: Bot, event: Event) -> bool: return event.get_type() == "meta_event" @@ -140,14 +137,14 @@ def USER(*user: str, perm: Optional[Permission] = None): * ``perm: Optional[Permission]``: 需要同时满足的权限 """ - async def _user(bot: "Bot", event: "Event") -> bool: + async def _user(bot: Bot, event: Event) -> bool: return bool(event.get_session_id() in user and (perm is None or await perm(bot, event))) return Permission(_user) -async def _superuser(bot: "Bot", event: "Event") -> bool: +async def _superuser(bot: Bot, event: Event) -> bool: return (event.get_type() == "message" and event.get_user_id() in bot.config.superusers) diff --git a/nonebot/plugin/on.py b/nonebot/plugin/on.py index 46a5ecfe..40038984 100644 --- a/nonebot/plugin/on.py +++ b/nonebot/plugin/on.py @@ -2,19 +2,16 @@ import re import sys import inspect from types import ModuleType -from typing import (TYPE_CHECKING, Any, Set, Dict, List, Type, Tuple, Union, - Optional) +from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional from .manager import _current_plugin +from nonebot.adapters import Bot, Event from nonebot.permission import Permission from nonebot.processor import Handler, Matcher from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_StateFactory from nonebot.rule import (Rule, ArgumentParser, regex, command, keyword, endswith, startswith, shell_command) -if TYPE_CHECKING: - from nonebot.adapters import Bot, Event - def _store_matcher(matcher: Type[Matcher]) -> None: plugin = _current_plugin.get() @@ -375,7 +372,7 @@ def on_command(cmd: Union[str, Tuple[str, ...]], - ``Type[Matcher]`` """ - async def _strip_cmd(bot: "Bot", event: "Event", state: T_State): + async def _strip_cmd(bot: Bot, event: Event, state: T_State): message = event.get_message() if len(message) < 1: return @@ -432,7 +429,7 @@ def on_shell_command(cmd: Union[str, Tuple[str, ...]], - ``Type[Matcher]`` """ - async def _strip_cmd(bot: "Bot", event: "Event", state: T_State): + async def _strip_cmd(bot: Bot, event: Event, state: T_State): message = event.get_message() segment = message.pop(0) new_message = message.__class__( diff --git a/nonebot/processor/__init__.py b/nonebot/processor/__init__.py index 1ef0def8..70689631 100644 --- a/nonebot/processor/__init__.py +++ b/nonebot/processor/__init__.py @@ -3,6 +3,7 @@ from itertools import chain from typing import Any, Dict, List, Tuple, Callable, Optional, cast from .models import Dependent +from nonebot.log import logger from nonebot.typing import T_State from nonebot.adapters import Bot, Event from .models import Depends as DependsClass @@ -70,7 +71,7 @@ def get_dependent(*, f"{dependent.event_param_name} / {param_name}") dependent.event_param_name = param_name dependent.event_param_type = generic_get_types(param.annotation) - elif generic_check_issubclass(param.annotation, dict): + elif generic_check_issubclass(param.annotation, Dict): if dependent.state_param_name is not None: raise ValueError(f"{func} has more than one State parameter: " f"{dependent.state_param_name} / {param_name}") @@ -114,9 +115,15 @@ async def solve_dependencies( # check bot and event type if sub_dependent.bot_param_type and not isinstance( bot, sub_dependent.bot_param_type): + logger.debug( + f"Matcher {matcher} bot type {type(bot)} not match depends {func} " + f"annotation {sub_dependent.bot_param_type}, ignored") return values, dependency_cache, True elif sub_dependent.event_param_type and not isinstance( event, sub_dependent.event_param_type): + logger.debug( + f"Matcher {matcher} event type {type(event)} not match depends {func} " + f"annotation {sub_dependent.event_param_type}, ignored") return values, dependency_cache, True # dependency overrides diff --git a/nonebot/processor/handler.py b/nonebot/processor/handler.py index f21a9b37..0aafc8c9 100644 --- a/nonebot/processor/handler.py +++ b/nonebot/processor/handler.py @@ -8,6 +8,7 @@ import asyncio from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Callable, Optional +from nonebot.log import logger from .models import Depends, Dependent from nonebot.utils import get_name, run_sync from nonebot.typing import T_State, T_Handler @@ -48,7 +49,18 @@ class Handler: self.dependency_overrides_provider = dependency_overrides_provider self.dependent = get_dependent(func=func) - async def __call__(self, matcher: "Matcher", bot: Bot, event: Event, + def __repr__(self) -> str: + return ( + f"") + + def __str__(self) -> str: + return repr(self) + + async def __call__(self, matcher: "Matcher", bot: "Bot", event: "Event", state: T_State): values, _, ignored = await solve_dependencies( dependent=self.dependent, @@ -68,9 +80,14 @@ class Handler: # check bot and event type if self.dependent.bot_param_type and not isinstance( bot, self.dependent.bot_param_type): + logger.debug(f"Matcher {matcher} bot type {type(bot)} not match " + f"annotation {self.dependent.bot_param_type}, ignored") return elif self.dependent.event_param_type and not isinstance( event, self.dependent.event_param_type): + logger.debug( + f"Matcher {matcher} event type {type(event)} not match " + f"annotation {self.dependent.event_param_type}, ignored") return if asyncio.iscoroutinefunction(self.func): diff --git a/nonebot/processor/matcher.py b/nonebot/processor/matcher.py index 612b6d36..9fbceaa9 100644 --- a/nonebot/processor/matcher.py +++ b/nonebot/processor/matcher.py @@ -17,8 +17,9 @@ from .handler import Handler from nonebot.rule import Rule from nonebot import get_driver from nonebot.log import logger -from nonebot.adapters import MessageTemplate from nonebot.permission import USER, Permission +from nonebot.adapters import (Bot, Event, Message, MessageSegment, + MessageTemplate) from nonebot.exception import (PausedException, StopPropagation, FinishedException, RejectedException) from nonebot.typing import (T_State, T_Handler, T_ArgsParser, T_TypeUpdater, @@ -26,15 +27,14 @@ from nonebot.typing import (T_State, T_Handler, T_ArgsParser, T_TypeUpdater, if TYPE_CHECKING: from nonebot.plugin import Plugin - from nonebot.adapters import Bot, Event, Message, MessageSegment matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list) """ :类型: ``Dict[int, List[Type[Matcher]]]`` :说明: 用于存储当前所有的事件响应器 """ -current_bot: ContextVar["Bot"] = ContextVar("current_bot") -current_event: ContextVar["Event"] = ContextVar("current_event") +current_bot: ContextVar[Bot] = ContextVar("current_bot") +current_event: ContextVar[Event] = ContextVar("current_event") current_state: ContextVar[T_State] = ContextVar("current_state") @@ -259,7 +259,7 @@ class Matcher(metaclass=MatcherMeta): return NewMatcher @classmethod - async def check_perm(cls, bot: "Bot", event: "Event") -> bool: + async def check_perm(cls, bot: Bot, event: Event) -> bool: """ :说明: @@ -279,8 +279,7 @@ class Matcher(metaclass=MatcherMeta): await cls.permission(bot, event)) @classmethod - async def check_rule(cls, bot: "Bot", event: "Event", - state: T_State) -> bool: + async def check_rule(cls, bot: Bot, event: Event, state: T_State) -> bool: """ :说明: @@ -383,18 +382,21 @@ class Matcher(metaclass=MatcherMeta): * 无 """ + async def _receive(state: T_State) -> Union[None, NoReturn]: + if state.get(_receive): + return + state[_receive] = True + raise RejectedException + def _decorator(func: T_Handler) -> T_Handler: - async def _receive() -> NoReturn: - func_handler.remove_dependency(depend) - raise PausedException - depend = Depends(_receive) + if cls.handlers and cls.handlers[-1].func is func: func_handler = cls.handlers[-1] func_handler.prepend_dependency(depend) else: - func_handler = cls.append_handler( + cls.append_handler( func, dependencies=[depend] if cls.handlers else []) return func @@ -405,7 +407,7 @@ class Matcher(metaclass=MatcherMeta): def got( cls, key: str, - prompt: Optional[Union[str, "Message", "MessageSegment", + prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None, args_parser: Optional[T_ArgsParser] = None ) -> Callable[[T_Handler], T_Handler]: @@ -421,32 +423,36 @@ class Matcher(metaclass=MatcherMeta): * ``args_parser: Optional[T_ArgsParser]``: 可选参数解析函数,空则使用默认解析函数 """ + async def _key_getter(bot: Bot, event: Event, state: T_State): + if state.get(f"_{key}_prompted"): + return + + state["_current_key"] = key + state[f"_{key}_prompted"] = True + if key not in state: + if prompt is not None: + if isinstance(prompt, MessageTemplate): + _prompt = prompt.format(**state) + else: + _prompt = prompt + await bot.send(event=event, message=_prompt) + raise RejectedException + else: + state[f"_{key}_parsed"] = True + + async def _key_parser(bot: Bot, event: Event, state: T_State): + if key in state and state.get(f"_{key}_parsed"): + return + + parser = args_parser or cls._default_parser + if parser: + await parser(bot, event, state) + else: + state[key] = str(event.get_message()) + state[f"_{key}_parsed"] = True + def _decorator(func: T_Handler) -> T_Handler: - async def _key_getter(bot: "Bot", event: "Event", state: T_State): - func_handler.remove_dependency(get_depend) - state["_current_key"] = key - if key not in state: - if prompt is not None: - if isinstance(prompt, MessageTemplate): - _prompt = prompt.format(**state) - else: - _prompt = prompt - await bot.send(event=event, message=_prompt) - raise PausedException - else: - state["_skip_key"] = True - - async def _key_parser(bot: "Bot", event: "Event", state: T_State): - if key in state and state.get("_skip_key"): - del state["_skip_key"] - return - parser = args_parser or cls._default_parser - if parser: - await parser(bot, event, state) - else: - state[state["_current_key"]] = str(event.get_message()) - get_depend = Depends(_key_getter) parser_depend = Depends(_key_parser) @@ -455,15 +461,15 @@ class Matcher(metaclass=MatcherMeta): func_handler.prepend_dependency(parser_depend) func_handler.prepend_dependency(get_depend) else: - func_handler = cls.append_handler( - func, dependencies=[get_depend, parser_depend]) + cls.append_handler(func, + dependencies=[get_depend, parser_depend]) return func return _decorator @classmethod - async def send(cls, message: Union[str, "Message", "MessageSegment", + async def send(cls, message: Union[str, Message, MessageSegment, MessageTemplate], **kwargs) -> Any: """ :说明: @@ -486,7 +492,7 @@ class Matcher(metaclass=MatcherMeta): @classmethod async def finish(cls, - message: Optional[Union[str, "Message", "MessageSegment", + message: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None, **kwargs) -> NoReturn: """ @@ -512,7 +518,7 @@ class Matcher(metaclass=MatcherMeta): @classmethod async def pause(cls, - prompt: Optional[Union[str, "Message", "MessageSegment", + prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None, **kwargs) -> NoReturn: """ @@ -538,8 +544,8 @@ class Matcher(metaclass=MatcherMeta): @classmethod async def reject(cls, - prompt: Optional[Union[str, "Message", - "MessageSegment"]] = None, + prompt: Optional[Union[str, Message, + MessageSegment]] = None, **kwargs) -> NoReturn: """ :说明: @@ -554,6 +560,8 @@ class Matcher(metaclass=MatcherMeta): bot = current_bot.get() event = current_event.get() state = current_state.get() + if "_current_key" in state and f"_{state['_current_key']}_parsed" in state: + del state[f"_{state['_current_key']}_parsed"] if isinstance(prompt, MessageTemplate): _prompt = prompt.format(**state) else: @@ -571,7 +579,7 @@ class Matcher(metaclass=MatcherMeta): self.block = True # 运行handlers - async def run(self, bot: "Bot", event: "Event", state: T_State): + async def run(self, bot: Bot, event: Event, state: T_State): b_t = current_bot.set(bot) e_t = current_event.set(event) s_t = current_state.set(self.state) diff --git a/nonebot/processor/utils.py b/nonebot/processor/utils.py index bdae3aea..6f13e96c 100644 --- a/nonebot/processor/utils.py +++ b/nonebot/processor/utils.py @@ -1,8 +1,9 @@ import inspect from typing import Any, Dict, Type, Tuple, Union, Callable +from typing_extensions import GenericAlias, get_args, get_origin # type: ignore -from pydantic.typing import (ForwardRef, get_args, get_origin, - evaluate_forwardref) +from loguru import logger +from pydantic.typing import ForwardRef, evaluate_forwardref def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: @@ -25,7 +26,13 @@ def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, annotation = param.annotation if isinstance(annotation, str): annotation = ForwardRef(annotation) - annotation = evaluate_forwardref(annotation, globalns, globalns) + try: + annotation = evaluate_forwardref(annotation, globalns, globalns) + except Exception as e: + logger.opt(colors=True, exception=e).warning( + f"Unknown ForwardRef[\"{param.annotation}\"] for parameter {param.name}" + ) + return inspect.Parameter.empty return annotation @@ -33,13 +40,16 @@ def generic_check_issubclass( cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]) -> bool: try: - return isinstance(cls, type) and issubclass(cls, class_or_tuple) + return issubclass(cls, class_or_tuple) except TypeError: if get_origin(cls) is Union: for type_ in get_args(cls): if not generic_check_issubclass(type_, class_or_tuple): return False return True + elif isinstance(cls, GenericAlias): + origin = get_origin(cls) + return bool(origin and issubclass(origin, class_or_tuple)) raise diff --git a/nonebot/rule.py b/nonebot/rule.py index 863339df..40fc1b43 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -15,20 +15,18 @@ import asyncio from itertools import product from argparse import Namespace from argparse import ArgumentParser as ArgParser -from typing import (TYPE_CHECKING, Any, Dict, Tuple, Union, Callable, NoReturn, - Optional, Sequence, Awaitable) +from typing import (Any, Dict, Tuple, Union, Callable, NoReturn, Optional, + Sequence, Awaitable) from pygtrie import CharTrie from nonebot import get_driver from nonebot.log import logger from nonebot.utils import run_sync +from nonebot.adapters import Bot, Event from nonebot.exception import ParserExit from nonebot.typing import T_State, T_RuleChecker -if TYPE_CHECKING: - from nonebot.adapters import Bot, Event - class Rule: """ @@ -48,8 +46,8 @@ class Rule: __slots__ = ("checkers",) def __init__( - self, *checkers: Callable[["Bot", "Event", T_State], - Awaitable[bool]]) -> None: + self, *checkers: Callable[[Bot, Event, T_State], + Awaitable[bool]]) -> None: """ :参数: @@ -67,8 +65,7 @@ class Rule: * ``Set[Callable[[Bot, Event, T_State], Awaitable[bool]]]`` """ - async def __call__(self, bot: "Bot", event: "Event", - state: T_State) -> bool: + async def __call__(self, bot: Bot, event: Event, state: T_State) -> bool: """ :说明: @@ -123,7 +120,7 @@ class TrieRule: cls.suffix[suffix[::-1]] = value @classmethod - def get_value(cls, bot: "Bot", event: "Event", + def get_value(cls, bot: Bot, event: Event, state: T_State) -> Tuple[Dict[str, Any], Dict[str, Any]]: if event.get_type() != "message": state["_prefix"] = {"raw_command": None, "command": None} @@ -195,7 +192,7 @@ def startswith(msg: Union[str, Tuple[str, ...]], f"^(?:{'|'.join(re.escape(prefix) for prefix in msg)})", re.IGNORECASE if ignorecase else 0) - async def _startswith(bot: "Bot", event: "Event", state: T_State) -> bool: + async def _startswith(bot: Bot, event: Event, state: T_State) -> bool: if event.get_type() != "message": return False text = event.get_plaintext() @@ -222,7 +219,7 @@ def endswith(msg: Union[str, Tuple[str, ...]], f"(?:{'|'.join(re.escape(prefix) for prefix in msg)})$", re.IGNORECASE if ignorecase else 0) - async def _endswith(bot: "Bot", event: "Event", state: T_State) -> bool: + async def _endswith(bot: Bot, event: Event, state: T_State) -> bool: if event.get_type() != "message": return False text = event.get_plaintext() @@ -242,7 +239,7 @@ def keyword(*keywords: str) -> Rule: * ``*keywords: str``: 关键词 """ - async def _keyword(bot: "Bot", event: "Event", state: T_State) -> bool: + async def _keyword(bot: Bot, event: Event, state: T_State) -> bool: if event.get_type() != "message": return False text = event.get_plaintext() @@ -290,7 +287,7 @@ def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule: for start, sep in product(command_start, command_sep): TrieRule.add_prefix(f"{start}{sep.join(command)}", command) - async def _command(bot: "Bot", event: "Event", state: T_State) -> bool: + async def _command(bot: Bot, event: Event, state: T_State) -> bool: return state["_prefix"]["command"] in commands return Rule(_command) @@ -376,8 +373,7 @@ def shell_command(*cmds: Union[str, Tuple[str, ...]], for start, sep in product(command_start, command_sep): TrieRule.add_prefix(f"{start}{sep.join(command)}", command) - async def _shell_command(bot: "Bot", event: "Event", - state: T_State) -> bool: + async def _shell_command(bot: Bot, event: Event, state: T_State) -> bool: if state["_prefix"]["command"] in commands: message = str(event.get_message()) strip_message = message[len(state["_prefix"]["raw_command"] @@ -417,7 +413,7 @@ def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule: pattern = re.compile(regex, flags) - async def _regex(bot: "Bot", event: "Event", state: T_State) -> bool: + async def _regex(bot: Bot, event: Event, state: T_State) -> bool: if event.get_type() != "message": return False matched = pattern.search(str(event.get_message())) @@ -443,7 +439,7 @@ def to_me() -> Rule: * 无 """ - async def _to_me(bot: "Bot", event: "Event", state: T_State) -> bool: + async def _to_me(bot: Bot, event: Event, state: T_State) -> bool: return event.is_tome() return Rule(_to_me) diff --git a/tests/test_plugins/test_depends.py b/tests/test_plugins/test_depends.py new file mode 100644 index 00000000..77580374 --- /dev/null +++ b/tests/test_plugins/test_depends.py @@ -0,0 +1,17 @@ +from nonebot import on_command +from nonebot.log import logger +from nonebot.processor import Depends + +test = on_command("123") + + +def depend(state: dict): + return state + + +@test.got("a", prompt="a") +@test.got("b", prompt="b") +@test.receive() +@test.got("c", prompt="c") +async def _(state: dict = Depends(depend)): + logger.info(f"=======, {state}") From cafe5c9af0d438ed18b81d30e6ac3010b223c107 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Mon, 15 Nov 2021 01:28:47 +0800 Subject: [PATCH 06/21] :construction: add generator dependency support --- nonebot/drivers/__init__.py | 5 +++++ nonebot/processor/__init__.py | 22 +++++++++++++++++----- nonebot/processor/handler.py | 31 +++++++++++++++++++++---------- nonebot/processor/models.py | 2 +- nonebot/utils.py | 32 +++++++++++++++++++++++++++++++- 5 files changed, 75 insertions(+), 17 deletions(-) diff --git a/nonebot/drivers/__init__.py b/nonebot/drivers/__init__.py index d08f6b0c..74c42ff6 100644 --- a/nonebot/drivers/__init__.py +++ b/nonebot/drivers/__init__.py @@ -40,6 +40,11 @@ class Driver(abc.ABC): :类型: ``Set[T_BotDisconnectionHook]`` :说明: Bot 连接断开时执行的函数 """ + dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {} + """ + :类型: ``Dict[Callable[..., Any], Callable[..., Any]]`` + :说明: Depends 函数的替换表 + """ def __init__(self, env: Env, config: Config): """ diff --git a/nonebot/processor/__init__.py b/nonebot/processor/__init__.py index 70689631..8b344b01 100644 --- a/nonebot/processor/__init__.py +++ b/nonebot/processor/__init__.py @@ -1,15 +1,17 @@ import inspect from itertools import chain from typing import Any, Dict, List, Tuple, Callable, Optional, cast +from contextlib import AsyncExitStack, contextmanager, asynccontextmanager from .models import Dependent from nonebot.log import logger from nonebot.typing import T_State from nonebot.adapters import Bot, Event from .models import Depends as DependsClass -from nonebot.utils import run_sync, is_coroutine_callable from .utils import (generic_get_types, get_typed_signature, generic_check_issubclass) +from nonebot.utils import (run_sync, is_gen_callable, run_sync_ctx_manager, + is_async_gen_callable, is_coroutine_callable) def get_param_sub_dependent(*, param: inspect.Parameter) -> Dependent: @@ -95,11 +97,12 @@ async def solve_dependencies( bot: Bot, event: Event, state: T_State, - matcher: "Matcher", + matcher: Optional["Matcher"], + stack: Optional[AsyncExitStack] = None, sub_dependents: Optional[List[Dependent]] = None, dependency_overrides_provider: Optional[Any] = None, - dependency_cache: Optional[Dict[Tuple[Callable[..., Any]], Any]] = None, -) -> Tuple[Dict[str, Any], Dict[Tuple[Callable[..., Any]], Any], bool]: + dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None, +) -> Tuple[Dict[str, Any], Dict[Callable[..., Any], Any], bool]: values: Dict[str, Any] = {} dependency_cache = dependency_cache or {} @@ -108,7 +111,7 @@ async def solve_dependencies( for sub_dependent in chain(sub_dependents or tuple(), dependent.dependencies): sub_dependent.func = cast(Callable[..., Any], sub_dependent.func) - sub_dependent.cache_key = cast(Tuple[Callable[..., Any]], + sub_dependent.cache_key = cast(Callable[..., Any], sub_dependent.cache_key) func = sub_dependent.func @@ -158,6 +161,15 @@ async def solve_dependencies( # run dependency function if sub_dependent.use_cache and sub_dependent.cache_key in dependency_cache: solved = dependency_cache[sub_dependent.cache_key] + elif is_gen_callable(func) or is_async_gen_callable(func): + assert isinstance( + stack, AsyncExitStack + ), "Generator dependency should be called in context" + if is_gen_callable(func): + cm = run_sync_ctx_manager(contextmanager(func)(**sub_values)) + else: + cm = asynccontextmanager(func)(**sub_values) + solved = await stack.enter_async_context(cm) elif is_coroutine_callable(func): solved = await func(**sub_values) else: diff --git a/nonebot/processor/handler.py b/nonebot/processor/handler.py index 0aafc8c9..57e38af5 100644 --- a/nonebot/processor/handler.py +++ b/nonebot/processor/handler.py @@ -6,6 +6,7 @@ """ import asyncio +from contextlib import AsyncExitStack from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Callable, Optional from nonebot.log import logger @@ -37,15 +38,15 @@ class Handler: self.name = get_name(func) if name is None else name self.dependencies = dependencies or [] - self.sub_dependents: Dict[Tuple[Callable[..., Any]], Dependent] = {} + self.sub_dependents: Dict[Callable[..., Any], Dependent] = {} if dependencies: for depends in dependencies: if not depends.dependency: raise ValueError(f"{depends} has no dependency") - if (depends.dependency,) in self.sub_dependents: + if depends.dependency in self.sub_dependents: raise ValueError(f"{depends} is already in dependencies") sub_dependant = get_parameterless_sub_dependant(depends=depends) - self.sub_dependents[(depends.dependency,)] = sub_dependant + self.sub_dependents[depends.dependency] = sub_dependant self.dependency_overrides_provider = dependency_overrides_provider self.dependent = get_dependent(func=func) @@ -60,19 +61,29 @@ class Handler: def __str__(self) -> str: return repr(self) - async def __call__(self, matcher: "Matcher", bot: "Bot", event: "Event", - state: T_State): + async def __call__( + self, + matcher: "Matcher", + bot: "Bot", + event: "Event", + state: T_State, + *, + stack: Optional[AsyncExitStack] = None, + dependency_cache: Optional[Dict[Callable[..., Any], + Any]] = None) -> Any: values, _, ignored = await solve_dependencies( dependent=self.dependent, bot=bot, event=event, state=state, matcher=matcher, + stack=stack, sub_dependents=[ - self.sub_dependents[(dependency.dependency,)] # type: ignore + self.sub_dependents[dependency.dependency] # type: ignore for dependency in self.dependencies ], - dependency_overrides_provider=self.dependency_overrides_provider) + dependency_overrides_provider=self.dependency_overrides_provider, + dependency_cache=dependency_cache) if ignored: return @@ -101,7 +112,7 @@ class Handler: if (dependency.dependency,) in self.sub_dependents: raise ValueError(f"{dependency} is already in dependencies") sub_dependant = get_parameterless_sub_dependant(depends=dependency) - self.sub_dependents[(dependency.dependency,)] = sub_dependant + self.sub_dependents[dependency.dependency] = sub_dependant def prepend_dependency(self, dependency: Depends): self.cache_dependent(dependency) @@ -114,7 +125,7 @@ class Handler: def remove_dependency(self, dependency: Depends): if not dependency.dependency: raise ValueError(f"{dependency} has no dependency") - if (dependency.dependency,) in self.sub_dependents: - del self.sub_dependents[(dependency.dependency,)] + if dependency.dependency in self.sub_dependents: + del self.sub_dependents[dependency.dependency] if dependency in self.dependencies: self.dependencies.remove(dependency) diff --git a/nonebot/processor/models.py b/nonebot/processor/models.py index 0e3cd7e4..9413fb8a 100644 --- a/nonebot/processor/models.py +++ b/nonebot/processor/models.py @@ -45,4 +45,4 @@ class Dependent: self.matcher_param_name = matcher_param_name self.dependencies = dependencies or [] self.use_cache = use_cache - self.cache_key = (self.func,) + self.cache_key = self.func diff --git a/nonebot/utils.py b/nonebot/utils.py index f7d78457..2786ef1d 100644 --- a/nonebot/utils.py +++ b/nonebot/utils.py @@ -5,13 +5,16 @@ import inspect import dataclasses from functools import wraps, partial from typing_extensions import ParamSpec -from typing import Any, TypeVar, Callable, Optional, Awaitable +from contextlib import asynccontextmanager +from typing import (Any, TypeVar, Callable, Optional, Awaitable, AsyncGenerator, + ContextManager) from nonebot.log import logger from nonebot.typing import overrides P = ParamSpec("P") R = TypeVar("R") +T = TypeVar("T") def escape_tag(s: str) -> str: @@ -40,6 +43,20 @@ def is_coroutine_callable(func: Callable[..., Any]) -> bool: return inspect.iscoroutinefunction(func_) +def is_gen_callable(func: Callable[..., Any]) -> bool: + if inspect.isgeneratorfunction(func): + return True + func_ = getattr(func, "__call__", None) + return inspect.isgeneratorfunction(func_) + + +def is_async_gen_callable(func: Callable[..., Any]) -> bool: + if inspect.isasyncgenfunction(func): + return True + func_ = getattr(func, "__call__", None) + return inspect.isasyncgenfunction(func_) + + def run_sync(func: Callable[P, R]) -> Callable[P, Awaitable[R]]: """ :说明: @@ -65,6 +82,19 @@ def run_sync(func: Callable[P, R]) -> Callable[P, Awaitable[R]]: return _wrapper +@asynccontextmanager +async def run_sync_ctx_manager( + cm: ContextManager[T],) -> AsyncGenerator[T, None]: + try: + yield await run_sync(cm.__enter__)() + except Exception as e: + ok = await run_sync(cm.__exit__)(type(e), e, None) + if not ok: + raise e + else: + await run_sync(cm.__exit__)(None, None, None) + + def get_name(obj: Any) -> str: if inspect.isfunction(obj) or inspect.isclass(obj): return obj.__name__ From d1c6eeb6c2b731c7fd315c59d58592231a30b322 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Mon, 15 Nov 2021 21:44:24 +0800 Subject: [PATCH 07/21] :recycle: allow dynamic param types --- nonebot/adapters/_bot.py | 10 +++ nonebot/message.py | 110 +++++++++++++------------- nonebot/processor/__init__.py | 141 ++++++++++++++++++---------------- nonebot/processor/handler.py | 32 +++----- nonebot/processor/matcher.py | 12 ++- nonebot/processor/models.py | 21 ++--- nonebot/processor/params.py | 91 ++++++++++++++++++++++ 7 files changed, 260 insertions(+), 157 deletions(-) create mode 100644 nonebot/processor/params.py diff --git a/nonebot/adapters/_bot.py b/nonebot/adapters/_bot.py index b96e31ae..fba3a134 100644 --- a/nonebot/adapters/_bot.py +++ b/nonebot/adapters/_bot.py @@ -55,6 +55,16 @@ class Bot(abc.ABC): def __getattr__(self, name: str) -> _ApiCall: return partial(self.call_api, name) + @classmethod + def __get_validators__(cls): + yield cls.validate + + @classmethod + def validate(cls, v): + if not isinstance(v, cls): + raise TypeError(f"{v} is not an instance of {cls}") + return v + @property @abc.abstractmethod def type(self) -> str: diff --git a/nonebot/message.py b/nonebot/message.py index f03a7c6d..a74003fe 100644 --- a/nonebot/message.py +++ b/nonebot/message.py @@ -7,7 +7,8 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供 import asyncio from datetime import datetime -from typing import TYPE_CHECKING, Set, Type, Optional +from contextlib import AsyncExitStack +from typing import TYPE_CHECKING, Set, Type from nonebot.log import logger from nonebot.rule import TrieRule @@ -204,58 +205,63 @@ async def handle_event(bot: "Bot", event: "Event") -> None: logger.opt(colors=True).success(log_msg) state = {} - coros = list(map(lambda x: x(bot, event, state), _event_preprocessors)) - if coros: - 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 ignored") - return - except Exception as e: - logger.opt(colors=True, exception=e).error( - "Error when running EventPreProcessors. " - "Event ignored!") - return - # Trie Match - _, _ = TrieRule.get_value(bot, event, state) - - break_flag = False - for priority in sorted(matchers.keys()): - if break_flag: - break - - if show_log: - logger.debug(f"Checking for matchers in priority {priority}...") - - pending_tasks = [ - _check_matcher(priority, matcher, bot, event, state.copy()) - for matcher in matchers[priority] - ] - - results = await asyncio.gather(*pending_tasks, return_exceptions=True) - - for result in results: - if not isinstance(result, Exception): - continue - if isinstance(result, StopPropagation): - break_flag = True - logger.debug("Stop event propagation") - else: - logger.opt(colors=True, exception=result).error( - "Error when checking Matcher." + # TODO + async with AsyncExitStack() as stack: + coros = list(map(lambda x: x(bot, event, state), _event_preprocessors)) + if coros: + 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 ignored" ) + return + except Exception as e: + logger.opt(colors=True, exception=e).error( + "Error when running EventPreProcessors. " + "Event ignored!") + return + + # Trie Match + _, _ = TrieRule.get_value(bot, event, state) + + break_flag = False + for priority in sorted(matchers.keys()): + if break_flag: + break - coros = list(map(lambda x: x(bot, event, state), _event_postprocessors)) - if coros: - try: if show_log: - logger.debug("Running PostProcessors...") - await asyncio.gather(*coros) - except Exception as e: - logger.opt(colors=True, exception=e).error( - "Error when running EventPostProcessors" - ) + logger.debug(f"Checking for matchers in priority {priority}...") + + pending_tasks = [ + _check_matcher(priority, matcher, bot, event, state.copy()) + for matcher in matchers[priority] + ] + + results = await asyncio.gather(*pending_tasks, + return_exceptions=True) + + for result in results: + if not isinstance(result, Exception): + continue + if isinstance(result, StopPropagation): + break_flag = True + logger.debug("Stop event propagation") + else: + logger.opt(colors=True, exception=result).error( + "Error when checking Matcher." + ) + + coros = list(map(lambda x: x(bot, event, state), _event_postprocessors)) + if coros: + try: + if show_log: + logger.debug("Running PostProcessors...") + await asyncio.gather(*coros) + except Exception as e: + logger.opt(colors=True, exception=e).error( + "Error when running EventPostProcessors" + ) diff --git a/nonebot/processor/__init__.py b/nonebot/processor/__init__.py index 8b344b01..9860fc8d 100644 --- a/nonebot/processor/__init__.py +++ b/nonebot/processor/__init__.py @@ -1,15 +1,18 @@ import inspect from itertools import chain -from typing import Any, Dict, List, Tuple, Callable, Optional, cast +from typing import Any, Dict, List, Type, Tuple, Callable, Optional, cast from contextlib import AsyncExitStack, contextmanager, asynccontextmanager +from pydantic import BaseConfig +from pydantic.fields import Required, ModelField +from pydantic.schema import get_annotation_from_field_info + from .models import Dependent from nonebot.log import logger from nonebot.typing import T_State +from .utils import get_typed_signature from nonebot.adapters import Bot, Event from .models import Depends as DependsClass -from .utils import (generic_get_types, get_typed_signature, - generic_check_issubclass) from nonebot.utils import (run_sync, is_gen_callable, run_sync_ctx_manager, is_async_gen_callable, is_coroutine_callable) @@ -27,33 +30,42 @@ def get_param_sub_dependent(*, param: inspect.Parameter) -> Dependent: ) -def get_parameterless_sub_dependant(*, depends: DependsClass) -> Dependent: +def get_parameterless_sub_dependant( + *, + depends: DependsClass, + allow_types: Optional[List["ParamTypes"]] = None) -> Dependent: assert callable( depends.dependency ), "A parameter-less dependency must have a callable dependency" - return get_sub_dependant(depends=depends, dependency=depends.dependency) + return get_sub_dependant(depends=depends, + dependency=depends.dependency, + allow_types=allow_types) def get_sub_dependant( - *, - depends: DependsClass, - dependency: Callable[..., Any], - name: Optional[str] = None, -) -> Dependent: - sub_dependant = get_dependent( - func=dependency, - name=name, - use_cache=depends.use_cache, - ) + *, + depends: DependsClass, + dependency: Callable[..., Any], + name: Optional[str] = None, + allow_types: Optional[List["ParamTypes"]] = None) -> Dependent: + sub_dependant = get_dependent(func=dependency, + name=name, + use_cache=depends.use_cache, + allow_types=allow_types) return sub_dependant -def get_dependent(*, - func: Callable[..., Any], - name: Optional[str] = None, - use_cache: bool = True) -> Dependent: +def get_dependent( + *, + func: Callable[..., Any], + name: Optional[str] = None, + use_cache: bool = True, + allow_types: Optional[List["ParamTypes"]] = None) -> Dependent: signature = get_typed_signature(func) params = signature.parameters + allow_types = allow_types or [ + ParamTypes.BOT, ParamTypes.EVENT, ParamTypes.STATE + ] dependent = Dependent(func=func, name=name, use_cache=use_cache) for param_name, param in params.items(): if isinstance(param.default, DependsClass): @@ -61,33 +73,29 @@ def get_dependent(*, dependent.dependencies.append(sub_dependent) continue - if generic_check_issubclass(param.annotation, Bot): - if dependent.bot_param_name is not None: - raise ValueError(f"{func} has more than one Bot parameter: " - f"{dependent.bot_param_name} / {param_name}") - dependent.bot_param_name = param_name - dependent.bot_param_type = generic_get_types(param.annotation) - elif generic_check_issubclass(param.annotation, Event): - if dependent.event_param_name is not None: - raise ValueError(f"{func} has more than one Event parameter: " - f"{dependent.event_param_name} / {param_name}") - dependent.event_param_name = param_name - dependent.event_param_type = generic_get_types(param.annotation) - elif generic_check_issubclass(param.annotation, Dict): - if dependent.state_param_name is not None: - raise ValueError(f"{func} has more than one State parameter: " - f"{dependent.state_param_name} / {param_name}") - dependent.state_param_name = param_name - elif generic_check_issubclass(param.annotation, Matcher): - if dependent.matcher_param_name is not None: - raise ValueError( - f"{func} has more than one Matcher parameter: " - f"{dependent.matcher_param_name} / {param_name}") - dependent.matcher_param_name = param_name + for allow_type in allow_types: + field_info_class: Type[Param] = allow_type.value + if field_info_class._check(param_name, param): + field_info = field_info_class(param.default) + break else: raise ValueError( f"Unknown parameter {param_name} with type {param.annotation}") + annotation: Any = Any + if param.annotation != param.empty: + annotation = param.annotation + annotation = get_annotation_from_field_info(annotation, field_info, + param_name) + dependent.params.append( + ModelField(name=param_name, + type_=annotation, + class_validators=None, + model_config=BaseConfig, + default=Required, + required=True, + field_info=field_info)) + return dependent @@ -97,7 +105,8 @@ async def solve_dependencies( bot: Bot, event: Event, state: T_State, - matcher: Optional["Matcher"], + matcher: Optional["Matcher"] = None, + exception: Optional[Exception] = None, stack: Optional[AsyncExitStack] = None, sub_dependents: Optional[List[Dependent]] = None, dependency_overrides_provider: Optional[Any] = None, @@ -115,20 +124,6 @@ async def solve_dependencies( sub_dependent.cache_key) func = sub_dependent.func - # check bot and event type - if sub_dependent.bot_param_type and not isinstance( - bot, sub_dependent.bot_param_type): - logger.debug( - f"Matcher {matcher} bot type {type(bot)} not match depends {func} " - f"annotation {sub_dependent.bot_param_type}, ignored") - return values, dependency_cache, True - elif sub_dependent.event_param_type and not isinstance( - event, sub_dependent.event_param_type): - logger.debug( - f"Matcher {matcher} event type {type(event)} not match depends {func} " - f"annotation {sub_dependent.event_param_type}, ignored") - return values, dependency_cache, True - # dependency overrides use_sub_dependant = sub_dependent if (dependency_overrides_provider and @@ -183,14 +178,28 @@ async def solve_dependencies( dependency_cache[sub_dependent.cache_key] = solved # usual dependency - if dependent.bot_param_name is not None: - values[dependent.bot_param_name] = bot - if dependent.event_param_name is not None: - values[dependent.event_param_name] = event - if dependent.state_param_name is not None: - values[dependent.state_param_name] = state - if dependent.matcher_param_name is not None: - values[dependent.matcher_param_name] = matcher + for field in dependent.params: + field_info = field.field_info + assert isinstance(field_info, + Param), "Params must be subclasses of Param" + value = field_info._solve(bot=bot, + event=event, + state=state, + matcher=matcher, + exception=exception) + _, errs_ = field.validate(value, + values, + loc=(ParamTypes(type(field_info)).name, + field.alias)) + if errs_: + logger.debug( + f"Matcher {matcher} {ParamTypes(type(field_info)).name} " + f"type {type(value)} not match depends {dependent.func} " + f"annotation {field._type_display()}, ignored") + return values, dependency_cache, True + else: + values[field.name] = value + return values, dependency_cache, False @@ -200,6 +209,8 @@ def Depends(dependency: Optional[Callable[..., Any]] = None, return DependsClass(dependency=dependency, use_cache=use_cache) +from .params import Param from .handler import Handler as Handler from .matcher import Matcher as Matcher from .matcher import matchers as matchers +from .params import ParamTypes as ParamTypes diff --git a/nonebot/processor/handler.py b/nonebot/processor/handler.py index 57e38af5..fed8d92c 100644 --- a/nonebot/processor/handler.py +++ b/nonebot/processor/handler.py @@ -9,7 +9,6 @@ import asyncio from contextlib import AsyncExitStack from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Callable, Optional -from nonebot.log import logger from .models import Depends, Dependent from nonebot.utils import get_name, run_sync from nonebot.typing import T_State, T_Handler @@ -17,6 +16,7 @@ from . import get_dependent, solve_dependencies, get_parameterless_sub_dependant if TYPE_CHECKING: from .matcher import Matcher + from .params import ParamTypes from nonebot.adapters import Bot, Event @@ -28,6 +28,7 @@ class Handler: *, name: Optional[str] = None, dependencies: Optional[List[Depends]] = None, + allow_types: Optional[List["ParamTypes"]] = None, dependency_overrides_provider: Optional[Any] = None): """装饰事件处理函数以便根据动态参数运行""" self.func: T_Handler = func @@ -36,6 +37,7 @@ class Handler: :说明: 事件处理函数 """ self.name = get_name(func) if name is None else name + self.allow_types = allow_types self.dependencies = dependencies or [] self.sub_dependents: Dict[Callable[..., Any], Dependent] = {} @@ -45,18 +47,16 @@ class Handler: raise ValueError(f"{depends} has no dependency") if depends.dependency in self.sub_dependents: raise ValueError(f"{depends} is already in dependencies") - sub_dependant = get_parameterless_sub_dependant(depends=depends) + sub_dependant = get_parameterless_sub_dependant( + depends=depends, allow_types=self.allow_types) self.sub_dependents[depends.dependency] = sub_dependant self.dependency_overrides_provider = dependency_overrides_provider - self.dependent = get_dependent(func=func) + self.dependent = get_dependent(func=func, allow_types=self.allow_types) def __repr__(self) -> str: return ( - f"") + f"" + ) def __str__(self) -> str: return repr(self) @@ -88,19 +88,6 @@ class Handler: if ignored: return - # check bot and event type - if self.dependent.bot_param_type and not isinstance( - bot, self.dependent.bot_param_type): - logger.debug(f"Matcher {matcher} bot type {type(bot)} not match " - f"annotation {self.dependent.bot_param_type}, ignored") - return - elif self.dependent.event_param_type and not isinstance( - event, self.dependent.event_param_type): - logger.debug( - f"Matcher {matcher} event type {type(event)} not match " - f"annotation {self.dependent.event_param_type}, ignored") - return - if asyncio.iscoroutinefunction(self.func): await self.func(**values) else: @@ -111,7 +98,8 @@ class Handler: raise ValueError(f"{dependency} has no dependency") if (dependency.dependency,) in self.sub_dependents: raise ValueError(f"{dependency} is already in dependencies") - sub_dependant = get_parameterless_sub_dependant(depends=dependency) + sub_dependant = get_parameterless_sub_dependant( + depends=dependency, allow_types=self.allow_types) self.sub_dependents[dependency.dependency] = sub_dependant def prepend_dependency(self, dependency: Depends): diff --git a/nonebot/processor/matcher.py b/nonebot/processor/matcher.py index 9fbceaa9..4ae3ca21 100644 --- a/nonebot/processor/matcher.py +++ b/nonebot/processor/matcher.py @@ -15,6 +15,7 @@ from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Callable, from .models import Depends from .handler import Handler from nonebot.rule import Rule +from .params import ParamTypes from nonebot import get_driver from nonebot.log import logger from nonebot.permission import USER, Permission @@ -153,6 +154,10 @@ class Matcher(metaclass=MatcherMeta): :说明: 事件响应器权限更新函数 """ + HANDLER_PARAM_TYPES = [ + ParamTypes.BOT, ParamTypes.EVENT, ParamTypes.STATE, ParamTypes.MATCHER + ] + def __init__(self): """实例化 Matcher 以便运行""" self.handlers = self.handlers.copy() @@ -230,7 +235,9 @@ class Matcher(metaclass=MatcherMeta): permission or Permission(), "handlers": [ handler if isinstance(handler, Handler) else Handler( - handler, dependency_overrides_provider=get_driver()) + handler, + dependency_overrides_provider=get_driver(), + allow_types=cls.HANDLER_PARAM_TYPES) for handler in handlers ] if handlers else [], "temp": @@ -348,7 +355,8 @@ class Matcher(metaclass=MatcherMeta): dependencies: Optional[List[Depends]] = None) -> Handler: handler_ = Handler(handler, dependencies=dependencies, - dependency_overrides_provider=get_driver()) + dependency_overrides_provider=get_driver(), + allow_types=cls.HANDLER_PARAM_TYPES) cls.handlers.append(handler_) return handler_ diff --git a/nonebot/processor/models.py b/nonebot/processor/models.py index 9413fb8a..06d11890 100644 --- a/nonebot/processor/models.py +++ b/nonebot/processor/models.py @@ -1,10 +1,9 @@ -from typing import TYPE_CHECKING, Any, List, Type, Tuple, Callable, Optional +from typing import Any, List, Callable, Optional + +from pydantic.fields import ModelField from nonebot.utils import get_name -if TYPE_CHECKING: - from nonebot.adapters import Bot, Event - class Depends: @@ -27,22 +26,12 @@ class Dependent: *, func: Optional[Callable[..., Any]] = None, name: Optional[str] = None, - bot_param_name: Optional[str] = None, - bot_param_type: Optional[Tuple[Type["Bot"], ...]] = None, - event_param_name: Optional[str] = None, - event_param_type: Optional[Tuple[Type["Event"], ...]] = None, - state_param_name: Optional[str] = None, - matcher_param_name: Optional[str] = None, + params: Optional[List[ModelField]] = None, dependencies: Optional[List["Dependent"]] = None, use_cache: bool = True) -> None: self.func = func self.name = name - self.bot_param_name = bot_param_name - self.bot_param_type = bot_param_type - self.event_param_name = event_param_name - self.event_param_type = event_param_type - self.state_param_name = state_param_name - self.matcher_param_name = matcher_param_name + self.params = params or [] self.dependencies = dependencies or [] self.use_cache = use_cache self.cache_key = self.func diff --git a/nonebot/processor/params.py b/nonebot/processor/params.py new file mode 100644 index 00000000..f777889c --- /dev/null +++ b/nonebot/processor/params.py @@ -0,0 +1,91 @@ +import abc +import inspect +from enum import Enum +from typing import Any, Dict, Optional + +from pydantic.fields import FieldInfo + +from nonebot.typing import T_State +from nonebot.adapters import Bot, Event +from .utils import generic_check_issubclass + + +class Param(FieldInfo, abc.ABC): + + def __repr__(self) -> str: + return f"{self.__class__.__name__}" + + def __str__(self) -> str: + return repr(self) + + @classmethod + @abc.abstractmethod + def _check(cls, name: str, param: inspect.Parameter) -> bool: + raise NotImplementedError + + @abc.abstractmethod + def _solve(self, **kwargs: Any) -> Any: + raise NotImplementedError + + +class BotParam(Param): + + @classmethod + def _check(cls, name: str, param: inspect.Parameter) -> bool: + return generic_check_issubclass(param.annotation, Bot) + + def _solve(self, bot: Bot, **kwargs: Any) -> Any: + return bot + + +class EventParam(Param): + + @classmethod + def _check(cls, name: str, param: inspect.Parameter) -> bool: + return generic_check_issubclass(param.annotation, Event) + + def _solve(self, event: Event, **kwargs: Any) -> Any: + return event + + +class StateParam(Param): + + @classmethod + def _check(cls, name: str, param: inspect.Parameter) -> bool: + return generic_check_issubclass(param.annotation, Dict) + + def _solve(self, state: T_State, **kwargs: Any) -> Any: + return state + + +class MatcherParam(Param): + + @classmethod + def _check(cls, name: str, param: inspect.Parameter) -> bool: + return generic_check_issubclass(param.annotation, Matcher) + + def _solve(self, matcher: Optional["Matcher"] = None, **kwargs: Any) -> Any: + return matcher + + +class ExceptionParam(Param): + + @classmethod + def _check(cls, name: str, param: inspect.Parameter) -> bool: + return generic_check_issubclass(param.annotation, Exception) + + def _solve(self, + exception: Optional[Exception] = None, + **kwargs: Any) -> Any: + return exception + + +class ParamTypes(Enum): + BOT = BotParam + EVENT = EventParam + STATE = StateParam + MATCHER = MatcherParam + EXCEPTION = ExceptionParam + + +from .matcher import Matcher From 4cbdd726e51dfa476c2d75f388e9f01f7c9e51dd Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Tue, 16 Nov 2021 18:30:16 +0800 Subject: [PATCH 08/21] :building_construction: change code structure --- docs_build/dependencies.rst | 12 ++ nonebot/__init__.py | 1 + nonebot/adapters/_bot.py | 10 -- .../{processor => dependencies}/__init__.py | 117 +++++++++--------- nonebot/{processor => dependencies}/models.py | 28 ++++- nonebot/{processor => dependencies}/utils.py | 26 +--- nonebot/{processor => }/handler.py | 93 ++++++++------ nonebot/{processor => }/matcher.py | 28 +++-- nonebot/message.py | 60 ++++++--- nonebot/{processor => }/params.py | 55 +++----- nonebot/plugin/on.py | 3 +- nonebot/plugin/on.pyi | 3 +- nonebot/plugin/plugin.py | 2 +- nonebot/plugins/echo.py | 10 +- nonebot/plugins/single_session.py | 7 +- nonebot/typing.py | 12 +- nonebot/utils.py | 25 +++- tests/test_plugins/test_depends.py | 2 +- tests/test_plugins/test_processor.py | 8 +- 19 files changed, 276 insertions(+), 226 deletions(-) create mode 100644 docs_build/dependencies.rst rename nonebot/{processor => dependencies}/__init__.py (71%) rename nonebot/{processor => dependencies}/models.py (59%) rename nonebot/{processor => dependencies}/utils.py (57%) rename nonebot/{processor => }/handler.py (52%) rename nonebot/{processor => }/matcher.py (96%) rename nonebot/{processor => }/params.py (53%) diff --git a/docs_build/dependencies.rst b/docs_build/dependencies.rst new file mode 100644 index 00000000..4db2e19a --- /dev/null +++ b/docs_build/dependencies.rst @@ -0,0 +1,12 @@ +\-\-\- +contentSidebar: true +sidebarDepth: 0 +\-\-\- + +NoneBot.handler 模块 +==================== + +.. automodule:: nonebot.dependencies + :members: + :private-members: + :show-inheritance: diff --git a/nonebot/__init__.py b/nonebot/__init__.py index a9c6d350..809b696c 100644 --- a/nonebot/__init__.py +++ b/nonebot/__init__.py @@ -278,6 +278,7 @@ def run(host: Optional[str] = None, get_driver().run(host, port, *args, **kwargs) +import nonebot.params as params from nonebot.plugin import export as export from nonebot.plugin import require as require from nonebot.plugin import on_regex as on_regex diff --git a/nonebot/adapters/_bot.py b/nonebot/adapters/_bot.py index fba3a134..b96e31ae 100644 --- a/nonebot/adapters/_bot.py +++ b/nonebot/adapters/_bot.py @@ -55,16 +55,6 @@ class Bot(abc.ABC): def __getattr__(self, name: str) -> _ApiCall: return partial(self.call_api, name) - @classmethod - def __get_validators__(cls): - yield cls.validate - - @classmethod - def validate(cls, v): - if not isinstance(v, cls): - raise TypeError(f"{v} is not an instance of {cls}") - return v - @property @abc.abstractmethod def type(self) -> str: diff --git a/nonebot/processor/__init__.py b/nonebot/dependencies/__init__.py similarity index 71% rename from nonebot/processor/__init__.py rename to nonebot/dependencies/__init__.py index 9860fc8d..bbf835fb 100644 --- a/nonebot/processor/__init__.py +++ b/nonebot/dependencies/__init__.py @@ -1,3 +1,10 @@ +""" +依赖注入处理模块 +=============== + +该模块实现了依赖注入的定义与处理。 +""" + import inspect from itertools import chain from typing import Any, Dict, List, Type, Tuple, Callable, Optional, cast @@ -7,33 +14,38 @@ from pydantic import BaseConfig from pydantic.fields import Required, ModelField from pydantic.schema import get_annotation_from_field_info -from .models import Dependent from nonebot.log import logger -from nonebot.typing import T_State +from .models import Param as Param from .utils import get_typed_signature -from nonebot.adapters import Bot, Event -from .models import Depends as DependsClass +from .models import Dependent as Dependent +from .models import DependsWrapper as DependsWrapper from nonebot.utils import (run_sync, is_gen_callable, run_sync_ctx_manager, is_async_gen_callable, is_coroutine_callable) -def get_param_sub_dependent(*, param: inspect.Parameter) -> Dependent: - depends: DependsClass = param.default +class CustomConfig(BaseConfig): + arbitrary_types_allowed = True + + +def get_param_sub_dependent( + *, + param: inspect.Parameter, + allow_types: Optional[List[Type[Param]]] = None) -> Dependent: + depends: DependsWrapper = param.default if depends.dependency: dependency = depends.dependency else: dependency = param.annotation - return get_sub_dependant( - depends=depends, - dependency=dependency, - name=param.name, - ) + return get_sub_dependant(depends=depends, + dependency=dependency, + name=param.name, + allow_types=allow_types) def get_parameterless_sub_dependant( *, - depends: DependsClass, - allow_types: Optional[List["ParamTypes"]] = None) -> Dependent: + depends: DependsWrapper, + allow_types: Optional[List[Type[Param]]] = None) -> Dependent: assert callable( depends.dependency ), "A parameter-less dependency must have a callable dependency" @@ -44,10 +56,10 @@ def get_parameterless_sub_dependant( def get_sub_dependant( *, - depends: DependsClass, + depends: DependsWrapper, dependency: Callable[..., Any], name: Optional[str] = None, - allow_types: Optional[List["ParamTypes"]] = None) -> Dependent: + allow_types: Optional[List[Type[Param]]] = None) -> Dependent: sub_dependant = get_dependent(func=dependency, name=name, use_cache=depends.use_cache, @@ -55,32 +67,32 @@ def get_sub_dependant( return sub_dependant -def get_dependent( - *, - func: Callable[..., Any], - name: Optional[str] = None, - use_cache: bool = True, - allow_types: Optional[List["ParamTypes"]] = None) -> Dependent: +def get_dependent(*, + func: Callable[..., Any], + name: Optional[str] = None, + use_cache: bool = True, + allow_types: Optional[List[Type[Param]]] = None) -> Dependent: signature = get_typed_signature(func) params = signature.parameters - allow_types = allow_types or [ - ParamTypes.BOT, ParamTypes.EVENT, ParamTypes.STATE - ] - dependent = Dependent(func=func, name=name, use_cache=use_cache) + dependent = Dependent(func=func, + name=name, + allow_types=allow_types, + use_cache=use_cache) for param_name, param in params.items(): - if isinstance(param.default, DependsClass): - sub_dependent = get_param_sub_dependent(param=param) + if isinstance(param.default, DependsWrapper): + sub_dependent = get_param_sub_dependent(param=param, + allow_types=allow_types) dependent.dependencies.append(sub_dependent) continue - for allow_type in allow_types: - field_info_class: Type[Param] = allow_type.value - if field_info_class._check(param_name, param): - field_info = field_info_class(param.default) + for allow_type in dependent.allow_types: + if allow_type._check(param_name, param): + field_info = allow_type(param.default) break else: raise ValueError( - f"Unknown parameter {param_name} with type {param.annotation}") + f"Unknown parameter {param_name} for funcction {func} with type {param.annotation}" + ) annotation: Any = Any if param.annotation != param.empty: @@ -91,7 +103,7 @@ def get_dependent( ModelField(name=param_name, type_=annotation, class_validators=None, - model_config=BaseConfig, + model_config=CustomConfig, default=Required, required=True, field_info=field_info)) @@ -102,15 +114,11 @@ def get_dependent( async def solve_dependencies( *, dependent: Dependent, - bot: Bot, - event: Event, - state: T_State, - matcher: Optional["Matcher"] = None, - exception: Optional[Exception] = None, stack: Optional[AsyncExitStack] = None, sub_dependents: Optional[List[Dependent]] = None, dependency_overrides_provider: Optional[Any] = None, dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None, + **params: Any ) -> Tuple[Dict[str, Any], Dict[Callable[..., Any], Any], bool]: values: Dict[str, Any] = {} dependency_cache = dependency_cache or {} @@ -135,18 +143,15 @@ async def solve_dependencies( use_sub_dependant = get_dependent( func=func, name=sub_dependent.name, + allow_types=sub_dependent.allow_types, ) # solve sub dependency with current cache solved_result = await solve_dependencies( dependent=use_sub_dependant, - bot=bot, - event=event, - state=state, - matcher=matcher, dependency_overrides_provider=dependency_overrides_provider, dependency_cache=dependency_cache, - ) + **params) sub_values, sub_dependency_cache, ignored = solved_result if ignored: return values, dependency_cache, True @@ -182,18 +187,13 @@ async def solve_dependencies( field_info = field.field_info assert isinstance(field_info, Param), "Params must be subclasses of Param" - value = field_info._solve(bot=bot, - event=event, - state=state, - matcher=matcher, - exception=exception) + value = field_info._solve(**params) _, errs_ = field.validate(value, values, - loc=(ParamTypes(type(field_info)).name, - field.alias)) + loc=(str(field_info), field.alias)) if errs_: logger.debug( - f"Matcher {matcher} {ParamTypes(type(field_info)).name} " + f"{field_info} " f"type {type(value)} not match depends {dependent.func} " f"annotation {field._type_display()}, ignored") return values, dependency_cache, True @@ -206,11 +206,14 @@ async def solve_dependencies( def Depends(dependency: Optional[Callable[..., Any]] = None, *, use_cache: bool = True) -> Any: - return DependsClass(dependency=dependency, use_cache=use_cache) + """ + :说明: + 参数依赖注入装饰器 -from .params import Param -from .handler import Handler as Handler -from .matcher import Matcher as Matcher -from .matcher import matchers as matchers -from .params import ParamTypes as ParamTypes + :参数: + + * ``dependency: Optional[Callable[..., Any]] = None``: 依赖函数。默认为参数的类型注释。 + * ``use_cache: bool = True``: 是否使用缓存。默认为 ``True``。 + """ + return DependsWrapper(dependency=dependency, use_cache=use_cache) diff --git a/nonebot/processor/models.py b/nonebot/dependencies/models.py similarity index 59% rename from nonebot/processor/models.py rename to nonebot/dependencies/models.py index 06d11890..ca764f9b 100644 --- a/nonebot/processor/models.py +++ b/nonebot/dependencies/models.py @@ -1,11 +1,31 @@ -from typing import Any, List, Callable, Optional +import abc +import inspect +from typing import Any, List, Type, Callable, Optional -from pydantic.fields import ModelField +from pydantic.fields import FieldInfo, ModelField from nonebot.utils import get_name -class Depends: +class Param(FieldInfo, abc.ABC): + + def __repr__(self) -> str: + return f"{self.__class__.__name__}" + + def __str__(self) -> str: + return repr(self) + + @classmethod + @abc.abstractmethod + def _check(cls, name: str, param: inspect.Parameter) -> bool: + raise NotImplementedError + + @abc.abstractmethod + def _solve(self, **kwargs: Any) -> Any: + raise NotImplementedError + + +class DependsWrapper: def __init__(self, dependency: Optional[Callable[..., Any]] = None, @@ -27,11 +47,13 @@ class Dependent: func: Optional[Callable[..., Any]] = None, name: Optional[str] = None, params: Optional[List[ModelField]] = None, + allow_types: Optional[List[Type[Param]]] = None, dependencies: Optional[List["Dependent"]] = None, use_cache: bool = True) -> None: self.func = func self.name = name self.params = params or [] + self.allow_types = allow_types or [] self.dependencies = dependencies or [] self.use_cache = use_cache self.cache_key = self.func diff --git a/nonebot/processor/utils.py b/nonebot/dependencies/utils.py similarity index 57% rename from nonebot/processor/utils.py rename to nonebot/dependencies/utils.py index 6f13e96c..ed08b228 100644 --- a/nonebot/processor/utils.py +++ b/nonebot/dependencies/utils.py @@ -1,6 +1,5 @@ import inspect -from typing import Any, Dict, Type, Tuple, Union, Callable -from typing_extensions import GenericAlias, get_args, get_origin # type: ignore +from typing import Any, Dict, Callable from loguru import logger from pydantic.typing import ForwardRef, evaluate_forwardref @@ -34,26 +33,3 @@ def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, ) return inspect.Parameter.empty return annotation - - -def generic_check_issubclass( - cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], - ...]]) -> bool: - try: - return issubclass(cls, class_or_tuple) - except TypeError: - if get_origin(cls) is Union: - for type_ in get_args(cls): - if not generic_check_issubclass(type_, class_or_tuple): - return False - return True - elif isinstance(cls, GenericAlias): - origin = get_origin(cls) - return bool(origin and issubclass(origin, class_or_tuple)) - raise - - -def generic_get_types(cls: Any) -> Tuple[Type[Any], ...]: - if get_origin(cls) is Union: - return get_args(cls) - return (cls,) diff --git a/nonebot/processor/handler.py b/nonebot/handler.py similarity index 52% rename from nonebot/processor/handler.py rename to nonebot/handler.py index fed8d92c..232f2200 100644 --- a/nonebot/processor/handler.py +++ b/nonebot/handler.py @@ -7,83 +7,94 @@ import asyncio from contextlib import AsyncExitStack -from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Callable, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Type, Callable, Optional -from .models import Depends, Dependent +from nonebot.typing import T_Handler from nonebot.utils import get_name, run_sync -from nonebot.typing import T_State, T_Handler -from . import get_dependent, solve_dependencies, get_parameterless_sub_dependant +from nonebot.dependencies import (Param, Dependent, DependsWrapper, + get_dependent, solve_dependencies, + get_parameterless_sub_dependant) if TYPE_CHECKING: - from .matcher import Matcher - from .params import ParamTypes + from nonebot.matcher import Matcher from nonebot.adapters import Bot, Event class Handler: - """事件处理函数类""" + """事件处理器类。支持依赖注入。""" def __init__(self, func: T_Handler, *, name: Optional[str] = None, - dependencies: Optional[List[Depends]] = None, - allow_types: Optional[List["ParamTypes"]] = None, + dependencies: Optional[List[DependsWrapper]] = None, + allow_types: Optional[List[Type[Param]]] = None, dependency_overrides_provider: Optional[Any] = None): - """装饰事件处理函数以便根据动态参数运行""" - self.func: T_Handler = func + """ + :说明: + + 装饰一个函数为事件处理器。 + + :参数: + + * ``func: T_Handler``: 事件处理函数。 + * ``name: Optional[str]``: 事件处理器名称。默认为函数名。 + * ``dependencies: Optional[List[DependsWrapper]]``: 额外的非参数依赖注入。 + * ``allow_types: Optional[List[Type[Param]]]``: 允许的参数类型。 + * ``dependency_overrides_provider: Optional[Any]``: 依赖注入覆盖提供者。 + """ + self.func = func """ :类型: ``T_Handler`` :说明: 事件处理函数 """ self.name = get_name(func) if name is None else name - self.allow_types = allow_types + """ + :类型: ``str`` + :说明: 事件处理函数名 + """ + self.allow_types = allow_types or [] + """ + :类型: ``List[Type[Param]]`` + :说明: 事件处理器允许的参数类型 + """ self.dependencies = dependencies or [] + """ + :类型: ``List[DependsWrapper]`` + :说明: 事件处理器的额外依赖 + """ self.sub_dependents: Dict[Callable[..., Any], Dependent] = {} if dependencies: for depends in dependencies: - if not depends.dependency: - raise ValueError(f"{depends} has no dependency") - if depends.dependency in self.sub_dependents: - raise ValueError(f"{depends} is already in dependencies") - sub_dependant = get_parameterless_sub_dependant( - depends=depends, allow_types=self.allow_types) - self.sub_dependents[depends.dependency] = sub_dependant + self.cache_dependent(depends) self.dependency_overrides_provider = dependency_overrides_provider self.dependent = get_dependent(func=func, allow_types=self.allow_types) def __repr__(self) -> str: return ( - f"" + f"" ) def __str__(self) -> str: return repr(self) - async def __call__( - self, - matcher: "Matcher", - bot: "Bot", - event: "Event", - state: T_State, - *, - stack: Optional[AsyncExitStack] = None, - dependency_cache: Optional[Dict[Callable[..., Any], - Any]] = None) -> Any: + async def __call__(self, + *, + _stack: Optional[AsyncExitStack] = None, + _dependency_cache: Optional[Dict[Callable[..., Any], + Any]] = None, + **params) -> Any: values, _, ignored = await solve_dependencies( dependent=self.dependent, - bot=bot, - event=event, - state=state, - matcher=matcher, - stack=stack, + stack=_stack, sub_dependents=[ self.sub_dependents[dependency.dependency] # type: ignore for dependency in self.dependencies ], dependency_overrides_provider=self.dependency_overrides_provider, - dependency_cache=dependency_cache) + dependency_cache=_dependency_cache, + **params) if ignored: return @@ -93,24 +104,24 @@ class Handler: else: await run_sync(self.func)(**values) - def cache_dependent(self, dependency: Depends): + def cache_dependent(self, dependency: DependsWrapper): if not dependency.dependency: raise ValueError(f"{dependency} has no dependency") - if (dependency.dependency,) in self.sub_dependents: + if dependency.dependency in self.sub_dependents: raise ValueError(f"{dependency} is already in dependencies") sub_dependant = get_parameterless_sub_dependant( depends=dependency, allow_types=self.allow_types) self.sub_dependents[dependency.dependency] = sub_dependant - def prepend_dependency(self, dependency: Depends): + def prepend_dependency(self, dependency: DependsWrapper): self.cache_dependent(dependency) self.dependencies.insert(0, dependency) - def append_dependency(self, dependency: Depends): + def append_dependency(self, dependency: DependsWrapper): self.cache_dependent(dependency) self.dependencies.append(dependency) - def remove_dependency(self, dependency: Depends): + def remove_dependency(self, dependency: DependsWrapper): if not dependency.dependency: raise ValueError(f"{dependency} has no dependency") if dependency.dependency in self.sub_dependents: diff --git a/nonebot/processor/matcher.py b/nonebot/matcher.py similarity index 96% rename from nonebot/processor/matcher.py rename to nonebot/matcher.py index 4ae3ca21..8856a3cf 100644 --- a/nonebot/processor/matcher.py +++ b/nonebot/matcher.py @@ -12,12 +12,11 @@ from collections import defaultdict from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Callable, NoReturn, Optional) -from .models import Depends -from .handler import Handler from nonebot.rule import Rule -from .params import ParamTypes -from nonebot import get_driver from nonebot.log import logger +from nonebot.handler import Handler +from nonebot import params, get_driver +from nonebot.dependencies import DependsWrapper from nonebot.permission import USER, Permission from nonebot.adapters import (Bot, Event, Message, MessageSegment, MessageTemplate) @@ -155,7 +154,8 @@ class Matcher(metaclass=MatcherMeta): """ HANDLER_PARAM_TYPES = [ - ParamTypes.BOT, ParamTypes.EVENT, ParamTypes.STATE, ParamTypes.MATCHER + params.BotParam, params.EventParam, params.StateParam, + params.MatcherParam ] def __init__(self): @@ -350,9 +350,10 @@ class Matcher(metaclass=MatcherMeta): return func @classmethod - def append_handler(cls, - handler: T_Handler, - dependencies: Optional[List[Depends]] = None) -> Handler: + def append_handler( + cls, + handler: T_Handler, + dependencies: Optional[List[DependsWrapper]] = None) -> Handler: handler_ = Handler(handler, dependencies=dependencies, dependency_overrides_provider=get_driver(), @@ -398,7 +399,7 @@ class Matcher(metaclass=MatcherMeta): def _decorator(func: T_Handler) -> T_Handler: - depend = Depends(_receive) + depend = DependsWrapper(_receive) if cls.handlers and cls.handlers[-1].func is func: func_handler = cls.handlers[-1] @@ -461,8 +462,8 @@ class Matcher(metaclass=MatcherMeta): def _decorator(func: T_Handler) -> T_Handler: - get_depend = Depends(_key_getter) - parser_depend = Depends(_key_parser) + get_depend = DependsWrapper(_key_getter) + parser_depend = DependsWrapper(_key_parser) if cls.handlers and cls.handlers[-1].func is func: func_handler = cls.handlers[-1] @@ -600,7 +601,10 @@ class Matcher(metaclass=MatcherMeta): while self.handlers: handler = self.handlers.pop(0) logger.debug(f"Running handler {handler}") - await handler(self, bot, event, self.state) + await handler(matcher=self, + bot=bot, + event=event, + state=self.state) except RejectedException: self.handlers.insert(0, handler) # type: ignore diff --git a/nonebot/message.py b/nonebot/message.py index a74003fe..fec00687 100644 --- a/nonebot/message.py +++ b/nonebot/message.py @@ -12,8 +12,10 @@ from typing import TYPE_CHECKING, Set, Type from nonebot.log import logger from nonebot.rule import TrieRule +from nonebot.handler import Handler from nonebot.utils import escape_tag -from nonebot.processor import Matcher, matchers +from nonebot import params, get_driver +from nonebot.matcher import Matcher, matchers from nonebot.exception import NoLogException, StopPropagation, IgnoredException from nonebot.typing import (T_State, T_RunPreProcessor, T_RunPostProcessor, T_EventPreProcessor, T_EventPostProcessor) @@ -21,10 +23,19 @@ from nonebot.typing import (T_State, T_RunPreProcessor, T_RunPostProcessor, if TYPE_CHECKING: from nonebot.adapters import Bot, Event -_event_preprocessors: Set[T_EventPreProcessor] = set() -_event_postprocessors: Set[T_EventPostProcessor] = set() -_run_preprocessors: Set[T_RunPreProcessor] = set() -_run_postprocessors: Set[T_RunPostProcessor] = set() +_event_preprocessors: Set[Handler] = set() +_event_postprocessors: Set[Handler] = set() +_run_preprocessors: Set[Handler] = set() +_run_postprocessors: Set[Handler] = set() + +EVENT_PCS_PARAMS = [params.BotParam, params.EventParam, params.StateParam] +RUN_PREPCS_PARAMS = [ + params.MatcherParam, params.BotParam, params.EventParam, params.StateParam +] +RUN_POSTPCS_PARAMS = [ + params.MatcherParam, params.ExceptionParam, params.BotParam, + params.EventParam, params.StateParam +] def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor: @@ -41,7 +52,10 @@ def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor: * ``event: Event``: Event 对象 * ``state: T_State``: 当前 State """ - _event_preprocessors.add(func) + _event_preprocessors.add( + Handler(func, + allow_types=EVENT_PCS_PARAMS, + dependency_overrides_provider=get_driver())) return func @@ -59,7 +73,10 @@ def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor: * ``event: Event``: Event 对象 * ``state: T_State``: 当前事件运行前 State """ - _event_postprocessors.add(func) + _event_postprocessors.add( + Handler(func, + allow_types=EVENT_PCS_PARAMS, + dependency_overrides_provider=get_driver())) return func @@ -78,7 +95,10 @@ def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor: * ``event: Event``: Event 对象 * ``state: T_State``: 当前 State """ - _run_preprocessors.add(func) + _run_preprocessors.add( + Handler(func, + allow_types=RUN_PREPCS_PARAMS, + dependency_overrides_provider=get_driver())) return func @@ -98,7 +118,10 @@ def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor: * ``event: Event``: Event 对象 * ``state: T_State``: 当前 State """ - _run_postprocessors.add(func) + _run_postprocessors.add( + Handler(func, + allow_types=RUN_POSTPCS_PARAMS, + dependency_overrides_provider=get_driver())) return func @@ -136,7 +159,8 @@ async def _run_matcher(Matcher: Type[Matcher], bot: "Bot", event: "Event", matcher = Matcher() coros = list( - map(lambda x: x(matcher, bot, event, state), _run_preprocessors)) + map(lambda x: x(matcher=matcher, bot=bot, event=event, state=state), + _run_preprocessors)) if coros: try: await asyncio.gather(*coros) @@ -162,8 +186,12 @@ async def _run_matcher(Matcher: Type[Matcher], bot: "Bot", event: "Event", exception = e coros = list( - map(lambda x: x(matcher, exception, bot, event, state), - _run_postprocessors)) + map( + lambda x: x(matcher=matcher, + exception=exception, + bot=bot, + event=event, + state=state), _run_postprocessors)) if coros: try: await asyncio.gather(*coros) @@ -208,7 +236,9 @@ async def handle_event(bot: "Bot", event: "Event") -> None: # TODO async with AsyncExitStack() as stack: - coros = list(map(lambda x: x(bot, event, state), _event_preprocessors)) + coros = list( + map(lambda x: x(bot=bot, event=event, state=state), + _event_preprocessors)) if coros: try: if show_log: @@ -255,7 +285,9 @@ async def handle_event(bot: "Bot", event: "Event") -> None: "Error when checking Matcher." ) - coros = list(map(lambda x: x(bot, event, state), _event_postprocessors)) + coros = list( + map(lambda x: x(bot=bot, event=event, state=state), + _event_postprocessors)) if coros: try: if show_log: diff --git a/nonebot/processor/params.py b/nonebot/params.py similarity index 53% rename from nonebot/processor/params.py rename to nonebot/params.py index f777889c..8b644f91 100644 --- a/nonebot/processor/params.py +++ b/nonebot/params.py @@ -1,38 +1,19 @@ -import abc import inspect -from enum import Enum from typing import Any, Dict, Optional -from pydantic.fields import FieldInfo - from nonebot.typing import T_State +from nonebot.dependencies import Param from nonebot.adapters import Bot, Event -from .utils import generic_check_issubclass - - -class Param(FieldInfo, abc.ABC): - - def __repr__(self) -> str: - return f"{self.__class__.__name__}" - - def __str__(self) -> str: - return repr(self) - - @classmethod - @abc.abstractmethod - def _check(cls, name: str, param: inspect.Parameter) -> bool: - raise NotImplementedError - - @abc.abstractmethod - def _solve(self, **kwargs: Any) -> Any: - raise NotImplementedError +from nonebot.utils import generic_check_issubclass class BotParam(Param): @classmethod def _check(cls, name: str, param: inspect.Parameter) -> bool: - return generic_check_issubclass(param.annotation, Bot) + return generic_check_issubclass( + param.annotation, Bot) or (param.annotation == param.empty and + name == "bot") def _solve(self, bot: Bot, **kwargs: Any) -> Any: return bot @@ -42,7 +23,9 @@ class EventParam(Param): @classmethod def _check(cls, name: str, param: inspect.Parameter) -> bool: - return generic_check_issubclass(param.annotation, Event) + return generic_check_issubclass( + param.annotation, Event) or (param.annotation == param.empty and + name == "event") def _solve(self, event: Event, **kwargs: Any) -> Any: return event @@ -52,7 +35,9 @@ class StateParam(Param): @classmethod def _check(cls, name: str, param: inspect.Parameter) -> bool: - return generic_check_issubclass(param.annotation, Dict) + return generic_check_issubclass( + param.annotation, Dict) or (param.annotation == param.empty and + name == "state") def _solve(self, state: T_State, **kwargs: Any) -> Any: return state @@ -62,7 +47,9 @@ class MatcherParam(Param): @classmethod def _check(cls, name: str, param: inspect.Parameter) -> bool: - return generic_check_issubclass(param.annotation, Matcher) + return generic_check_issubclass( + param.annotation, Matcher) or (param.annotation == param.empty and + name == "matcher") def _solve(self, matcher: Optional["Matcher"] = None, **kwargs: Any) -> Any: return matcher @@ -72,7 +59,9 @@ class ExceptionParam(Param): @classmethod def _check(cls, name: str, param: inspect.Parameter) -> bool: - return generic_check_issubclass(param.annotation, Exception) + return generic_check_issubclass( + param.annotation, Exception) or (param.annotation == param.empty and + name == "exception") def _solve(self, exception: Optional[Exception] = None, @@ -80,12 +69,4 @@ class ExceptionParam(Param): return exception -class ParamTypes(Enum): - BOT = BotParam - EVENT = EventParam - STATE = StateParam - MATCHER = MatcherParam - EXCEPTION = ExceptionParam - - -from .matcher import Matcher +from nonebot.matcher import Matcher diff --git a/nonebot/plugin/on.py b/nonebot/plugin/on.py index 40038984..626e4ed1 100644 --- a/nonebot/plugin/on.py +++ b/nonebot/plugin/on.py @@ -4,10 +4,11 @@ import inspect from types import ModuleType from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional +from nonebot.handler import Handler +from nonebot.matcher import Matcher from .manager import _current_plugin from nonebot.adapters import Bot, Event from nonebot.permission import Permission -from nonebot.processor import Handler, Matcher from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_StateFactory from nonebot.rule import (Rule, ArgumentParser, regex, command, keyword, endswith, startswith, shell_command) diff --git a/nonebot/plugin/on.pyi b/nonebot/plugin/on.pyi index fbbf9c90..68e8ad62 100644 --- a/nonebot/plugin/on.pyi +++ b/nonebot/plugin/on.pyi @@ -1,9 +1,10 @@ import re from typing import Set, List, Type, Tuple, Union, Optional +from nonebot.handler import Handler +from nonebot.matcher import Matcher from nonebot.permission import Permission from nonebot.rule import Rule, ArgumentParser -from nonebot.processor import Handler, Matcher from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_StateFactory diff --git a/nonebot/plugin/plugin.py b/nonebot/plugin/plugin.py index bf7773ac..ee5a7c51 100644 --- a/nonebot/plugin/plugin.py +++ b/nonebot/plugin/plugin.py @@ -3,7 +3,7 @@ from dataclasses import field, dataclass from typing import Set, Dict, Type, Optional from .export import Export -from nonebot.processor import Matcher +from nonebot.matcher import Matcher plugins: Dict[str, "Plugin"] = {} """ diff --git a/nonebot/plugins/echo.py b/nonebot/plugins/echo.py index 5e61fd95..440aa4bf 100644 --- a/nonebot/plugins/echo.py +++ b/nonebot/plugins/echo.py @@ -3,14 +3,14 @@ from functools import reduce from nonebot.rule import to_me from nonebot.plugin import on_command from nonebot.permission import SUPERUSER -from nonebot.adapters.cqhttp import (Bot, Message, MessageEvent, MessageSegment, +from nonebot.adapters.cqhttp import (Message, MessageEvent, MessageSegment, unescape) say = on_command("say", to_me(), permission=SUPERUSER) @say.handle() -async def say_unescape(bot: Bot, event: MessageEvent): +async def say_unescape(event: MessageEvent): def _unescape(message: Message, segment: MessageSegment): if segment.is_text(): @@ -18,12 +18,12 @@ async def say_unescape(bot: Bot, event: MessageEvent): return message.append(segment) message = reduce(_unescape, event.get_message(), Message()) # type: ignore - await bot.send(message=message, event=event) + await say.send(message=message) echo = on_command("echo", to_me()) @echo.handle() -async def echo_escape(bot: Bot, event: MessageEvent): - await bot.send(message=event.get_message(), event=event) +async def echo_escape(event: MessageEvent): + await say.send(message=event.get_message()) diff --git a/nonebot/plugins/single_session.py b/nonebot/plugins/single_session.py index 1b6d4116..81a9a9d1 100644 --- a/nonebot/plugins/single_session.py +++ b/nonebot/plugins/single_session.py @@ -1,7 +1,7 @@ from typing import Dict, Optional from nonebot.typing import T_State -from nonebot.processor import Matcher +from nonebot.matcher import Matcher from nonebot.adapters import Bot, Event from nonebot.message import (IgnoredException, run_preprocessor, run_postprocessor) @@ -10,7 +10,7 @@ _running_matcher: Dict[str, int] = {} @run_preprocessor -async def preprocess(matcher: Matcher, bot: Bot, event: Event, state: T_State): +async def preprocess(event: Event): try: session_id = event.get_session_id() except Exception: @@ -24,8 +24,7 @@ async def preprocess(matcher: Matcher, bot: Bot, event: Event, state: T_State): @run_postprocessor -async def postprocess(matcher: Matcher, exception: Optional[Exception], - bot: Bot, event: Event, state: T_State): +async def postprocess(event: Event): try: session_id = event.get_session_id() except Exception: diff --git a/nonebot/typing.py b/nonebot/typing.py index 053fcff7..3725b301 100644 --- a/nonebot/typing.py +++ b/nonebot/typing.py @@ -22,7 +22,7 @@ from typing import (TYPE_CHECKING, Any, Dict, Union, TypeVar, Callable, NoReturn, Optional, Awaitable) if TYPE_CHECKING: - from nonebot.processor import Matcher + from nonebot.matcher import Matcher from nonebot.adapters import Bot, Event from nonebot.permission import Permission @@ -90,7 +90,7 @@ T_CalledAPIHook = Callable[ ``bot.call_api`` 后执行的函数,参数分别为 bot, exception, api, data, result """ -T_EventPreProcessor = Callable[["Bot", "Event", T_State], Awaitable[None]] +T_EventPreProcessor = Callable[..., Awaitable[None]] """ :类型: ``Callable[[Bot, Event, T_State], Awaitable[None]]`` @@ -98,7 +98,7 @@ T_EventPreProcessor = Callable[["Bot", "Event", T_State], Awaitable[None]] 事件预处理函数 EventPreProcessor 类型 """ -T_EventPostProcessor = Callable[["Bot", "Event", T_State], Awaitable[None]] +T_EventPostProcessor = Callable[..., Awaitable[None]] """ :类型: ``Callable[[Bot, Event, T_State], Awaitable[None]]`` @@ -106,8 +106,7 @@ T_EventPostProcessor = Callable[["Bot", "Event", T_State], Awaitable[None]] 事件预处理函数 EventPostProcessor 类型 """ -T_RunPreProcessor = Callable[["Matcher", "Bot", "Event", T_State], - Awaitable[None]] +T_RunPreProcessor = Callable[..., Awaitable[None]] """ :类型: ``Callable[[Matcher, Bot, Event, T_State], Awaitable[None]]`` @@ -115,8 +114,7 @@ T_RunPreProcessor = Callable[["Matcher", "Bot", "Event", T_State], 事件响应器运行前预处理函数 RunPreProcessor 类型 """ -T_RunPostProcessor = Callable[ - ["Matcher", Optional[Exception], "Bot", "Event", T_State], Awaitable[None]] +T_RunPostProcessor = Callable[..., Awaitable[None]] """ :类型: ``Callable[[Matcher, Optional[Exception], Bot, Event, T_State], Awaitable[None]]`` diff --git a/nonebot/utils.py b/nonebot/utils.py index 2786ef1d..66e905e6 100644 --- a/nonebot/utils.py +++ b/nonebot/utils.py @@ -4,10 +4,11 @@ import asyncio import inspect import dataclasses from functools import wraps, partial -from typing_extensions import ParamSpec from contextlib import asynccontextmanager -from typing import (Any, TypeVar, Callable, Optional, Awaitable, AsyncGenerator, - ContextManager) +from typing_extensions import GenericAlias # type: ignore +from typing_extensions import ParamSpec, get_args, get_origin +from typing import (Any, Type, Tuple, Union, TypeVar, Callable, Optional, + Awaitable, AsyncGenerator, ContextManager) from nonebot.log import logger from nonebot.typing import overrides @@ -34,6 +35,24 @@ def escape_tag(s: str) -> str: return re.sub(r"\s]*)>", r"\\\g<0>", s) +def generic_check_issubclass( + cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], + ...]]) -> bool: + try: + return issubclass(cls, class_or_tuple) + except TypeError: + if get_origin(cls) is Union: + for type_ in get_args(cls): + if type_ is not type(None) and not generic_check_issubclass( + type_, class_or_tuple): + return False + return True + elif isinstance(cls, GenericAlias): + origin = get_origin(cls) + return bool(origin and issubclass(origin, class_or_tuple)) + raise + + def is_coroutine_callable(func: Callable[..., Any]) -> bool: if inspect.isroutine(func): return inspect.iscoroutinefunction(func) diff --git a/tests/test_plugins/test_depends.py b/tests/test_plugins/test_depends.py index 77580374..c604169b 100644 --- a/tests/test_plugins/test_depends.py +++ b/tests/test_plugins/test_depends.py @@ -1,6 +1,6 @@ from nonebot import on_command from nonebot.log import logger -from nonebot.processor import Depends +from nonebot.dependencies import Depends test = on_command("123") diff --git a/tests/test_plugins/test_processor.py b/tests/test_plugins/test_processor.py index 5eb6b70e..438dda96 100644 --- a/tests/test_plugins/test_processor.py +++ b/tests/test_plugins/test_processor.py @@ -1,15 +1,15 @@ +from nonebot.adapters import Event from nonebot.typing import T_State -from nonebot.processor import Matcher -from nonebot.adapters import Bot, Event +from nonebot.matcher import Matcher from nonebot.message import run_preprocessor, event_preprocessor @event_preprocessor -async def handle(bot: Bot, event: Event, state: T_State): +async def handle(event: Event, state: T_State): state["preprocessed"] = True print(type(event), event) @run_preprocessor -async def run(matcher: Matcher, bot: Bot, event: Event, state: T_State): +async def run(matcher: Matcher): print(matcher) From dc31afbd186beec2250faa975de2d0561bdaccf3 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Wed, 17 Nov 2021 00:27:58 +0800 Subject: [PATCH 09/21] :wheelchair: improve command rule types --- nonebot/plugin/on.py | 14 +++--- nonebot/rule.py | 103 +++++++++++++++++++------------------------ 2 files changed, 52 insertions(+), 65 deletions(-) diff --git a/nonebot/plugin/on.py b/nonebot/plugin/on.py index 626e4ed1..1b2cf16e 100644 --- a/nonebot/plugin/on.py +++ b/nonebot/plugin/on.py @@ -4,14 +4,14 @@ import inspect from types import ModuleType from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional +from nonebot.adapters import Event from nonebot.handler import Handler from nonebot.matcher import Matcher from .manager import _current_plugin -from nonebot.adapters import Bot, Event from nonebot.permission import Permission from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_StateFactory -from nonebot.rule import (Rule, ArgumentParser, regex, command, keyword, - endswith, startswith, shell_command) +from nonebot.rule import (PREFIX_KEY, RAW_CMD_KEY, Rule, ArgumentParser, regex, + command, keyword, endswith, startswith, shell_command) def _store_matcher(matcher: Type[Matcher]) -> None: @@ -373,16 +373,16 @@ def on_command(cmd: Union[str, Tuple[str, ...]], - ``Type[Matcher]`` """ - async def _strip_cmd(bot: Bot, event: Event, state: T_State): + async def _strip_cmd(event: Event, state: T_State): message = event.get_message() if len(message) < 1: return segment = message.pop(0) segment_text = str(segment).lstrip() - if not segment_text.startswith(state["_prefix"]["raw_command"]): + if not segment_text.startswith(state[PREFIX_KEY][RAW_CMD_KEY]): return new_message = message.__class__( - segment_text[len(state["_prefix"]["raw_command"]):].lstrip()) + segment_text[len(state[PREFIX_KEY][RAW_CMD_KEY]):].lstrip()) for new_segment in reversed(new_message): message.insert(0, new_segment) @@ -430,7 +430,7 @@ def on_shell_command(cmd: Union[str, Tuple[str, ...]], - ``Type[Matcher]`` """ - async def _strip_cmd(bot: Bot, event: Event, state: T_State): + async def _strip_cmd(event: Event, state: T_State): message = event.get_message() segment = message.pop(0) new_message = message.__class__( diff --git a/nonebot/rule.py b/nonebot/rule.py index 40fc1b43..7ac0fe8d 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -14,18 +14,35 @@ import shlex import asyncio from itertools import product from argparse import Namespace +from typing_extensions import TypedDict from argparse import ArgumentParser as ArgParser -from typing import (Any, Dict, Tuple, Union, Callable, NoReturn, Optional, - Sequence, Awaitable) +from typing import (Any, Tuple, Union, Callable, NoReturn, Optional, Sequence, + Awaitable) from pygtrie import CharTrie from nonebot import get_driver from nonebot.log import logger from nonebot.utils import run_sync -from nonebot.adapters import Bot, Event from nonebot.exception import ParserExit from nonebot.typing import T_State, T_RuleChecker +from nonebot.adapters import Bot, Event, MessageSegment + +PREFIX_KEY = "_prefix" +SUFFIX_KEY = "_suffix" +CMD_KEY = "command" +RAW_CMD_KEY = "raw_command" +CMD_RESULT = TypedDict("CMD_RESULT", { + "command": Optional[Tuple[str, ...]], + "raw_command": Optional[str] +}) + +SHELL_ARGS = "_args" +SHELL_ARGV = "_argv" + +REGEX_MATCHED = "_matched" +REGEX_GROUP = "_matched_groups" +REGEX_DICT = "_matched_dict" class Rule: @@ -121,57 +138,27 @@ class TrieRule: @classmethod def get_value(cls, bot: Bot, event: Event, - state: T_State) -> Tuple[Dict[str, Any], Dict[str, Any]]: + state: T_State) -> Tuple[CMD_RESULT, CMD_RESULT]: + prefix = CMD_RESULT(command=None, raw_command=None) + suffix = CMD_RESULT(command=None, raw_command=None) + state[PREFIX_KEY] = prefix + state[SUFFIX_KEY] = suffix if event.get_type() != "message": - state["_prefix"] = {"raw_command": None, "command": None} - state["_suffix"] = {"raw_command": None, "command": None} - return { - "raw_command": None, - "command": None - }, { - "raw_command": None, - "command": None - } + return prefix, suffix - prefix = None - suffix = None message = event.get_message() - message_seg = message[0] + message_seg: MessageSegment = message[0] if message_seg.is_text(): - prefix = cls.prefix.longest_prefix(str(message_seg).lstrip()) - message_seg_r = message[-1] + pf = cls.prefix.longest_prefix(str(message_seg).lstrip()) + prefix[RAW_CMD_KEY] = pf.key + prefix[CMD_KEY] = pf.value + message_seg_r: MessageSegment = message[-1] if message_seg_r.is_text(): - suffix = cls.suffix.longest_prefix( - str(message_seg_r).rstrip()[::-1]) + sf = cls.suffix.longest_prefix(str(message_seg_r).rstrip()[::-1]) + suffix[RAW_CMD_KEY] = sf.key + suffix[CMD_KEY] = sf.value - state["_prefix"] = { - "raw_command": prefix.key, - "command": prefix.value - } if prefix else { - "raw_command": None, - "command": None - } - state["_suffix"] = { - "raw_command": suffix.key, - "command": suffix.value - } if suffix else { - "raw_command": None, - "command": None - } - - return ({ - "raw_command": prefix.key, - "command": prefix.value - } if prefix else { - "raw_command": None, - "command": None - }, { - "raw_command": suffix.key, - "command": suffix.value - } if suffix else { - "raw_command": None, - "command": None - }) + return prefix, suffix def startswith(msg: Union[str, Tuple[str, ...]], @@ -288,7 +275,7 @@ def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule: TrieRule.add_prefix(f"{start}{sep.join(command)}", command) async def _command(bot: Bot, event: Event, state: T_State) -> bool: - return state["_prefix"]["command"] in commands + return state[PREFIX_KEY][CMD_KEY] in commands return Rule(_command) @@ -374,17 +361,17 @@ def shell_command(*cmds: Union[str, Tuple[str, ...]], TrieRule.add_prefix(f"{start}{sep.join(command)}", command) async def _shell_command(bot: Bot, event: Event, state: T_State) -> bool: - if state["_prefix"]["command"] in commands: + if state[PREFIX_KEY][CMD_KEY] in commands: message = str(event.get_message()) - strip_message = message[len(state["_prefix"]["raw_command"] + strip_message = message[len(state[PREFIX_KEY][RAW_CMD_KEY] ):].lstrip() - state["argv"] = shlex.split(strip_message) + state[SHELL_ARGV] = shlex.split(strip_message) if parser: try: - args = parser.parse_args(state["argv"]) - state["args"] = args + args = parser.parse_args(state[SHELL_ARGV]) + state[SHELL_ARGS] = args except ParserExit as e: - state["args"] = e + state[SHELL_ARGS] = e return True else: return False @@ -418,9 +405,9 @@ def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule: return False matched = pattern.search(str(event.get_message())) if matched: - state["_matched"] = matched.group() - state["_matched_groups"] = matched.groups() - state["_matched_dict"] = matched.groupdict() + state[REGEX_MATCHED] = matched.group() + state[REGEX_GROUP] = matched.groups() + state[REGEX_DICT] = matched.groupdict() return True else: return False From 36d93b8a3acae88ff685ee4139750c584686e711 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Wed, 17 Nov 2021 19:52:12 +0800 Subject: [PATCH 10/21] :arrow_up: update dependencies --- .github/workflows/build_docs.yml | 2 - pages/changelog.md | 4 +- poetry.lock | 257 +++++++++++++++++-------------- pyproject.toml | 4 +- 4 files changed, 145 insertions(+), 122 deletions(-) diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index bd20695a..ed960d53 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -25,8 +25,6 @@ jobs: with: path: ~/.cache/pypoetry/virtualenvs key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} - restore-keys: | - ${{ runner.os }}-poetry- - name: Set up dependencies run: | diff --git a/pages/changelog.md b/pages/changelog.md index 76c9f1c5..a8565f0c 100644 --- a/pages/changelog.md +++ b/pages/changelog.md @@ -4,11 +4,13 @@ sidebar: auto # 更新日志 -## v2.0.0a17 +## v2.0.0b1 - 新增 `MessageTemplate` 对于 `str` 普通模板的支持 - 移除插件加载的 `NameSpace` 模式 - 修改 toml 加载插件时的键名为 `tool.nonebot` 以符合规范 +- 新增 Handler 依赖注入支持 +- 统一 `Processor`, `Rule`, `Permission` 使用 `Handler` ## v2.0.0a16 diff --git a/poetry.lock b/poetry.lock index 91c140fc..b3568e16 100644 --- a/poetry.lock +++ b/poetry.lock @@ -33,7 +33,7 @@ python-versions = ">=3.6,<4.0" [[package]] name = "aiohttp" -version = "3.8.0" +version = "3.8.1" description = "Async http client/server framework (asyncio)" category = "main" optional = true @@ -297,7 +297,7 @@ python-versions = ">=3.5" [[package]] name = "httpcore" -version = "0.13.7" +version = "0.14.2" description = "A minimal low-level HTTP client." category = "main" optional = false @@ -305,6 +305,7 @@ python-versions = ">=3.6" [package.dependencies] anyio = ">=3.0.0,<4.0.0" +certifi = "*" h11 = ">=0.11,<0.13" sniffio = ">=1.0.0,<2.0.0" @@ -324,7 +325,7 @@ test = ["Cython (==0.29.22)"] [[package]] name = "httpx" -version = "0.20.0" +version = "0.21.1" description = "The next generation HTTP client." category = "main" optional = false @@ -334,7 +335,7 @@ python-versions = ">=3.6" certifi = "*" charset-normalizer = "*" h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""} -httpcore = ">=0.13.3,<0.14.0" +httpcore = ">=0.14.0,<0.15.0" rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} sniffio = "*" @@ -771,7 +772,7 @@ python-versions = ">=3.5" [[package]] name = "snowballstemmer" -version = "2.1.0" +version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." category = "dev" optional = false @@ -828,7 +829,7 @@ yapf = "*" type = "git" url = "https://github.com/nonebot/sphinx-markdown-builder.git" reference = "master" -resolved_reference = "7a8c8a66dfe42436b4584d1d13f5d0127fc83301" +resolved_reference = "2204923f5938a8f7354c6a69ed58079edd180a43" [[package]] name = "sphinxcontrib-applehelp" @@ -934,11 +935,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.0.0" +description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "unify" @@ -1019,7 +1020,7 @@ python-versions = ">=3.5" [[package]] name = "websockets" -version = "10.0" +version = "10.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" category = "main" optional = false @@ -1099,7 +1100,7 @@ quart = ["Quart"] [metadata] lock-version = "1.1" python-versions = "^3.7.3" -content-hash = "81edd95f4289e55d7cfe632664c930846bde723cb8fa0359fa1e18474853f454" +content-hash = "537c91f98fd6598dbce8c2942530f18dee0858a896b6f393a684252a77dc76c6" [metadata.files] aiocache = [ @@ -1115,78 +1116,78 @@ aiofiles = [ {file = "aiofiles-0.7.0.tar.gz", hash = "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4"}, ] aiohttp = [ - {file = "aiohttp-3.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:48f218a5257b6bc16bcf26a91d97ecea0c7d29c811a90d965f3dd97c20f016d6"}, - {file = "aiohttp-3.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2fee4d656a7cc9ab47771b2a9e8fad8a9a33331c1b59c3057ecf0ac858f5bfe"}, - {file = "aiohttp-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:688a1eb8c1a5f7e795c7cb67e0fe600194e6723ba35f138dfae0db20c0cb8f94"}, - {file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ba09bb3dcb0b7ec936a485db2b64be44fe14cdce0a5eac56f50e55da3627385"}, - {file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7715daf84f10bcebc083ad137e3eced3e1c8e7fa1f096ade9a8d02b08f0d91c"}, - {file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e3f81fbbc170418e22918a9585fd7281bbc11d027064d62aa4b507552c92671"}, - {file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1fa9f50aa1f114249b7963c98e20dc35c51be64096a85bc92433185f331de9cc"}, - {file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8a50150419b741ee048b53146c39c47053f060cb9d98e78be08fdbe942eaa3c4"}, - {file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a84c335337b676d832c1e2bc47c3a97531b46b82de9f959dafb315cbcbe0dfcd"}, - {file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88d4917c30fcd7f6404fb1dc713fa21de59d3063dcc048f4a8a1a90e6bbbd739"}, - {file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b76669b7c058b8020b11008283c3b8e9c61bfd978807c45862956119b77ece45"}, - {file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:84fe1732648c1bc303a70faa67cbc2f7f2e810c8a5bca94f6db7818e722e4c0a"}, - {file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:730b7c2b7382194d9985ffdc32ab317e893bca21e0665cb1186bdfbb4089d990"}, - {file = "aiohttp-3.8.0-cp310-cp310-win32.whl", hash = "sha256:0a96473a1f61d7920a9099bc8e729dc8282539d25f79c12573ee0fdb9c8b66a8"}, - {file = "aiohttp-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:764c7c6aa1f78bd77bd9674fc07d1ec44654da1818d0eef9fb48aa8371a3c847"}, - {file = "aiohttp-3.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9951c2696c4357703001e1fe6edc6ae8e97553ac630492ea1bf64b429cb712a3"}, - {file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0af379221975054162959e00daf21159ff69a712fc42ed0052caddbd70d52ff4"}, - {file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9689af0f0a89e5032426c143fa3683b0451f06c83bf3b1e27902bd33acfae769"}, - {file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe4a327da0c6b6e59f2e474ae79d6ee7745ac3279fd15f200044602fa31e3d79"}, - {file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ecb314e59bedb77188017f26e6b684b1f6d0465e724c3122a726359fa62ca1ba"}, - {file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5399a44a529083951b55521cf4ecbf6ad79fd54b9df57dbf01699ffa0549fc9"}, - {file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:09754a0d5eaab66c37591f2f8fac8f9781a5f61d51aa852a3261c4805ca6b984"}, - {file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:adf0cb251b1b842c9dee5cfcdf880ba0aae32e841b8d0e6b6feeaef002a267c5"}, - {file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a4759e85a191de58e0ea468ab6fd9c03941986eee436e0518d7a9291fab122c8"}, - {file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:28369fe331a59d80393ec82df3d43307c7461bfaf9217999e33e2acc7984ff7c"}, - {file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2f44d1b1c740a9e2275160d77c73a11f61e8a916191c572876baa7b282bcc934"}, - {file = "aiohttp-3.8.0-cp36-cp36m-win32.whl", hash = "sha256:e27cde1e8d17b09730801ce97b6e0c444ba2a1f06348b169fd931b51d3402f0d"}, - {file = "aiohttp-3.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:15a660d06092b7c92ed17c1dbe6c1eab0a02963992d60e3e8b9d5fa7fa81f01e"}, - {file = "aiohttp-3.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:257f4fad1714d26d562572095c8c5cd271d5a333252795cb7a002dca41fdbad7"}, - {file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6074a3b2fa2d0c9bf0963f8dfc85e1e54a26114cc8594126bc52d3fa061c40e"}, - {file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a315ceb813208ef32bdd6ec3a85cbe3cb3be9bbda5fd030c234592fa9116993"}, - {file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a52b141ff3b923a9166595de6e3768a027546e75052ffba267d95b54267f4ab"}, - {file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a038cb1e6e55b26bb5520ccffab7f539b3786f5553af2ee47eb2ec5cbd7084e"}, - {file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98b1ea2763b33559dd9ec621d67fc17b583484cb90735bfb0ec3614c17b210e4"}, - {file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9e8723c3256641e141cd18f6ce478d54a004138b9f1a36e41083b36d9ecc5fc5"}, - {file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:14a6f026eca80dfa3d52e86be89feb5cd878f6f4a6adb34457e2c689fd85229b"}, - {file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c62d4791a8212c885b97a63ef5f3974b2cd41930f0cd224ada9c6ee6654f8150"}, - {file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:90a97c2ed2830e7974cbe45f0838de0aefc1c123313f7c402e21c29ec063fbb4"}, - {file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dcc4d5dd5fba3affaf4fd08f00ef156407573de8c63338787614ccc64f96b321"}, - {file = "aiohttp-3.8.0-cp37-cp37m-win32.whl", hash = "sha256:de42f513ed7a997bc821bddab356b72e55e8396b1b7ba1bf39926d538a76a90f"}, - {file = "aiohttp-3.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7d76e8a83396e06abe3df569b25bd3fc88bf78b7baa2c8e4cf4aaf5983af66a3"}, - {file = "aiohttp-3.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d79174d96446a02664e2bffc95e7b6fa93b9e6d8314536c5840dff130d0878b"}, - {file = "aiohttp-3.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a6551057a846bf72c7a04f73de3fcaca269c0bd85afe475ceb59d261c6a938c"}, - {file = "aiohttp-3.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:871d4fdc56288caa58b1094c20f2364215f7400411f76783ea19ad13be7c8e19"}, - {file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ba08a71caa42eef64357257878fb17f3fba3fba6e81a51d170e32321569e079"}, - {file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51f90dabd9933b1621260b32c2f0d05d36923c7a5a909eb823e429dba0fd2f3e"}, - {file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f348ebd20554e8bc26e8ef3ed8a134110c0f4bf015b3b4da6a4ddf34e0515b19"}, - {file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d5f8c04574efa814a24510122810e3a3c77c0552f9f6ff65c9862f1f046be2c3"}, - {file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ecffdc748d3b40dd3618ede0170e4f5e1d3c9647cfb410d235d19e62cb54ee0"}, - {file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:577cc2c7b807b174814dac2d02e673728f2e46c7f90ceda3a70ea4bb6d90b769"}, - {file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6b79f6c31e68b6dafc0317ec453c83c86dd8db1f8f0c6f28e97186563fca87a0"}, - {file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2bdd655732e38b40f8a8344d330cfae3c727fb257585df923316aabbd489ccb8"}, - {file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:63fa57a0708573d3c059f7b5527617bd0c291e4559298473df238d502e4ab98c"}, - {file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d3f90ee275b1d7c942e65b5c44c8fb52d55502a0b9a679837d71be2bd8927661"}, - {file = "aiohttp-3.8.0-cp38-cp38-win32.whl", hash = "sha256:fa818609357dde5c4a94a64c097c6404ad996b1d38ca977a72834b682830a722"}, - {file = "aiohttp-3.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:097ecf52f6b9859b025c1e36401f8aa4573552e887d1b91b4b999d68d0b5a3b3"}, - {file = "aiohttp-3.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:be03a7483ad9ea60388f930160bb3728467dd0af538aa5edc60962ee700a0bdc"}, - {file = "aiohttp-3.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:78d51e35ed163783d721b6f2ce8ce3f82fccfe471e8e50a10fba13a766d31f5a"}, - {file = "aiohttp-3.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bda75d73e7400e81077b0910c9a60bf9771f715420d7e35fa7739ae95555f195"}, - {file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:707adc30ea6918fba725c3cb3fe782d271ba352b22d7ae54a7f9f2e8a8488c41"}, - {file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f58aa995b905ab82fe228acd38538e7dc1509e01508dcf307dad5046399130f"}, - {file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c996eb91bfbdab1e01e2c02e7ff678c51e2b28e3a04e26e41691991cc55795"}, - {file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d6a1a66bb8bac9bc2892c2674ea363486bfb748b86504966a390345a11b1680e"}, - {file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dafc01a32b4a1d7d3ef8bfd3699406bb44f7b2e0d3eb8906d574846e1019b12f"}, - {file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:949a605ef3907254b122f845baa0920407080cdb1f73aa64f8d47df4a7f4c4f9"}, - {file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0d7b056fd3972d353cb4bc305c03f9381583766b7f8c7f1c44478dba69099e33"}, - {file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f1d39a744101bf4043fa0926b3ead616607578192d0a169974fb5265ab1e9d2"}, - {file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:67ca7032dfac8d001023fadafc812d9f48bf8a8c3bb15412d9cdcf92267593f4"}, - {file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cb751ef712570d3bda9a73fd765ff3e1aba943ec5d52a54a0c2e89c7eef9da1e"}, - {file = "aiohttp-3.8.0-cp39-cp39-win32.whl", hash = "sha256:6d3e027fe291b77f6be9630114a0200b2c52004ef20b94dc50ca59849cd623b3"}, - {file = "aiohttp-3.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:3c5e9981e449d54308c6824f172ec8ab63eb9c5f922920970249efee83f7e919"}, - {file = "aiohttp-3.8.0.tar.gz", hash = "sha256:d3b19d8d183bcfd68b25beebab8dc3308282fe2ca3d6ea3cb4cd101b3c279f8d"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"}, + {file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"}, + {file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"}, + {file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"}, + {file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"}, + {file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"}, + {file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"}, + {file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"}, + {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, + {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, ] aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, @@ -1470,8 +1471,8 @@ html2text = [ {file = "html2text-2020.1.16.tar.gz", hash = "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb"}, ] httpcore = [ - {file = "httpcore-0.13.7-py3-none-any.whl", hash = "sha256:369aa481b014cf046f7067fddd67d00560f2f00426e79569d99cb11245134af0"}, - {file = "httpcore-0.13.7.tar.gz", hash = "sha256:036f960468759e633574d7c121afba48af6419615d36ab8ede979f1ad6276fa3"}, + {file = "httpcore-0.14.2-py3-none-any.whl", hash = "sha256:47d7c8f755719d4a57be0b6e022897e9e963bf9ce4b15b9cc006a38a1cfa2932"}, + {file = "httpcore-0.14.2.tar.gz", hash = "sha256:ff8f8b9434ec4823f95a30596fbe78039913e706d3e598b0b8955b1e1828e093"}, ] httptools = [ {file = "httptools-0.2.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:79dbc21f3612a78b28384e989b21872e2e3cf3968532601544696e4ed0007ce5"}, @@ -1491,8 +1492,8 @@ httptools = [ {file = "httptools-0.2.0.tar.gz", hash = "sha256:94505026be56652d7a530ab03d89474dc6021019d6b8682281977163b3471ea0"}, ] httpx = [ - {file = "httpx-0.20.0-py3-none-any.whl", hash = "sha256:33af5aad9bdc82ef1fc89219c1e36f5693bf9cd0ebe330884df563445682c0f8"}, - {file = "httpx-0.20.0.tar.gz", hash = "sha256:09606d630f070d07f9ff28104fbcea429ea0014c1e89ac90b4d8de8286c40e7b"}, + {file = "httpx-0.21.1-py3-none-any.whl", hash = "sha256:208e5ef2ad4d105213463cfd541898ed9d11851b346473539a8425e644bb7c66"}, + {file = "httpx-0.21.1.tar.gz", hash = "sha256:02af20df486b78892a614a7ccd4e4e86a5409ec4981ab0e422c579a887acad83"}, ] hypercorn = [ {file = "Hypercorn-0.12.0-py3-none-any.whl", hash = "sha256:485a03dc171549dd802c5a2d4cce2d46daf077fbc06c7db90e0862ebc1bd07c9"}, @@ -1867,8 +1868,8 @@ sniffio = [ {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, ] snowballstemmer = [ - {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, - {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] sphinx = [ {file = "Sphinx-4.3.0-py3-none-any.whl", hash = "sha256:7e2b30da5f39170efcd95c6270f07669d623c276521fee27ad6c380f49d2bf5b"}, @@ -1912,9 +1913,8 @@ tomlkit = [ {file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, + {file = "typing_extensions-4.0.0-py3-none-any.whl", hash = "sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9"}, + {file = "typing_extensions-4.0.0.tar.gz", hash = "sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed"}, ] unify = [ {file = "unify-0.5.tar.gz", hash = "sha256:8ddce812b2457212b7598fe574c9e6eb3ad69710f445391338270c7f8a71723c"}, @@ -1953,31 +1953,54 @@ watchgod = [ {file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"}, ] websockets = [ - {file = "websockets-10.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cd8c6f2ec24aedace251017bc7a414525171d4e6578f914acab9349362def4da"}, - {file = "websockets-10.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1f6b814cff6aadc4288297cb3a248614829c6e4ff5556593c44a115e9dd49939"}, - {file = "websockets-10.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:01db0ecd1a0ca6702d02a5ed40413e18b7d22f94afb3bbe0d323bac86c42c1c8"}, - {file = "websockets-10.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:82b17524b1ce6ae7f7dd93e4d18e9b9474071e28b65dbf1dfe9b5767778db379"}, - {file = "websockets-10.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:8bbf8660c3f833ddc8b1afab90213f2e672a9ddac6eecb3cde968e6b2807c1c7"}, - {file = "websockets-10.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b8176deb6be540a46695960a765a77c28ac8b2e3ef2ec95d50a4f5df901edb1c"}, - {file = "websockets-10.0-cp37-cp37m-win32.whl", hash = "sha256:706e200fc7f03bed99ad0574cd1ea8b0951477dd18cc978ccb190683c69dba76"}, - {file = "websockets-10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b2600e01c7ca6f840c42c747ffbe0254f319594ed108db847eb3d75f4aacb80"}, - {file = "websockets-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:085bb8a6e780d30eaa1ba48ac7f3a6707f925edea787cfb761ce5a39e77ac09b"}, - {file = "websockets-10.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9a4d889162bd48588e80950e07fa5e039eee9deb76a58092e8c3ece96d7ef537"}, - {file = "websockets-10.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b4ade7569b6fd17912452f9c3757d96f8e4044016b6d22b3b8391e641ca50456"}, - {file = "websockets-10.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:2a43072e434c041a99f2e1eb9b692df0232a38c37c61d00e9f24db79474329e4"}, - {file = "websockets-10.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7f79f02c7f9a8320aff7d3321cd1c7e3a7dbc15d922ac996cca827301ee75238"}, - {file = "websockets-10.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:1ac35426fe3e7d3d0fac3d63c8965c76ed67a8fd713937be072bf0ce22808539"}, - {file = "websockets-10.0-cp38-cp38-win32.whl", hash = "sha256:ff59c6bdb87b31f7e2d596f09353d5a38c8c8ff571b0e2238e8ee2d55ad68465"}, - {file = "websockets-10.0-cp38-cp38-win_amd64.whl", hash = "sha256:d67646ddd17a86117ae21c27005d83c1895c0cef5d7be548b7549646372f868a"}, - {file = "websockets-10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82bd921885231f4a30d9bc550552495b3fc36b1235add6d374e7c65c3babd805"}, - {file = "websockets-10.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7d2e12e4f901f1bc062dfdf91831712c4106ed18a9a4cdb65e2e5f502124ca37"}, - {file = "websockets-10.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:71358c7816e2762f3e4af3adf0040f268e219f5a38cb3487a9d0fc2e554fef6a"}, - {file = "websockets-10.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:fe83b3ec9ef34063d86dfe1029160a85f24a5a94271036e5714a57acfdd089a1"}, - {file = "websockets-10.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:eb282127e9c136f860c6068a4fba5756eb25e755baffb5940b6f1eae071928b2"}, - {file = "websockets-10.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:62160772314920397f9d219147f958b33fa27a12c662d4455c9ccbba9a07e474"}, - {file = "websockets-10.0-cp39-cp39-win32.whl", hash = "sha256:e42a1f1e03437b017af341e9bbfdc09252cd48ef32a8c3c3ead769eab3b17368"}, - {file = "websockets-10.0-cp39-cp39-win_amd64.whl", hash = "sha256:c5880442f5fc268f1ef6d37b2c152c114deccca73f48e3a8c48004d2f16f4567"}, - {file = "websockets-10.0.tar.gz", hash = "sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002"}, + {file = "websockets-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:38db6e2163b021642d0a43200ee2dec8f4980bdbda96db54fde72b283b54cbfc"}, + {file = "websockets-10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1b60fd297adb9fc78375778a5220da7f07bf54d2a33ac781319650413fc6a60"}, + {file = "websockets-10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3477146d1f87ead8df0f27e8960249f5248dceb7c2741e8bbec9aa5338d0c053"}, + {file = "websockets-10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb01ea7b5f52e7125bdc3c5807aeaa2d08a0553979cf2d96a8b7803ea33e15e7"}, + {file = "websockets-10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9fd62c6dc83d5d35fb6a84ff82ec69df8f4657fff05f9cd6c7d9bec0dd57f0f6"}, + {file = "websockets-10.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bbf080f3892ba1dc8838786ec02899516a9d227abe14a80ef6fd17d4fb57127"}, + {file = "websockets-10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5560558b0dace8312c46aa8915da977db02738ac8ecffbc61acfbfe103e10155"}, + {file = "websockets-10.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:667c41351a6d8a34b53857ceb8343a45c85d438ee4fd835c279591db8aeb85be"}, + {file = "websockets-10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:468f0031fdbf4d643f89403a66383247eb82803430b14fa27ce2d44d2662ca37"}, + {file = "websockets-10.1-cp310-cp310-win32.whl", hash = "sha256:d0d81b46a5c87d443e40ce2272436da8e6092aa91f5fbeb60d1be9f11eff5b4c"}, + {file = "websockets-10.1-cp310-cp310-win_amd64.whl", hash = "sha256:b68b6caecb9a0c6db537aa79750d1b592a841e4f1a380c6196091e65b2ad35f9"}, + {file = "websockets-10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a249139abc62ef333e9e85064c27fefb113b16ffc5686cefc315bdaef3eefbc8"}, + {file = "websockets-10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8877861e3dee38c8d302eee0d5dbefa6663de3b46dc6a888f70cd7e82562d1f7"}, + {file = "websockets-10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e3872ae57acd4306ecf937d36177854e218e999af410a05c17168cd99676c512"}, + {file = "websockets-10.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b66e6d514f12c28d7a2d80bb2a48ef223342e99c449782d9831b0d29a9e88a17"}, + {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9f304a22ece735a3da8a51309bc2c010e23961a8f675fae46fdf62541ed62123"}, + {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:189ed478395967d6a98bb293abf04e8815349e17456a0a15511f1088b6cb26e4"}, + {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:08a42856158307e231b199671c4fce52df5786dd3d703f36b5d8ac76b206c485"}, + {file = "websockets-10.1-cp37-cp37m-win32.whl", hash = "sha256:3ef6f73854cded34e78390dbdf40dfdcf0b89b55c0e282468ef92646fce8d13a"}, + {file = "websockets-10.1-cp37-cp37m-win_amd64.whl", hash = "sha256:89e985d40d407545d5f5e2e58e1fdf19a22bd2d8cd54d20a882e29f97e930a0a"}, + {file = "websockets-10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:002071169d2e44ce8eb9e5ebac9fbce142ba4b5146eef1cfb16b177a27662657"}, + {file = "websockets-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cfae282c2aa7f0c4be45df65c248481f3509f8c40ca8b15ed96c35668ae0ff69"}, + {file = "websockets-10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:97b4b68a2ddaf5c4707ae79c110bfd874c5be3c6ac49261160fb243fa45d8bbb"}, + {file = "websockets-10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c9407719f42cb77049975410490c58a705da6af541adb64716573e550e5c9db"}, + {file = "websockets-10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1d858fb31e5ac992a2cdf17e874c95f8a5b1e917e1fb6b45ad85da30734b223f"}, + {file = "websockets-10.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7bdd3d26315db0a9cf8a0af30ca95e0aa342eda9c1377b722e71ccd86bc5d1dd"}, + {file = "websockets-10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e259be0863770cb91b1a6ccf6907f1ac2f07eff0b7f01c249ed751865a70cb0d"}, + {file = "websockets-10.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6b014875fae19577a392372075e937ebfebf53fd57f613df07b35ab210f31534"}, + {file = "websockets-10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:98de71f86bdb29430fd7ba9997f47a6b10866800e3ea577598a786a785701bb0"}, + {file = "websockets-10.1-cp38-cp38-win32.whl", hash = "sha256:3a02ab91d84d9056a9ee833c254895421a6333d7ae7fff94b5c68e4fa8095519"}, + {file = "websockets-10.1-cp38-cp38-win_amd64.whl", hash = "sha256:7d6673b2753f9c5377868a53445d0c321ef41ff3c8e3b6d57868e72054bfce5f"}, + {file = "websockets-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ddab2dc69ee5ae27c74dbfe9d7bb6fee260826c136dca257faa1a41d1db61a89"}, + {file = "websockets-10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14e9cf68a08d1a5d42109549201aefba473b1d925d233ae19035c876dd845da9"}, + {file = "websockets-10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e4819c6fb4f336fd5388372cb556b1f3a165f3f68e66913d1a2fc1de55dc6f58"}, + {file = "websockets-10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05e7f098c76b0a4743716590bb8f9706de19f1ef5148d61d0cf76495ec3edb9c"}, + {file = "websockets-10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bb6256de5a4fb1d42b3747b4e2268706c92965d75d0425be97186615bf2f24f"}, + {file = "websockets-10.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:888a5fa2a677e0c2b944f9826c756475980f1b276b6302e606f5c4ff5635be9e"}, + {file = "websockets-10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6fdec1a0b3e5630c58e3d8704d2011c678929fce90b40908c97dfc47de8dca72"}, + {file = "websockets-10.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:531d8eb013a9bc6b3ad101588182aa9b6dd994b190c56df07f0d84a02b85d530"}, + {file = "websockets-10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0d93b7cadc761347d98da12ec1930b5c71b2096f1ceed213973e3cda23fead9c"}, + {file = "websockets-10.1-cp39-cp39-win32.whl", hash = "sha256:d9b245db5a7e64c95816e27d72830e51411c4609c05673d1ae81eb5d23b0be54"}, + {file = "websockets-10.1-cp39-cp39-win_amd64.whl", hash = "sha256:882c0b8bdff3bf1bd7f024ce17c6b8006042ec4cceba95cf15df57e57efa471c"}, + {file = "websockets-10.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:10edd9d7d3581cfb9ff544ac09fc98cab7ee8f26778a5a8b2d5fd4b0684c5ba5"}, + {file = "websockets-10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa83174390c0ff4fc1304fbe24393843ac7a08fdd59295759c4b439e06b1536"}, + {file = "websockets-10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:483edee5abed738a0b6a908025be47f33634c2ad8e737edd03ffa895bd600909"}, + {file = "websockets-10.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:816ae7dac2c6522cfa620947ead0ca95ac654916eebf515c94d7c28de5601a6e"}, + {file = "websockets-10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1dafe98698ece09b8ccba81b910643ff37198e43521d977be76caf37709cf62b"}, + {file = "websockets-10.1.tar.gz", hash = "sha256:181d2b25de5a437b36aefedaf006ecb6fa3aa1328ec0236cdde15f32f9d3ff6d"}, ] werkzeug = [ {file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"}, diff --git a/pyproject.toml b/pyproject.toml index 4153931a..404eb0ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nonebot2" -version = "2.0.0-alpha.16" +version = "2.0.0-beta.1" description = "An asynchronous python bot framework." authors = ["yanyongyu "] license = "MIT" @@ -28,7 +28,7 @@ pygtrie = "^2.4.1" tomlkit = "^0.7.0" fastapi = "^0.70.0" websockets = ">=9.1" -typing-extensions = "^3.10.0" +typing-extensions = ">=3.10.0,<5.0.0" Quart = { version = "^0.15.0", optional = true } httpx = { version = ">=0.20.0, <1.0.0", extras = ["http2"] } pydantic = { version = "~1.8.0", extras = ["dotenv"] } From ee619a33a97fb7afbc62cb64731c1b47c6188b47 Mon Sep 17 00:00:00 2001 From: nonebot Date: Wed, 17 Nov 2021 12:38:35 +0000 Subject: [PATCH 11/21] :memo: update api docs --- docs/api/dependencies.md | 28 ++++++++++++++ docs/api/drivers/README.md | 15 ++++++++ docs/api/handler.md | 77 +++++++++++++++++--------------------- docs/api/typing.md | 11 +----- docs/api/utils.md | 4 +- 5 files changed, 81 insertions(+), 54 deletions(-) create mode 100644 docs/api/dependencies.md diff --git a/docs/api/dependencies.md b/docs/api/dependencies.md new file mode 100644 index 00000000..d88aabaa --- /dev/null +++ b/docs/api/dependencies.md @@ -0,0 +1,28 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.handler 模块 + +## 依赖注入处理模块 + +该模块实现了依赖注入的定义与处理。 + + +## `Depends(dependency=None, *, use_cache=True)` + + +* **说明** + + 参数依赖注入装饰器 + + + +* **参数** + + + * `dependency: Optional[Callable[..., Any]] = None`: 依赖函数。默认为参数的类型注释。 + + + * `use_cache: bool = True`: 是否使用缓存。默认为 `True`。 diff --git a/docs/api/drivers/README.md b/docs/api/drivers/README.md index 9f8ee3ee..d67b9bd8 100644 --- a/docs/api/drivers/README.md +++ b/docs/api/drivers/README.md @@ -62,6 +62,21 @@ Driver 基类。 +### `dependency_overrides` + + +* **类型** + + `Dict[Callable[..., Any], Callable[..., Any]]` + + + +* **说明** + + Depends 函数的替换表 + + + ### `__init__(env, config)` diff --git a/docs/api/handler.md b/docs/api/handler.md index dc2ab74f..977cbccb 100644 --- a/docs/api/handler.md +++ b/docs/api/handler.md @@ -14,12 +14,35 @@ sidebarDepth: 0 基类:`object` -事件处理函数类 +事件处理器类。支持依赖注入。 -### `__init__(func)` +### `__init__(func, *, name=None, dependencies=None, allow_types=None, dependency_overrides_provider=None)` + + +* **说明** + + 装饰一个函数为事件处理器。 + + + +* **参数** + + + * `func: T_Handler`: 事件处理函数。 + + + * `name: Optional[str]`: 事件处理器名称。默认为函数名。 + + + * `dependencies: Optional[List[DependsWrapper]]`: 额外的非参数依赖注入。 + + + * `allow_types: Optional[List[Type[Param]]]`: 允许的参数类型。 + + + * `dependency_overrides_provider: Optional[Any]`: 依赖注入覆盖提供者。 -装饰事件处理函数以便根据动态参数运行 ### `func` @@ -37,75 +60,45 @@ sidebarDepth: 0 -### `signature` +### `name` * **类型** - `inspect.Signature` + `str` * **说明** - 事件处理函数签名 + 事件处理函数名 -### _property_ `bot_type` +### `allow_types` * **类型** - `Union[Type["Bot"], inspect.Parameter.empty]` + `List[Type[Param]]` * **说明** - 事件处理函数接受的 Bot 对象类型 + 事件处理器允许的参数类型 -### _property_ `event_type` +### `dependencies` * **类型** - `Optional[Union[Type[Event], inspect.Parameter.empty]]` + `List[DependsWrapper]` * **说明** - 事件处理函数接受的 event 类型 / 不需要 event 参数 - - - -### _property_ `state_type` - - -* **类型** - - `Optional[Union[T_State, inspect.Parameter.empty]]` - - - -* **说明** - - 事件处理函数是否接受 state 参数 - - - -### _property_ `matcher_type` - - -* **类型** - - `Optional[Union[Type["Matcher"], inspect.Parameter.empty]]` - - - -* **说明** - - 事件处理函数是否接受 matcher 参数 + 事件处理器的额外依赖 diff --git a/docs/api/typing.md b/docs/api/typing.md index 6d339e12..a5597e8d 100644 --- a/docs/api/typing.md +++ b/docs/api/typing.md @@ -212,16 +212,7 @@ sidebarDepth: 0 * **类型** - * `Callable[[Bot, Event, T_State], Union[Awaitable[None], Awaitable[NoReturn]]]` - - - * `Callable[[Bot, Event], Union[Awaitable[None], Awaitable[NoReturn]]]` - - - * `Callable[[Bot, T_State], Union[Awaitable[None], Awaitable[NoReturn]]]` - - - * `Callable[[Bot], Union[Awaitable[None], Awaitable[NoReturn]]]` + * `Callable[..., Union[Awaitable[None], Awaitable[NoReturn]]]` diff --git a/docs/api/utils.md b/docs/api/utils.md index 8584f390..54aa11cb 100644 --- a/docs/api/utils.md +++ b/docs/api/utils.md @@ -41,14 +41,14 @@ sidebarDepth: 0 * **参数** - * `func: Callable[..., Any]`: 被装饰的同步函数 + * `func: Callable[P, R]`: 被装饰的同步函数 * **返回** - * `Callable[..., Awaitable[Any]]` + * `Callable[P, Awaitable[R]]` From 471d306e13772670946fb91e229467c6e3838438 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Fri, 19 Nov 2021 18:18:53 +0800 Subject: [PATCH 12/21] :alembic: change rule to use handler --- nonebot/dependencies/__init__.py | 48 +++++----- nonebot/drivers/aiohttp.py | 3 +- nonebot/drivers/fastapi.py | 3 +- nonebot/exception.py | 157 ++++++++++++++++++------------- nonebot/handler.py | 32 +++---- nonebot/matcher.py | 16 ++-- nonebot/rule.py | 66 ++++++++----- nonebot/typing.py | 5 +- 8 files changed, 182 insertions(+), 148 deletions(-) diff --git a/nonebot/dependencies/__init__.py b/nonebot/dependencies/__init__.py index bbf835fb..f9a6ab63 100644 --- a/nonebot/dependencies/__init__.py +++ b/nonebot/dependencies/__init__.py @@ -18,6 +18,7 @@ from nonebot.log import logger from .models import Param as Param from .utils import get_typed_signature from .models import Dependent as Dependent +from nonebot.exception import SkippedException from .models import DependsWrapper as DependsWrapper from nonebot.utils import (run_sync, is_gen_callable, run_sync_ctx_manager, is_async_gen_callable, is_coroutine_callable) @@ -112,21 +113,20 @@ def get_dependent(*, async def solve_dependencies( - *, - dependent: Dependent, - stack: Optional[AsyncExitStack] = None, - sub_dependents: Optional[List[Dependent]] = None, - dependency_overrides_provider: Optional[Any] = None, - dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None, - **params: Any -) -> Tuple[Dict[str, Any], Dict[Callable[..., Any], Any], bool]: + *, + _dependent: Dependent, + _stack: Optional[AsyncExitStack] = None, + _sub_dependents: Optional[List[Dependent]] = None, + _dependency_overrides_provider: Optional[Any] = None, + _dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None, + **params: Any) -> Tuple[Dict[str, Any], Dict[Callable[..., Any], Any]]: values: Dict[str, Any] = {} - dependency_cache = dependency_cache or {} + dependency_cache = _dependency_cache or {} # solve sub dependencies sub_dependent: Dependent - for sub_dependent in chain(sub_dependents or tuple(), - dependent.dependencies): + for sub_dependent in chain(_sub_dependents or tuple(), + _dependent.dependencies): sub_dependent.func = cast(Callable[..., Any], sub_dependent.func) sub_dependent.cache_key = cast(Callable[..., Any], sub_dependent.cache_key) @@ -134,10 +134,10 @@ async def solve_dependencies( # dependency overrides use_sub_dependant = sub_dependent - if (dependency_overrides_provider and - hasattr(dependency_overrides_provider, "dependency_overrides")): + if (_dependency_overrides_provider and hasattr( + _dependency_overrides_provider, "dependency_overrides")): original_call = sub_dependent.func - func = getattr(dependency_overrides_provider, + func = getattr(_dependency_overrides_provider, "dependency_overrides", {}).get(original_call, original_call) use_sub_dependant = get_dependent( @@ -148,13 +148,11 @@ async def solve_dependencies( # solve sub dependency with current cache solved_result = await solve_dependencies( - dependent=use_sub_dependant, - dependency_overrides_provider=dependency_overrides_provider, + _dependent=use_sub_dependant, + _dependency_overrides_provider=_dependency_overrides_provider, dependency_cache=dependency_cache, **params) - sub_values, sub_dependency_cache, ignored = solved_result - if ignored: - return values, dependency_cache, True + sub_values, sub_dependency_cache = solved_result # update cache? dependency_cache.update(sub_dependency_cache) @@ -163,13 +161,13 @@ async def solve_dependencies( solved = dependency_cache[sub_dependent.cache_key] elif is_gen_callable(func) or is_async_gen_callable(func): assert isinstance( - stack, AsyncExitStack + _stack, AsyncExitStack ), "Generator dependency should be called in context" if is_gen_callable(func): cm = run_sync_ctx_manager(contextmanager(func)(**sub_values)) else: cm = asynccontextmanager(func)(**sub_values) - solved = await stack.enter_async_context(cm) + solved = await _stack.enter_async_context(cm) elif is_coroutine_callable(func): solved = await func(**sub_values) else: @@ -183,7 +181,7 @@ async def solve_dependencies( dependency_cache[sub_dependent.cache_key] = solved # usual dependency - for field in dependent.params: + for field in _dependent.params: field_info = field.field_info assert isinstance(field_info, Param), "Params must be subclasses of Param" @@ -194,13 +192,13 @@ async def solve_dependencies( if errs_: logger.debug( f"{field_info} " - f"type {type(value)} not match depends {dependent.func} " + f"type {type(value)} not match depends {_dependent.func} " f"annotation {field._type_display()}, ignored") - return values, dependency_cache, True + raise SkippedException else: values[field.name] = value - return values, dependency_cache, False + return values, dependency_cache def Depends(dependency: Optional[Callable[..., Any]] = None, diff --git a/nonebot/drivers/aiohttp.py b/nonebot/drivers/aiohttp.py index 20ca668f..3670c414 100644 --- a/nonebot/drivers/aiohttp.py +++ b/nonebot/drivers/aiohttp.py @@ -248,6 +248,8 @@ class Driver(ForwardDriver): await asyncio.sleep(3) continue + setup_ = cast(HTTPPollingSetup, setup_) + if not bot: request = await _build_request(setup_) if not request: @@ -264,7 +266,6 @@ class Driver(ForwardDriver): bot.request = request request = cast(HTTPRequest, request) - setup_ = cast(HTTPPollingSetup, setup_) headers = request.headers timeout = aiohttp.ClientTimeout(30) diff --git a/nonebot/drivers/fastapi.py b/nonebot/drivers/fastapi.py index 0ace2e31..e0d47d79 100644 --- a/nonebot/drivers/fastapi.py +++ b/nonebot/drivers/fastapi.py @@ -409,6 +409,8 @@ class Driver(ReverseDriver, ForwardDriver): await asyncio.sleep(3) continue + setup_ = cast(HTTPPollingSetup, setup_) + if not bot: request = await _build_request(setup_) if not request: @@ -423,7 +425,6 @@ class Driver(ReverseDriver, ForwardDriver): continue bot.request = request - setup_ = cast(HTTPPollingSetup, setup_) request = cast(HTTPRequest, request) headers = request.headers diff --git a/nonebot/exception.py b/nonebot/exception.py index 3cad317a..227d8e10 100644 --- a/nonebot/exception.py +++ b/nonebot/exception.py @@ -6,6 +6,8 @@ 这些异常并非所有需要用户处理,在 NoneBot 内部运行时被捕获,并进行对应操作。 """ +from typing import Optional + class NoneBotException(Exception): """ @@ -13,9 +15,33 @@ class NoneBotException(Exception): 所有 NoneBot 发生的异常基类。 """ - pass +# Rule Exception +class ParserExit(NoneBotException): + """ + :说明: + + ``shell command`` 处理消息失败时返回的异常 + + :参数: + + * ``status`` + * ``message`` + """ + + def __init__(self, status: int = 0, message: Optional[str] = None): + self.status = status + self.message = message + + def __repr__(self): + return f"" + + def __str__(self): + return self.__repr__() + + +# Processor Exception class IgnoredException(NoneBotException): """ :说明: @@ -37,71 +63,6 @@ class IgnoredException(NoneBotException): return self.__repr__() -class ParserExit(NoneBotException): - """ - :说明: - - ``shell command`` 处理消息失败时返回的异常 - - :参数: - - * ``status`` - * ``message`` - """ - - def __init__(self, status=0, message=None): - self.status = status - self.message = message - - def __repr__(self): - return f"" - - def __str__(self): - return self.__repr__() - - -class PausedException(NoneBotException): - """ - :说明: - - 指示 NoneBot 结束当前 ``Handler`` 并等待下一条消息后继续下一个 ``Handler``。 - 可用于用户输入新信息。 - - :用法: - - 可以在 ``Handler`` 中通过 ``Matcher.pause()`` 抛出。 - """ - pass - - -class RejectedException(NoneBotException): - """ - :说明: - - 指示 NoneBot 结束当前 ``Handler`` 并等待下一条消息后重新运行当前 ``Handler``。 - 可用于用户重新输入。 - - :用法: - - 可以在 ``Handler`` 中通过 ``Matcher.reject()`` 抛出。 - """ - pass - - -class FinishedException(NoneBotException): - """ - :说明: - - 指示 NoneBot 结束当前 ``Handler`` 且后续 ``Handler`` 不再被运行。 - 可用于结束用户会话。 - - :用法: - - 可以在 ``Handler`` 中通过 ``Matcher.finish()`` 抛出。 - """ - pass - - class StopPropagation(NoneBotException): """ :说明: @@ -112,9 +73,69 @@ class StopPropagation(NoneBotException): 在 ``Matcher.block == True`` 时抛出。 """ - pass +# Matcher Exceptions +class MatcherException(NoneBotException): + """ + :说明: + + 所有 Matcher 发生的异常基类。 + """ + + +class SkippedException(MatcherException): + """ + :说明: + + 指示 NoneBot 立即结束当前 ``Handler`` 的处理,继续处理下一个 ``Handler``。 + + :用法: + + 可以在 ``Handler`` 中通过 ``Matcher.skip()`` 抛出。 + """ + + +class PausedException(MatcherException): + """ + :说明: + + 指示 NoneBot 结束当前 ``Handler`` 并等待下一条消息后继续下一个 ``Handler``。 + 可用于用户输入新信息。 + + :用法: + + 可以在 ``Handler`` 中通过 ``Matcher.pause()`` 抛出。 + """ + + +class RejectedException(MatcherException): + """ + :说明: + + 指示 NoneBot 结束当前 ``Handler`` 并等待下一条消息后重新运行当前 ``Handler``。 + 可用于用户重新输入。 + + :用法: + + 可以在 ``Handler`` 中通过 ``Matcher.reject()`` 抛出。 + """ + + +class FinishedException(MatcherException): + """ + :说明: + + 指示 NoneBot 结束当前 ``Handler`` 且后续 ``Handler`` 不再被运行。 + 可用于结束用户会话。 + + :用法: + + 可以在 ``Handler`` 中通过 ``Matcher.finish()`` 抛出。 + """ + + +# Adapter Exceptions class AdapterException(NoneBotException): """ :说明: @@ -130,7 +151,7 @@ class AdapterException(NoneBotException): self.adapter_name = adapter_name -class NoLogException(Exception): +class NoLogException(AdapterException): """ :说明: diff --git a/nonebot/handler.py b/nonebot/handler.py index 232f2200..d6df70dd 100644 --- a/nonebot/handler.py +++ b/nonebot/handler.py @@ -7,24 +7,19 @@ import asyncio from contextlib import AsyncExitStack -from typing import TYPE_CHECKING, Any, Dict, List, Type, Callable, Optional +from typing import Any, Dict, List, Type, Callable, Optional -from nonebot.typing import T_Handler from nonebot.utils import get_name, run_sync from nonebot.dependencies import (Param, Dependent, DependsWrapper, get_dependent, solve_dependencies, get_parameterless_sub_dependant) -if TYPE_CHECKING: - from nonebot.matcher import Matcher - from nonebot.adapters import Bot, Event - class Handler: """事件处理器类。支持依赖注入。""" def __init__(self, - func: T_Handler, + func: Callable[..., Any], *, name: Optional[str] = None, dependencies: Optional[List[DependsWrapper]] = None, @@ -37,7 +32,7 @@ class Handler: :参数: - * ``func: T_Handler``: 事件处理函数。 + * ``func: Callable[..., Any]``: 事件处理函数。 * ``name: Optional[str]``: 事件处理器名称。默认为函数名。 * ``dependencies: Optional[List[DependsWrapper]]``: 额外的非参数依赖注入。 * ``allow_types: Optional[List[Type[Param]]]``: 允许的参数类型。 @@ -45,7 +40,7 @@ class Handler: """ self.func = func """ - :类型: ``T_Handler`` + :类型: ``Callable[..., Any]`` :说明: 事件处理函数 """ self.name = get_name(func) if name is None else name @@ -85,24 +80,21 @@ class Handler: _dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None, **params) -> Any: - values, _, ignored = await solve_dependencies( - dependent=self.dependent, - stack=_stack, - sub_dependents=[ + values, cache = await solve_dependencies( + _dependent=self.dependent, + _stack=_stack, + _sub_dependents=[ self.sub_dependents[dependency.dependency] # type: ignore for dependency in self.dependencies ], - dependency_overrides_provider=self.dependency_overrides_provider, - dependency_cache=_dependency_cache, + _dependency_overrides_provider=self.dependency_overrides_provider, + _dependency_cache=_dependency_cache, **params) - if ignored: - return - if asyncio.iscoroutinefunction(self.func): - await self.func(**values) + return await self.func(**values) else: - await run_sync(self.func)(**values) + return await run_sync(self.func)(**values) def cache_dependent(self, dependency: DependsWrapper): if not dependency.dependency: diff --git a/nonebot/matcher.py b/nonebot/matcher.py index 8856a3cf..0967d5c3 100644 --- a/nonebot/matcher.py +++ b/nonebot/matcher.py @@ -20,10 +20,11 @@ from nonebot.dependencies import DependsWrapper from nonebot.permission import USER, Permission from nonebot.adapters import (Bot, Event, Message, MessageSegment, MessageTemplate) -from nonebot.exception import (PausedException, StopPropagation, - FinishedException, RejectedException) from nonebot.typing import (T_State, T_Handler, T_ArgsParser, T_TypeUpdater, T_StateFactory, T_PermissionUpdater) +from nonebot.exception import (PausedException, StopPropagation, + SkippedException, FinishedException, + RejectedException) if TYPE_CHECKING: from nonebot.plugin import Plugin @@ -601,10 +602,13 @@ class Matcher(metaclass=MatcherMeta): while self.handlers: handler = self.handlers.pop(0) logger.debug(f"Running handler {handler}") - await handler(matcher=self, - bot=bot, - event=event, - state=self.state) + try: + await handler(matcher=self, + bot=bot, + event=event, + state=self.state) + except SkippedException: + pass except RejectedException: self.handlers.insert(0, handler) # type: ignore diff --git a/nonebot/rule.py b/nonebot/rule.py index 7ac0fe8d..3a1c2c33 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -14,16 +14,18 @@ import shlex import asyncio from itertools import product from argparse import Namespace +from contextlib import AsyncExitStack from typing_extensions import TypedDict from argparse import ArgumentParser as ArgParser -from typing import (Any, Tuple, Union, Callable, NoReturn, Optional, Sequence, - Awaitable) +from typing import (Any, Dict, Tuple, Union, Callable, NoReturn, Optional, + Sequence, Awaitable) from pygtrie import CharTrie -from nonebot import get_driver from nonebot.log import logger from nonebot.utils import run_sync +from nonebot.handler import Handler +from nonebot import params, get_driver from nonebot.exception import ParserExit from nonebot.typing import T_State, T_RuleChecker from nonebot.adapters import Bot, Event, MessageSegment @@ -62,16 +64,22 @@ class Rule: """ __slots__ = ("checkers",) - def __init__( - self, *checkers: Callable[[Bot, Event, T_State], - Awaitable[bool]]) -> None: + HANDLER_PARAM_TYPES = [ + params.BotParam, params.EventParam, params.StateParam + ] + + def __init__(self, *checkers: T_RuleChecker) -> None: """ :参数: - * ``*checkers: Callable[[Bot, Event, T_State], Awaitable[bool]]``: **异步** RuleChecker + * ``*checkers: T_RuleChecker``: RuleChecker """ - self.checkers = set(checkers) + self.checkers = set( + Handler(checker, + allow_types=self.HANDLER_PARAM_TYPES, + dependency_overrides_provider=get_driver()) + for checker in checkers) """ :说明: @@ -79,10 +87,17 @@ class Rule: :类型: - * ``Set[Callable[[Bot, Event, T_State], Awaitable[bool]]]`` + * ``Set[Handler]`` """ - async def __call__(self, bot: Bot, event: Event, state: T_State) -> bool: + async def __call__( + self, + bot: Bot, + event: Event, + state: T_State, + stack: Optional[AsyncExitStack] = None, + dependency_cache: Optional[Dict[Callable[..., Any], + Any]] = None) -> bool: """ :说明: @@ -99,19 +114,21 @@ class Rule: - ``bool`` """ results = await asyncio.gather( - *map(lambda c: c(bot, event, state), self.checkers)) + checker(bot=bot, + event=event, + state=state, + _stack=stack, + _dependency_cache=dependency_cache) + for checker in self.checkers) return all(results) def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule": - checkers = self.checkers.copy() if other is None: return self elif isinstance(other, Rule): - checkers |= other.checkers - elif asyncio.iscoroutinefunction(other): - checkers.add(other) # type: ignore + checkers = [*self.checkers, *other.checkers] else: - checkers.add(run_sync(other)) + checkers = [*self.checkers, other] return Rule(*checkers) def __or__(self, other) -> NoReturn: @@ -226,7 +243,7 @@ def keyword(*keywords: str) -> Rule: * ``*keywords: str``: 关键词 """ - async def _keyword(bot: Bot, event: Event, state: T_State) -> bool: + async def _keyword(event: Event) -> bool: if event.get_type() != "message": return False text = event.get_plaintext() @@ -274,7 +291,7 @@ def command(*cmds: Union[str, Tuple[str, ...]]) -> Rule: for start, sep in product(command_start, command_sep): TrieRule.add_prefix(f"{start}{sep.join(command)}", command) - async def _command(bot: Bot, event: Event, state: T_State) -> bool: + async def _command(state: T_State) -> bool: return state[PREFIX_KEY][CMD_KEY] in commands return Rule(_command) @@ -294,7 +311,7 @@ class ArgumentParser(ArgParser): old_message += message setattr(self, "message", old_message) - def exit(self, status=0, message=None): + def exit(self, status: int = 0, message: Optional[str] = None): raise ParserExit(status=status, message=message or getattr(self, "message", None)) @@ -360,7 +377,7 @@ def shell_command(*cmds: Union[str, Tuple[str, ...]], for start, sep in product(command_start, command_sep): TrieRule.add_prefix(f"{start}{sep.join(command)}", command) - async def _shell_command(bot: Bot, event: Event, state: T_State) -> bool: + async def _shell_command(event: Event, state: T_State) -> bool: if state[PREFIX_KEY][CMD_KEY] in commands: message = str(event.get_message()) strip_message = message[len(state[PREFIX_KEY][RAW_CMD_KEY] @@ -400,7 +417,7 @@ def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule: pattern = re.compile(regex, flags) - async def _regex(bot: Bot, event: Event, state: T_State) -> bool: + async def _regex(event: Event, state: T_State) -> bool: if event.get_type() != "message": return False matched = pattern.search(str(event.get_message())) @@ -415,6 +432,10 @@ def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule: return Rule(_regex) +async def _to_me(event: Event) -> bool: + return event.is_tome() + + def to_me() -> Rule: """ :说明: @@ -426,7 +447,4 @@ def to_me() -> Rule: * 无 """ - async def _to_me(bot: Bot, event: Event, state: T_State) -> bool: - return event.is_tome() - return Rule(_to_me) diff --git a/nonebot/typing.py b/nonebot/typing.py index 3725b301..337ff426 100644 --- a/nonebot/typing.py +++ b/nonebot/typing.py @@ -123,10 +123,9 @@ T_RunPostProcessor = Callable[..., Awaitable[None]] 事件响应器运行前预处理函数 RunPostProcessor 类型,第二个参数为运行时产生的错误(如果存在) """ -T_RuleChecker = Callable[["Bot", "Event", T_State], Union[bool, - Awaitable[bool]]] +T_RuleChecker = Callable[..., Union[bool, Awaitable[bool]]] """ -:类型: ``Callable[[Bot, Event, T_State], Union[bool, Awaitable[bool]]]`` +:类型: ``Callable[..., Union[bool, Awaitable[bool]]]`` :说明: From a5948fb5e3132948c7ed90312f57aa1c76b3c754 Mon Sep 17 00:00:00 2001 From: nonebot Date: Fri, 19 Nov 2021 10:20:20 +0000 Subject: [PATCH 13/21] :memo: update api docs --- docs/api/exception.md | 148 +++++++++++++++++++++++++----------------- docs/api/handler.md | 4 +- docs/api/rule.md | 6 +- docs/api/typing.md | 2 +- 4 files changed, 94 insertions(+), 66 deletions(-) diff --git a/docs/api/exception.md b/docs/api/exception.md index f48a493b..bf281b11 100644 --- a/docs/api/exception.md +++ b/docs/api/exception.md @@ -22,24 +22,6 @@ sidebarDepth: 0 -## _exception_ `IgnoredException` - -基类:`nonebot.exception.NoneBotException` - - -* **说明** - - 指示 NoneBot 应该忽略该事件。可由 PreProcessor 抛出。 - - - -* **参数** - - - * `reason`: 忽略事件的原因 - - - ## _exception_ `ParserExit` 基类:`nonebot.exception.NoneBotException` @@ -61,57 +43,21 @@ sidebarDepth: 0 -## _exception_ `PausedException` +## _exception_ `IgnoredException` 基类:`nonebot.exception.NoneBotException` * **说明** - 指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler`。 - 可用于用户输入新信息。 + 指示 NoneBot 应该忽略该事件。可由 PreProcessor 抛出。 -* **用法** +* **参数** - 可以在 `Handler` 中通过 `Matcher.pause()` 抛出。 - - - -## _exception_ `RejectedException` - -基类:`nonebot.exception.NoneBotException` - - -* **说明** - - 指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`。 - 可用于用户重新输入。 - - - -* **用法** - - 可以在 `Handler` 中通过 `Matcher.reject()` 抛出。 - - - -## _exception_ `FinishedException` - -基类:`nonebot.exception.NoneBotException` - - -* **说明** - - 指示 NoneBot 结束当前 `Handler` 且后续 `Handler` 不再被运行。 - 可用于结束用户会话。 - - - -* **用法** - - 可以在 `Handler` 中通过 `Matcher.finish()` 抛出。 + + * `reason`: 忽略事件的原因 @@ -132,6 +78,88 @@ sidebarDepth: 0 +## _exception_ `MatcherException` + +基类:`nonebot.exception.NoneBotException` + + +* **说明** + + 所有 Matcher 发生的异常基类。 + + + +## _exception_ `SkippedException` + +基类:`nonebot.exception.MatcherException` + + +* **说明** + + 指示 NoneBot 立即结束当前 `Handler` 的处理,继续处理下一个 `Handler`。 + + + +* **用法** + + 可以在 `Handler` 中通过 `Matcher.skip()` 抛出。 + + + +## _exception_ `PausedException` + +基类:`nonebot.exception.MatcherException` + + +* **说明** + + 指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler`。 + 可用于用户输入新信息。 + + + +* **用法** + + 可以在 `Handler` 中通过 `Matcher.pause()` 抛出。 + + + +## _exception_ `RejectedException` + +基类:`nonebot.exception.MatcherException` + + +* **说明** + + 指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`。 + 可用于用户重新输入。 + + + +* **用法** + + 可以在 `Handler` 中通过 `Matcher.reject()` 抛出。 + + + +## _exception_ `FinishedException` + +基类:`nonebot.exception.MatcherException` + + +* **说明** + + 指示 NoneBot 结束当前 `Handler` 且后续 `Handler` 不再被运行。 + 可用于结束用户会话。 + + + +* **用法** + + 可以在 `Handler` 中通过 `Matcher.finish()` 抛出。 + + + ## _exception_ `AdapterException` 基类:`nonebot.exception.NoneBotException` @@ -152,7 +180,7 @@ sidebarDepth: 0 ## _exception_ `NoLogException` -基类:`Exception` +基类:`nonebot.exception.AdapterException` * **说明** diff --git a/docs/api/handler.md b/docs/api/handler.md index 977cbccb..8d36478b 100644 --- a/docs/api/handler.md +++ b/docs/api/handler.md @@ -29,7 +29,7 @@ sidebarDepth: 0 * **参数** - * `func: T_Handler`: 事件处理函数。 + * `func: Callable[..., Any]`: 事件处理函数。 * `name: Optional[str]`: 事件处理器名称。默认为函数名。 @@ -50,7 +50,7 @@ sidebarDepth: 0 * **类型** - `T_Handler` + `Callable[..., Any]` diff --git a/docs/api/rule.md b/docs/api/rule.md index 0ee615df..8a930b5b 100644 --- a/docs/api/rule.md +++ b/docs/api/rule.md @@ -42,7 +42,7 @@ Rule(async_function, run_sync(sync_function)) * **参数** - * `*checkers: Callable[[Bot, Event, T_State], Awaitable[bool]]`: **异步** RuleChecker + * `*checkers: T_RuleChecker`: RuleChecker @@ -58,11 +58,11 @@ Rule(async_function, run_sync(sync_function)) * **类型** - * `Set[Callable[[Bot, Event, T_State], Awaitable[bool]]]` + * `Set[Handler]` -### _async_ `__call__(bot, event, state)` +### _async_ `__call__(bot, event, state, stack=None, dependency_cache=None)` * **说明** diff --git a/docs/api/typing.md b/docs/api/typing.md index a5597e8d..08de8de4 100644 --- a/docs/api/typing.md +++ b/docs/api/typing.md @@ -179,7 +179,7 @@ sidebarDepth: 0 * **类型** - `Callable[[Bot, Event, T_State], Union[bool, Awaitable[bool]]]` + `Callable[..., Union[bool, Awaitable[bool]]]` From b4d12d905d3523792e26d44ef9131cf5a81b5d50 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 21 Nov 2021 12:36:44 +0800 Subject: [PATCH 14/21] :alembic: change permission to use handler --- nonebot/dependencies/__init__.py | 11 ++-- nonebot/dependencies/models.py | 7 ++- nonebot/dependencies/utils.py | 10 ++-- nonebot/matcher.py | 52 +++++++++++------ nonebot/message.py | 98 ++++++++++++++++---------------- nonebot/permission.py | 50 +++++++++++----- nonebot/plugin/on.py | 3 +- nonebot/rule.py | 23 +++++--- nonebot/typing.py | 75 ++++++++++++++++++------ 9 files changed, 207 insertions(+), 122 deletions(-) diff --git a/nonebot/dependencies/__init__.py b/nonebot/dependencies/__init__.py index f9a6ab63..dd27ce9f 100644 --- a/nonebot/dependencies/__init__.py +++ b/nonebot/dependencies/__init__.py @@ -20,6 +20,7 @@ from .utils import get_typed_signature from .models import Dependent as Dependent from nonebot.exception import SkippedException from .models import DependsWrapper as DependsWrapper +from nonebot.typing import T_Handler, T_DependencyCache from nonebot.utils import (run_sync, is_gen_callable, run_sync_ctx_manager, is_async_gen_callable, is_coroutine_callable) @@ -58,7 +59,7 @@ def get_parameterless_sub_dependant( def get_sub_dependant( *, depends: DependsWrapper, - dependency: Callable[..., Any], + dependency: T_Handler, name: Optional[str] = None, allow_types: Optional[List[Type[Param]]] = None) -> Dependent: sub_dependant = get_dependent(func=dependency, @@ -69,7 +70,7 @@ def get_sub_dependant( def get_dependent(*, - func: Callable[..., Any], + func: T_Handler, name: Optional[str] = None, use_cache: bool = True, allow_types: Optional[List[Type[Param]]] = None) -> Dependent: @@ -118,8 +119,8 @@ async def solve_dependencies( _stack: Optional[AsyncExitStack] = None, _sub_dependents: Optional[List[Dependent]] = None, _dependency_overrides_provider: Optional[Any] = None, - _dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None, - **params: Any) -> Tuple[Dict[str, Any], Dict[Callable[..., Any], Any]]: + _dependency_cache: Optional[T_DependencyCache] = None, + **params: Any) -> Tuple[Dict[str, Any], T_DependencyCache]: values: Dict[str, Any] = {} dependency_cache = _dependency_cache or {} @@ -201,7 +202,7 @@ async def solve_dependencies( return values, dependency_cache -def Depends(dependency: Optional[Callable[..., Any]] = None, +def Depends(dependency: Optional[T_Handler] = None, *, use_cache: bool = True) -> Any: """ diff --git a/nonebot/dependencies/models.py b/nonebot/dependencies/models.py index ca764f9b..4431d11c 100644 --- a/nonebot/dependencies/models.py +++ b/nonebot/dependencies/models.py @@ -5,9 +5,10 @@ from typing import Any, List, Type, Callable, Optional from pydantic.fields import FieldInfo, ModelField from nonebot.utils import get_name +from nonebot.typing import T_Handler -class Param(FieldInfo, abc.ABC): +class Param(abc.ABC, FieldInfo): def __repr__(self) -> str: return f"{self.__class__.__name__}" @@ -28,7 +29,7 @@ class Param(FieldInfo, abc.ABC): class DependsWrapper: def __init__(self, - dependency: Optional[Callable[..., Any]] = None, + dependency: Optional[T_Handler] = None, *, use_cache: bool = True) -> None: self.dependency = dependency @@ -44,7 +45,7 @@ class Dependent: def __init__(self, *, - func: Optional[Callable[..., Any]] = None, + func: Optional[T_Handler] = None, name: Optional[str] = None, params: Optional[List[ModelField]] = None, allow_types: Optional[List[Type[Param]]] = None, diff --git a/nonebot/dependencies/utils.py b/nonebot/dependencies/utils.py index ed08b228..ade7fd97 100644 --- a/nonebot/dependencies/utils.py +++ b/nonebot/dependencies/utils.py @@ -1,13 +1,15 @@ import inspect -from typing import Any, Dict, Callable +from typing import Any, Dict from loguru import logger from pydantic.typing import ForwardRef, evaluate_forwardref +from nonebot.typing import T_Handler -def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: - signature = inspect.signature(call) - globalns = getattr(call, "__globals__", {}) + +def get_typed_signature(func: T_Handler) -> inspect.Signature: + signature = inspect.signature(func) + globalns = getattr(func, "__globals__", {}) typed_params = [ inspect.Parameter( name=param.name, diff --git a/nonebot/matcher.py b/nonebot/matcher.py index 0967d5c3..f68c78de 100644 --- a/nonebot/matcher.py +++ b/nonebot/matcher.py @@ -9,6 +9,7 @@ from types import ModuleType from datetime import datetime from contextvars import ContextVar from collections import defaultdict +from contextlib import AsyncExitStack from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Callable, NoReturn, Optional) @@ -20,11 +21,12 @@ from nonebot.dependencies import DependsWrapper from nonebot.permission import USER, Permission from nonebot.adapters import (Bot, Event, Message, MessageSegment, MessageTemplate) -from nonebot.typing import (T_State, T_Handler, T_ArgsParser, T_TypeUpdater, - T_StateFactory, T_PermissionUpdater) from nonebot.exception import (PausedException, StopPropagation, SkippedException, FinishedException, RejectedException) +from nonebot.typing import (T_State, T_Handler, T_ArgsParser, T_TypeUpdater, + T_StateFactory, T_DependencyCache, + T_PermissionUpdater) if TYPE_CHECKING: from nonebot.plugin import Plugin @@ -267,7 +269,13 @@ class Matcher(metaclass=MatcherMeta): return NewMatcher @classmethod - async def check_perm(cls, bot: Bot, event: Event) -> bool: + async def check_perm( + cls, + bot: Bot, + event: Event, + stack: Optional[AsyncExitStack] = None, + dependency_cache: Optional[Dict[Callable[..., Any], + Any]] = None) -> bool: """ :说明: @@ -284,10 +292,17 @@ class Matcher(metaclass=MatcherMeta): """ event_type = event.get_type() return (event_type == (cls.type or event_type) and - await cls.permission(bot, event)) + await cls.permission(bot, event, stack, dependency_cache)) @classmethod - async def check_rule(cls, bot: Bot, event: Event, state: T_State) -> bool: + async def check_rule( + cls, + bot: Bot, + event: Event, + state: T_State, + stack: Optional[AsyncExitStack] = None, + dependency_cache: Optional[Dict[Callable[..., Any], + Any]] = None) -> bool: """ :说明: @@ -305,7 +320,7 @@ class Matcher(metaclass=MatcherMeta): """ event_type = event.get_type() return (event_type == (cls.type or event_type) and - await cls.rule(bot, event, state)) + await cls.rule(bot, event, state, stack, dependency_cache)) @classmethod def args_parser(cls, func: T_ArgsParser) -> T_ArgsParser: @@ -589,7 +604,12 @@ class Matcher(metaclass=MatcherMeta): self.block = True # 运行handlers - async def run(self, bot: Bot, event: Event, state: T_State): + async def run(self, + bot: Bot, + event: Event, + state: T_State, + stack: Optional[AsyncExitStack] = None, + dependency_cache: Optional[T_DependencyCache] = None): b_t = current_bot.set(bot) e_t = current_event.set(event) s_t = current_state.set(self.state) @@ -606,7 +626,9 @@ class Matcher(metaclass=MatcherMeta): await handler(matcher=self, bot=bot, event=event, - state=self.state) + state=self.state, + _stack=stack, + _dependency_cache=dependency_cache) except SkippedException: pass @@ -624,11 +646,8 @@ class Matcher(metaclass=MatcherMeta): updater = self.__class__._default_permission_updater if updater: - permission = await updater( - bot, - event, - self.state, # type: ignore - self.permission) + permission = await updater(bot, event, self.state, + self.permission) else: permission = USER(event.get_session_id(), perm=self.permission) @@ -661,11 +680,8 @@ class Matcher(metaclass=MatcherMeta): updater = self.__class__._default_permission_updater if updater: - permission = await updater( - bot, - event, - self.state, # type: ignore - self.permission) + permission = await updater(bot, event, self.state, + self.permission) else: permission = USER(event.get_session_id(), perm=self.permission) diff --git a/nonebot/message.py b/nonebot/message.py index fec00687..66379e77 100644 --- a/nonebot/message.py +++ b/nonebot/message.py @@ -8,7 +8,7 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供 import asyncio from datetime import datetime from contextlib import AsyncExitStack -from typing import TYPE_CHECKING, Set, Type +from typing import TYPE_CHECKING, Any, Set, Dict, Type, Callable, Optional from nonebot.log import logger from nonebot.rule import TrieRule @@ -17,8 +17,9 @@ from nonebot.utils import escape_tag from nonebot import params, get_driver from nonebot.matcher import Matcher, matchers from nonebot.exception import NoLogException, StopPropagation, IgnoredException -from nonebot.typing import (T_State, T_RunPreProcessor, T_RunPostProcessor, - T_EventPreProcessor, T_EventPostProcessor) +from nonebot.typing import (T_State, T_DependencyCache, T_RunPreProcessor, + T_RunPostProcessor, T_EventPreProcessor, + T_EventPostProcessor) if TYPE_CHECKING: from nonebot.adapters import Bot, Event @@ -43,14 +44,6 @@ def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor: :说明: 事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。 - - :参数: - - 事件预处理函数接收三个参数。 - - * ``bot: Bot``: Bot 对象 - * ``event: Event``: Event 对象 - * ``state: T_State``: 当前 State """ _event_preprocessors.add( Handler(func, @@ -64,14 +57,6 @@ def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor: :说明: 事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。 - - :参数: - - 事件后处理函数接收三个参数。 - - * ``bot: Bot``: Bot 对象 - * ``event: Event``: Event 对象 - * ``state: T_State``: 当前事件运行前 State """ _event_postprocessors.add( Handler(func, @@ -85,15 +70,6 @@ def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor: :说明: 运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。 - - :参数: - - 运行预处理函数接收四个参数。 - - * ``matcher: Matcher``: 当前要运行的事件响应器 - * ``bot: Bot``: Bot 对象 - * ``event: Event``: Event 对象 - * ``state: T_State``: 当前 State """ _run_preprocessors.add( Handler(func, @@ -107,16 +83,6 @@ def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor: :说明: 运行后处理。装饰一个函数,使它在每次事件响应器运行后执行。 - - :参数: - - 运行后处理函数接收五个参数。 - - * ``matcher: Matcher``: 运行完毕的事件响应器 - * ``exception: Optional[Exception]``: 事件响应器运行错误(如果存在) - * ``bot: Bot``: Bot 对象 - * ``event: Event``: Event 对象 - * ``state: T_State``: 当前 State """ _run_postprocessors.add( Handler(func, @@ -125,8 +91,14 @@ def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor: return func -async def _check_matcher(priority: int, Matcher: Type[Matcher], bot: "Bot", - event: "Event", state: T_State) -> None: +async def _check_matcher( + priority: int, + Matcher: Type[Matcher], + 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: try: matchers[priority].remove(Matcher) @@ -136,7 +108,9 @@ async def _check_matcher(priority: int, Matcher: Type[Matcher], bot: "Bot", try: if not await Matcher.check_perm( - bot, event) or not await Matcher.check_rule(bot, event, state): + bot, event, stack, + dependency_cache) or not await Matcher.check_rule( + bot, event, state, stack, dependency_cache): return except Exception as e: logger.opt(colors=True, exception=e).error( @@ -149,17 +123,28 @@ async def _check_matcher(priority: int, Matcher: Type[Matcher], bot: "Bot", except Exception: pass - await _run_matcher(Matcher, bot, event, state) + await _run_matcher(Matcher, bot, event, state, stack, dependency_cache) -async def _run_matcher(Matcher: Type[Matcher], bot: "Bot", event: "Event", - state: T_State) -> None: +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: logger.info(f"Event will be handled by {Matcher}") matcher = Matcher() coros = list( - map(lambda x: x(matcher=matcher, bot=bot, event=event, state=state), + map( + lambda x: x(matcher=matcher, + bot=bot, + event=event, + state=state, + _stack=stack, + _dependency_cache=dependency_cache), _run_preprocessors)) if coros: try: @@ -191,7 +176,10 @@ async def _run_matcher(Matcher: Type[Matcher], bot: "Bot", event: "Event", exception=exception, bot=bot, event=event, - state=state), _run_postprocessors)) + state=state, + _stack=stack, + _dependency_cache=dependency_cache), + _run_postprocessors)) if coros: try: await asyncio.gather(*coros) @@ -232,12 +220,17 @@ async def handle_event(bot: "Bot", event: "Event") -> None: if show_log: logger.opt(colors=True).success(log_msg) - state = {} + state: Dict[Any, Any] = {} + dependency_cache: T_DependencyCache = {} - # TODO async with AsyncExitStack() as stack: coros = list( - map(lambda x: x(bot=bot, event=event, state=state), + map( + lambda x: x(bot=bot, + event=event, + state=state, + _stack=stack, + _dependency_cache=dependency_cache), _event_preprocessors)) if coros: try: @@ -286,7 +279,12 @@ async def handle_event(bot: "Bot", event: "Event") -> None: ) coros = list( - map(lambda x: x(bot=bot, event=event, state=state), + map( + lambda x: x(bot=bot, + event=event, + state=state, + _stack=stack, + _dependency_cache=dependency_cache), _event_postprocessors)) if coros: try: diff --git a/nonebot/permission.py b/nonebot/permission.py index 7564bb13..b83be58a 100644 --- a/nonebot/permission.py +++ b/nonebot/permission.py @@ -10,9 +10,12 @@ r""" """ import asyncio -from typing import Union, Callable, NoReturn, Optional, Awaitable +from contextlib import AsyncExitStack +from typing import Any, Dict, List, Type, Union, Callable, NoReturn, Optional -from nonebot.utils import run_sync +from nonebot import params +from nonebot.handler import Handler +from nonebot.dependencies import Param from nonebot.adapters import Bot, Event from nonebot.typing import T_PermissionChecker @@ -34,14 +37,23 @@ class Permission: """ __slots__ = ("checkers",) - def __init__(self, *checkers: Callable[[Bot, Event], - Awaitable[bool]]) -> None: + HANDLER_PARAM_TYPES: List[Type[Param]] = [ + params.BotParam, params.EventParam + ] + + def __init__(self, + *checkers: T_PermissionChecker, + dependency_overrides_provider: Optional[Any] = None) -> None: """ :参数: - * ``*checkers: Callable[[Bot, Event], Awaitable[bool]]``: **异步** PermissionChecker + * ``*checkers: T_PermissionChecker``: PermissionChecker """ - self.checkers = set(checkers) + self.checkers = set( + Handler(checker, + allow_types=self.HANDLER_PARAM_TYPES, + dependency_overrides_provider=dependency_overrides_provider) + for checker in checkers) """ :说明: @@ -49,10 +61,16 @@ class Permission: :类型: - * ``Set[Callable[[Bot, Event], Awaitable[bool]]]`` + * ``Set[Handler]`` """ - async def __call__(self, bot: Bot, event: Event) -> bool: + async def __call__( + self, + bot: Bot, + event: Event, + stack: Optional[AsyncExitStack] = None, + dependency_cache: Optional[Dict[Callable[..., Any], + Any]] = None) -> bool: """ :说明: @@ -62,6 +80,8 @@ class Permission: * ``bot: Bot``: Bot 对象 * ``event: Event``: Event 对象 + * ``stack: Optional[AsyncExitStack]``: 异步上下文栈 + * ``dependency_cache: Optional[Dict[Callable[..., Any], Any]]``: 依赖缓存 :返回: @@ -70,7 +90,11 @@ class Permission: if not self.checkers: return True results = await asyncio.gather( - *map(lambda c: c(bot, event), self.checkers)) + checker(bot=bot, + event=event, + _stack=stack, + _dependency_cache=dependency_cache) + for checker in self.checkers) return any(results) def __and__(self, other) -> NoReturn: @@ -79,16 +103,12 @@ class Permission: def __or__( self, other: Optional[Union["Permission", T_PermissionChecker]]) -> "Permission": - checkers = self.checkers.copy() if other is None: return self elif isinstance(other, Permission): - checkers |= other.checkers - elif asyncio.iscoroutinefunction(other): - checkers.add(other) # type: ignore + return Permission(*self.checkers, *other.checkers) else: - checkers.add(run_sync(other)) - return Permission(*checkers) + return Permission(*self.checkers, other) async def _message(bot: Bot, event: Event) -> bool: diff --git a/nonebot/plugin/on.py b/nonebot/plugin/on.py index 1b2cf16e..5b0f2979 100644 --- a/nonebot/plugin/on.py +++ b/nonebot/plugin/on.py @@ -434,8 +434,7 @@ def on_shell_command(cmd: Union[str, Tuple[str, ...]], message = event.get_message() segment = message.pop(0) new_message = message.__class__( - str(segment) - [len(state["_prefix"]["raw_command"]):].strip()) # type: ignore + str(segment)[len(state[PREFIX_KEY][RAW_CMD_KEY]):].strip()) for new_segment in reversed(new_message): message.insert(0, new_segment) diff --git a/nonebot/rule.py b/nonebot/rule.py index 3a1c2c33..c2ac4401 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -17,15 +17,15 @@ from argparse import Namespace from contextlib import AsyncExitStack from typing_extensions import TypedDict from argparse import ArgumentParser as ArgParser -from typing import (Any, Dict, Tuple, Union, Callable, NoReturn, Optional, - Sequence, Awaitable) +from typing import (Any, Dict, List, Type, Tuple, Union, Callable, NoReturn, + Optional, Sequence) from pygtrie import CharTrie from nonebot.log import logger -from nonebot.utils import run_sync from nonebot.handler import Handler from nonebot import params, get_driver +from nonebot.dependencies import Param from nonebot.exception import ParserExit from nonebot.typing import T_State, T_RuleChecker from nonebot.adapters import Bot, Event, MessageSegment @@ -64,11 +64,13 @@ class Rule: """ __slots__ = ("checkers",) - HANDLER_PARAM_TYPES = [ + HANDLER_PARAM_TYPES: List[Type[Param]] = [ params.BotParam, params.EventParam, params.StateParam ] - def __init__(self, *checkers: T_RuleChecker) -> None: + def __init__(self, + *checkers: T_RuleChecker, + dependency_overrides_provider: Optional[Any] = None) -> None: """ :参数: @@ -78,7 +80,7 @@ class Rule: self.checkers = set( Handler(checker, allow_types=self.HANDLER_PARAM_TYPES, - dependency_overrides_provider=get_driver()) + dependency_overrides_provider=dependency_overrides_provider) for checker in checkers) """ :说明: @@ -108,11 +110,15 @@ class Rule: * ``bot: Bot``: Bot 对象 * ``event: Event``: Event 对象 * ``state: T_State``: 当前 State + * ``stack: Optional[AsyncExitStack]``: 异步上下文栈 + * ``dependency_cache: Optional[Dict[Callable[..., Any], Any]]``: 依赖缓存 :返回: - ``bool`` """ + if not self.checkers: + return True results = await asyncio.gather( checker(bot=bot, event=event, @@ -126,10 +132,9 @@ class Rule: if other is None: return self elif isinstance(other, Rule): - checkers = [*self.checkers, *other.checkers] + return Rule(*self.checkers, *other.checkers) else: - checkers = [*self.checkers, other] - return Rule(*checkers) + return Rule(*self.checkers, other) def __or__(self, other) -> NoReturn: raise RuntimeError("Or operation between rules is not allowed.") diff --git a/nonebot/typing.py b/nonebot/typing.py index 337ff426..ac5a2335 100644 --- a/nonebot/typing.py +++ b/nonebot/typing.py @@ -22,7 +22,6 @@ from typing import (TYPE_CHECKING, Any, Dict, Union, TypeVar, Callable, NoReturn, Optional, Awaitable) if TYPE_CHECKING: - from nonebot.matcher import Matcher from nonebot.adapters import Bot, Event from nonebot.permission import Permission @@ -90,33 +89,60 @@ T_CalledAPIHook = Callable[ ``bot.call_api`` 后执行的函数,参数分别为 bot, exception, api, data, result """ -T_EventPreProcessor = Callable[..., Awaitable[None]] +T_EventPreProcessor = Callable[..., Union[None, Awaitable[None]]] """ -:类型: ``Callable[[Bot, Event, T_State], Awaitable[None]]`` +:类型: ``Callable[..., Union[None, Awaitable[None]]]`` + +:依赖参数: + + * ``BotParam``: Bot 对象 + * ``EventParam``: Event 对象 + * ``StateParam``: State 对象 :说明: 事件预处理函数 EventPreProcessor 类型 """ -T_EventPostProcessor = Callable[..., Awaitable[None]] +T_EventPostProcessor = Callable[..., Union[None, Awaitable[None]]] """ -:类型: ``Callable[[Bot, Event, T_State], Awaitable[None]]`` +:类型: ``Callable[..., Union[None, Awaitable[None]]]`` + +:依赖参数: + + * ``BotParam``: Bot 对象 + * ``EventParam``: Event 对象 + * ``StateParam``: State 对象 :说明: 事件预处理函数 EventPostProcessor 类型 """ -T_RunPreProcessor = Callable[..., Awaitable[None]] +T_RunPreProcessor = Callable[..., Union[None, Awaitable[None]]] """ -:类型: ``Callable[[Matcher, Bot, Event, T_State], Awaitable[None]]`` +:类型: ``Callable[..., Union[None, Awaitable[None]]]`` + +:依赖参数: + + * ``BotParam``: Bot 对象 + * ``EventParam``: Event 对象 + * ``StateParam``: State 对象 + * ``MatcherParam``: Matcher 对象 :说明: 事件响应器运行前预处理函数 RunPreProcessor 类型 """ -T_RunPostProcessor = Callable[..., Awaitable[None]] +T_RunPostProcessor = Callable[..., Union[None, Awaitable[None]]] """ -:类型: ``Callable[[Matcher, Optional[Exception], Bot, Event, T_State], Awaitable[None]]`` +:类型: ``Callable[..., Union[None, Awaitable[None]]]`` + +:依赖参数: + + * ``BotParam``: Bot 对象 + * ``EventParam``: Event 对象 + * ``StateParam``: State 对象 + * ``MatcherParam``: Matcher 对象 + * ``ExceptionParam``: 异常对象(可能为 None) :说明: @@ -127,28 +153,45 @@ T_RuleChecker = Callable[..., Union[bool, Awaitable[bool]]] """ :类型: ``Callable[..., Union[bool, Awaitable[bool]]]`` +:依赖参数: + + * ``BotParam``: Bot 对象 + * ``EventParam``: Event 对象 + * ``StateParam``: State 对象 + :说明: RuleChecker 即判断是否响应事件的处理函数。 """ -T_PermissionChecker = Callable[["Bot", "Event"], Union[bool, Awaitable[bool]]] +T_PermissionChecker = Callable[..., Union[bool, Awaitable[bool]]] """ -:类型: ``Callable[[Bot, Event], Union[bool, Awaitable[bool]]]`` +:类型: ``Callable[..., Union[bool, Awaitable[bool]]]`` + +:依赖参数: + + * ``BotParam``: Bot 对象 + * ``EventParam``: Event 对象 :说明: RuleChecker 即判断是否响应消息的处理函数。 """ -T_Handler = Callable[..., Union[Awaitable[None], Awaitable[NoReturn]]] +T_Handler = Callable[..., Any] """ -:类型: - - * ``Callable[..., Union[Awaitable[None], Awaitable[NoReturn]]]`` +:类型: ``Callable[..., Any]`` :说明: - Handler 即事件的处理函数。 + Handler 处理函数。 +""" +T_DependencyCache = Dict[T_Handler, Any] +""" +:类型: ``Dict[T_Handler, Any]`` + +:说明: + + 依赖缓存, 用于存储依赖函数的返回值 """ T_ArgsParser = Callable[["Bot", "Event", T_State], Union[Awaitable[None], Awaitable[NoReturn]]] From d22630e7686ea10459022699e3d8ee15d1c6c467 Mon Sep 17 00:00:00 2001 From: nonebot Date: Sun, 21 Nov 2021 04:37:46 +0000 Subject: [PATCH 15/21] :memo: update api docs --- docs/api/matcher.md | 4 +- docs/api/message.md | 69 ------------------------- docs/api/permission.md | 14 +++-- docs/api/rule.md | 8 ++- docs/api/typing.md | 115 ++++++++++++++++++++++++++++++++++++++--- 5 files changed, 126 insertions(+), 84 deletions(-) diff --git a/docs/api/matcher.md b/docs/api/matcher.md index 5cd6b03f..d520c8b1 100644 --- a/docs/api/matcher.md +++ b/docs/api/matcher.md @@ -348,7 +348,7 @@ sidebarDepth: 0 -### _async classmethod_ `check_perm(bot, event)` +### _async classmethod_ `check_perm(bot, event, stack=None, dependency_cache=None)` * **说明** @@ -374,7 +374,7 @@ sidebarDepth: 0 -### _async classmethod_ `check_rule(bot, event, state)` +### _async classmethod_ `check_rule(bot, event, state, stack=None, dependency_cache=None)` * **说明** diff --git a/docs/api/message.md b/docs/api/message.md index 5bd6c332..645ac8cd 100644 --- a/docs/api/message.md +++ b/docs/api/message.md @@ -19,21 +19,6 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供 -* **参数** - - 事件预处理函数接收三个参数。 - - - * `bot: Bot`: Bot 对象 - - - * `event: Event`: Event 对象 - - - * `state: T_State`: 当前 State - - - ## `event_postprocessor(func)` @@ -43,21 +28,6 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供 -* **参数** - - 事件后处理函数接收三个参数。 - - - * `bot: Bot`: Bot 对象 - - - * `event: Event`: Event 对象 - - - * `state: T_State`: 当前事件运行前 State - - - ## `run_preprocessor(func)` @@ -67,24 +37,6 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供 -* **参数** - - 运行预处理函数接收四个参数。 - - - * `matcher: Matcher`: 当前要运行的事件响应器 - - - * `bot: Bot`: Bot 对象 - - - * `event: Event`: Event 对象 - - - * `state: T_State`: 当前 State - - - ## `run_postprocessor(func)` @@ -94,27 +46,6 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供 -* **参数** - - 运行后处理函数接收五个参数。 - - - * `matcher: Matcher`: 运行完毕的事件响应器 - - - * `exception: Optional[Exception]`: 事件响应器运行错误(如果存在) - - - * `bot: Bot`: Bot 对象 - - - * `event: Event`: Event 对象 - - - * `state: T_State`: 当前 State - - - ## _async_ `handle_event(bot, event)` diff --git a/docs/api/permission.md b/docs/api/permission.md index e99b7e8b..3faf39d8 100644 --- a/docs/api/permission.md +++ b/docs/api/permission.md @@ -36,13 +36,13 @@ Permission(async_function, run_sync(sync_function)) ``` -### `__init__(*checkers)` +### `__init__(*checkers, dependency_overrides_provider=None)` * **参数** - * `*checkers: Callable[[Bot, Event], Awaitable[bool]]`: **异步** PermissionChecker + * `*checkers: T_PermissionChecker`: PermissionChecker @@ -58,11 +58,11 @@ Permission(async_function, run_sync(sync_function)) * **类型** - * `Set[Callable[[Bot, Event], Awaitable[bool]]]` + * `Set[Handler]` -### _async_ `__call__(bot, event)` +### _async_ `__call__(bot, event, stack=None, dependency_cache=None)` * **说明** @@ -80,6 +80,12 @@ Permission(async_function, run_sync(sync_function)) * `event: Event`: Event 对象 + * `stack: Optional[AsyncExitStack]`: 异步上下文栈 + + + * `dependency_cache: Optional[Dict[Callable[..., Any], Any]]`: 依赖缓存 + + * **返回** diff --git a/docs/api/rule.md b/docs/api/rule.md index 8a930b5b..f11525dc 100644 --- a/docs/api/rule.md +++ b/docs/api/rule.md @@ -36,7 +36,7 @@ Rule(async_function, run_sync(sync_function)) ``` -### `__init__(*checkers)` +### `__init__(*checkers, dependency_overrides_provider=None)` * **参数** @@ -83,6 +83,12 @@ Rule(async_function, run_sync(sync_function)) * `state: T_State`: 当前 State + * `stack: Optional[AsyncExitStack]`: 异步上下文栈 + + + * `dependency_cache: Optional[Dict[Callable[..., Any], Any]]`: 依赖缓存 + + * **返回** diff --git a/docs/api/typing.md b/docs/api/typing.md index 08de8de4..403c7f53 100644 --- a/docs/api/typing.md +++ b/docs/api/typing.md @@ -115,7 +115,20 @@ sidebarDepth: 0 * **类型** - `Callable[[Bot, Event, T_State], Awaitable[None]]` + `Callable[..., Union[None, Awaitable[None]]]` + + + +* **依赖参数** + + + * `BotParam`: Bot 对象 + + + * `EventParam`: Event 对象 + + + * `StateParam`: State 对象 @@ -131,7 +144,20 @@ sidebarDepth: 0 * **类型** - `Callable[[Bot, Event, T_State], Awaitable[None]]` + `Callable[..., Union[None, Awaitable[None]]]` + + + +* **依赖参数** + + + * `BotParam`: Bot 对象 + + + * `EventParam`: Event 对象 + + + * `StateParam`: State 对象 @@ -147,7 +173,23 @@ sidebarDepth: 0 * **类型** - `Callable[[Matcher, Bot, Event, T_State], Awaitable[None]]` + `Callable[..., Union[None, Awaitable[None]]]` + + + +* **依赖参数** + + + * `BotParam`: Bot 对象 + + + * `EventParam`: Event 对象 + + + * `StateParam`: State 对象 + + + * `MatcherParam`: Matcher 对象 @@ -163,7 +205,26 @@ sidebarDepth: 0 * **类型** - `Callable[[Matcher, Optional[Exception], Bot, Event, T_State], Awaitable[None]]` + `Callable[..., Union[None, Awaitable[None]]]` + + + +* **依赖参数** + + + * `BotParam`: Bot 对象 + + + * `EventParam`: Event 对象 + + + * `StateParam`: State 对象 + + + * `MatcherParam`: Matcher 对象 + + + * `ExceptionParam`: 异常对象(可能为 None) @@ -183,6 +244,19 @@ sidebarDepth: 0 +* **依赖参数** + + + * `BotParam`: Bot 对象 + + + * `EventParam`: Event 对象 + + + * `StateParam`: State 对象 + + + * **说明** RuleChecker 即判断是否响应事件的处理函数。 @@ -195,7 +269,17 @@ sidebarDepth: 0 * **类型** - `Callable[[Bot, Event], Union[bool, Awaitable[bool]]]` + `Callable[..., Union[bool, Awaitable[bool]]]` + + + +* **依赖参数** + + + * `BotParam`: Bot 对象 + + + * `EventParam`: Event 对象 @@ -211,14 +295,29 @@ sidebarDepth: 0 * **类型** - - * `Callable[..., Union[Awaitable[None], Awaitable[NoReturn]]]` + `Callable[..., Any]` * **说明** - Handler 即事件的处理函数。 + Handler 处理函数。 + + + + +## `T_DependencyCache` + + +* **类型** + + `Dict[T_Handler, Any]` + + + +* **说明** + + 依赖缓存, 用于存储依赖函数的返回值 From 75d4cd95658053f6b5cf3a7499489e44afde40aa Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 21 Nov 2021 15:46:48 +0800 Subject: [PATCH 16/21] :bug: fix cache concurrency --- nonebot/dependencies/__init__.py | 57 +++++++------- nonebot/handler.py | 2 +- nonebot/message.py | 5 +- nonebot/permission.py | 30 ++++---- nonebot/rule.py | 23 +++--- nonebot/utils.py | 76 ++++++++++++++++++- .../nonebot/adapters/cqhttp/permission.py | 25 +++--- tests/test_plugins/test_depends.py | 17 ++++- 8 files changed, 162 insertions(+), 73 deletions(-) diff --git a/nonebot/dependencies/__init__.py b/nonebot/dependencies/__init__.py index dd27ce9f..f863d5d9 100644 --- a/nonebot/dependencies/__init__.py +++ b/nonebot/dependencies/__init__.py @@ -21,8 +21,11 @@ from .models import Dependent as Dependent from nonebot.exception import SkippedException from .models import DependsWrapper as DependsWrapper from nonebot.typing import T_Handler, T_DependencyCache -from nonebot.utils import (run_sync, is_gen_callable, run_sync_ctx_manager, - is_async_gen_callable, is_coroutine_callable) +from nonebot.utils import (CacheLock, run_sync, is_gen_callable, + run_sync_ctx_manager, is_async_gen_callable, + is_coroutine_callable) + +cache_lock = CacheLock() class CustomConfig(BaseConfig): @@ -93,7 +96,7 @@ def get_dependent(*, break else: raise ValueError( - f"Unknown parameter {param_name} for funcction {func} with type {param.annotation}" + f"Unknown parameter {param_name} for function {func} with type {param.annotation}" ) annotation: Any = Any @@ -122,7 +125,7 @@ async def solve_dependencies( _dependency_cache: Optional[T_DependencyCache] = None, **params: Any) -> Tuple[Dict[str, Any], T_DependencyCache]: values: Dict[str, Any] = {} - dependency_cache = _dependency_cache or {} + dependency_cache = {} if _dependency_cache is None else _dependency_cache # solve sub dependencies sub_dependent: Dependent @@ -151,35 +154,37 @@ async def solve_dependencies( solved_result = await solve_dependencies( _dependent=use_sub_dependant, _dependency_overrides_provider=_dependency_overrides_provider, - dependency_cache=dependency_cache, + _dependency_cache=dependency_cache, **params) sub_values, sub_dependency_cache = solved_result # update cache? - dependency_cache.update(sub_dependency_cache) + # dependency_cache.update(sub_dependency_cache) # run dependency function - if sub_dependent.use_cache and sub_dependent.cache_key in dependency_cache: - solved = dependency_cache[sub_dependent.cache_key] - elif is_gen_callable(func) or is_async_gen_callable(func): - assert isinstance( - _stack, AsyncExitStack - ), "Generator dependency should be called in context" - if is_gen_callable(func): - cm = run_sync_ctx_manager(contextmanager(func)(**sub_values)) + async with cache_lock: + if sub_dependent.use_cache and sub_dependent.cache_key in dependency_cache: + solved = dependency_cache[sub_dependent.cache_key] + elif is_gen_callable(func) or is_async_gen_callable(func): + assert isinstance( + _stack, AsyncExitStack + ), "Generator dependency should be called in context" + if is_gen_callable(func): + cm = run_sync_ctx_manager( + contextmanager(func)(**sub_values)) + else: + cm = asynccontextmanager(func)(**sub_values) + solved = await _stack.enter_async_context(cm) + elif is_coroutine_callable(func): + solved = await func(**sub_values) else: - cm = asynccontextmanager(func)(**sub_values) - solved = await _stack.enter_async_context(cm) - elif is_coroutine_callable(func): - solved = await func(**sub_values) - else: - solved = await run_sync(func)(**sub_values) + solved = await run_sync(func)(**sub_values) - # parameter dependency - if sub_dependent.name is not None: - values[sub_dependent.name] = solved - # save current dependency to cache - if sub_dependent.cache_key not in dependency_cache: - dependency_cache[sub_dependent.cache_key] = solved + # parameter dependency + if sub_dependent.name is not None: + values[sub_dependent.name] = solved + # save current dependency to cache + if sub_dependent.cache_key not in dependency_cache: + dependency_cache[sub_dependent.cache_key] = solved # usual dependency for field in _dependent.params: diff --git a/nonebot/handler.py b/nonebot/handler.py index d6df70dd..a39003b1 100644 --- a/nonebot/handler.py +++ b/nonebot/handler.py @@ -80,7 +80,7 @@ class Handler: _dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None, **params) -> Any: - values, cache = await solve_dependencies( + values, _ = await solve_dependencies( _dependent=self.dependent, _stack=_stack, _sub_dependents=[ diff --git a/nonebot/message.py b/nonebot/message.py index 66379e77..3c00423b 100644 --- a/nonebot/message.py +++ b/nonebot/message.py @@ -163,7 +163,7 @@ async def _run_matcher( try: logger.debug(f"Running matcher {matcher}") - await matcher.run(bot, event, state) + await matcher.run(bot, event, state, stack, dependency_cache) except Exception as e: logger.opt(colors=True, exception=e).error( f"Running matcher {matcher} failed." @@ -260,7 +260,8 @@ async def handle_event(bot: "Bot", event: "Event") -> None: logger.debug(f"Checking for matchers in priority {priority}...") pending_tasks = [ - _check_matcher(priority, matcher, bot, event, state.copy()) + _check_matcher(priority, matcher, bot, event, state.copy(), + stack, dependency_cache) for matcher in matchers[priority] ] diff --git a/nonebot/permission.py b/nonebot/permission.py index b83be58a..48781e32 100644 --- a/nonebot/permission.py +++ b/nonebot/permission.py @@ -42,17 +42,19 @@ class Permission: ] def __init__(self, - *checkers: T_PermissionChecker, + *checkers: Union[T_PermissionChecker, Handler], dependency_overrides_provider: Optional[Any] = None) -> None: """ :参数: - * ``*checkers: T_PermissionChecker``: PermissionChecker + * ``*checkers: Union[T_PermissionChecker, Handler]``: PermissionChecker """ + self.checkers = set( - Handler(checker, - allow_types=self.HANDLER_PARAM_TYPES, - dependency_overrides_provider=dependency_overrides_provider) + checker if isinstance(checker, Handler) else Handler( + checker, + allow_types=self.HANDLER_PARAM_TYPES, + dependency_overrides_provider=dependency_overrides_provider) for checker in checkers) """ :说明: @@ -90,11 +92,11 @@ class Permission: if not self.checkers: return True results = await asyncio.gather( - checker(bot=bot, - event=event, - _stack=stack, - _dependency_cache=dependency_cache) - for checker in self.checkers) + *(checker(bot=bot, + event=event, + _stack=stack, + _dependency_cache=dependency_cache) + for checker in self.checkers)) return any(results) def __and__(self, other) -> NoReturn: @@ -111,19 +113,19 @@ class Permission: return Permission(*self.checkers, other) -async def _message(bot: Bot, event: Event) -> bool: +async def _message(event: Event) -> bool: return event.get_type() == "message" -async def _notice(bot: Bot, event: Event) -> bool: +async def _notice(event: Event) -> bool: return event.get_type() == "notice" -async def _request(bot: Bot, event: Event) -> bool: +async def _request(event: Event) -> bool: return event.get_type() == "request" -async def _metaevent(bot: Bot, event: Event) -> bool: +async def _metaevent(event: Event) -> bool: return event.get_type() == "meta_event" diff --git a/nonebot/rule.py b/nonebot/rule.py index c2ac4401..177b59d7 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -69,18 +69,19 @@ class Rule: ] def __init__(self, - *checkers: T_RuleChecker, + *checkers: Union[T_RuleChecker, Handler], dependency_overrides_provider: Optional[Any] = None) -> None: """ :参数: - * ``*checkers: T_RuleChecker``: RuleChecker + * ``*checkers: Union[T_RuleChecker, Handler]``: RuleChecker """ self.checkers = set( - Handler(checker, - allow_types=self.HANDLER_PARAM_TYPES, - dependency_overrides_provider=dependency_overrides_provider) + checker if isinstance(checker, Handler) else Handler( + checker, + allow_types=self.HANDLER_PARAM_TYPES, + dependency_overrides_provider=dependency_overrides_provider) for checker in checkers) """ :说明: @@ -120,12 +121,12 @@ class Rule: if not self.checkers: return True results = await asyncio.gather( - checker(bot=bot, - event=event, - state=state, - _stack=stack, - _dependency_cache=dependency_cache) - for checker in self.checkers) + *(checker(bot=bot, + event=event, + state=state, + _stack=stack, + _dependency_cache=dependency_cache) + for checker in self.checkers)) return all(results) def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule": diff --git a/nonebot/utils.py b/nonebot/utils.py index 66e905e6..32eca0db 100644 --- a/nonebot/utils.py +++ b/nonebot/utils.py @@ -2,12 +2,13 @@ import re import json import asyncio import inspect +import collections import dataclasses from functools import wraps, partial from contextlib import asynccontextmanager from typing_extensions import GenericAlias # type: ignore from typing_extensions import ParamSpec, get_args, get_origin -from typing import (Any, Type, Tuple, Union, TypeVar, Callable, Optional, +from typing import (Any, Type, Deque, Tuple, Union, TypeVar, Callable, Optional, Awaitable, AsyncGenerator, ContextManager) from nonebot.log import logger @@ -120,6 +121,79 @@ def get_name(obj: Any) -> str: return obj.__class__.__name__ +class CacheLock: + + def __init__(self): + self._waiters: Optional[Deque[asyncio.Future]] = None + self._locked = False + + def __repr__(self): + extra = "locked" if self._locked else "unlocked" + if self._waiters: + extra = f"{extra}, waiters: {len(self._waiters)}" + return f"<{self.__class__.__name__} [{extra}]>" + + async def __aenter__(self): + await self.acquire() + return None + + async def __aexit__(self, exc_type, exc, tb): + self.release() + + def locked(self): + return self._locked + + async def acquire(self): + if (not self._locked and (self._waiters is None or + all(w.cancelled() for w in self._waiters))): + self._locked = True + return True + + if self._waiters is None: + self._waiters = collections.deque() + + loop = asyncio.get_running_loop() + future = loop.create_future() + self._waiters.append(future) + + # Finally block should be called before the CancelledError + # handling as we don't want CancelledError to call + # _wake_up_first() and attempt to wake up itself. + try: + try: + await future + finally: + self._waiters.remove(future) + except asyncio.CancelledError: + if not self._locked: + self._wake_up_first() + raise + + self._locked = True + return True + + def release(self): + if self._locked: + self._locked = False + self._wake_up_first() + else: + raise RuntimeError("Lock is not acquired.") + + def _wake_up_first(self): + if not self._waiters: + return + try: + future = next(iter(self._waiters)) + except StopIteration: + return + + # .done() necessarily means that a waiter will wake up later on and + # either take the lock, or, if it was cancelled and lock wasn't + # taken already, will hit this again and wake up a new waiter. + if not future.done(): + future.set_result(True) + + class DataclassEncoder(json.JSONEncoder): """ :说明: diff --git a/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/permission.py b/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/permission.py index 1d3b3f36..09ea7b7a 100644 --- a/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/permission.py +++ b/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/permission.py @@ -1,26 +1,21 @@ -from typing import TYPE_CHECKING - +from nonebot.adapters import Event from nonebot.permission import Permission - -from .event import PrivateMessageEvent, GroupMessageEvent - -if TYPE_CHECKING: - from nonebot.adapters import Bot, Event +from .event import GroupMessageEvent, PrivateMessageEvent -async def _private(bot: "Bot", event: "Event") -> bool: +async def _private(event: Event) -> bool: return isinstance(event, PrivateMessageEvent) -async def _private_friend(bot: "Bot", event: "Event") -> bool: +async def _private_friend(event: Event) -> bool: return isinstance(event, PrivateMessageEvent) and event.sub_type == "friend" -async def _private_group(bot: "Bot", event: "Event") -> bool: +async def _private_group(event: Event) -> bool: return isinstance(event, PrivateMessageEvent) and event.sub_type == "group" -async def _private_other(bot: "Bot", event: "Event") -> bool: +async def _private_other(event: Event) -> bool: return isinstance(event, PrivateMessageEvent) and event.sub_type == "other" @@ -42,20 +37,20 @@ PRIVATE_OTHER = Permission(_private_other) """ -async def _group(bot: "Bot", event: "Event") -> bool: +async def _group(event: Event) -> bool: return isinstance(event, GroupMessageEvent) -async def _group_member(bot: "Bot", event: "Event") -> bool: +async def _group_member(event: Event) -> bool: return isinstance(event, GroupMessageEvent) and event.sender.role == "member" -async def _group_admin(bot: "Bot", event: "Event") -> bool: +async def _group_admin(event: Event) -> bool: return isinstance(event, GroupMessageEvent) and event.sender.role == "admin" -async def _group_owner(bot: "Bot", event: "Event") -> bool: +async def _group_owner(event: Event) -> bool: return isinstance(event, GroupMessageEvent) and event.sender.role == "owner" diff --git a/tests/test_plugins/test_depends.py b/tests/test_plugins/test_depends.py index c604169b..e23c9c4e 100644 --- a/tests/test_plugins/test_depends.py +++ b/tests/test_plugins/test_depends.py @@ -1,11 +1,12 @@ -from nonebot import on_command from nonebot.log import logger from nonebot.dependencies import Depends +from nonebot import on_command, on_message test = on_command("123") def depend(state: dict): + print("==== depends running =====") return state @@ -13,5 +14,15 @@ def depend(state: dict): @test.got("b", prompt="b") @test.receive() @test.got("c", prompt="c") -async def _(state: dict = Depends(depend)): - logger.info(f"=======, {state}") +async def _(x: dict = Depends(depend)): + logger.info(f"=======, {x}") + + +test_cache1 = on_message() +test_cache2 = on_message() + + +@test_cache1.handle() +@test_cache2.handle() +async def _(x: dict = Depends(depend)): + logger.info(f"======= test, {x}") From a864b36e9fa02765f1d5e8bd53220961e7a3e1eb Mon Sep 17 00:00:00 2001 From: nonebot Date: Sun, 21 Nov 2021 07:48:02 +0000 Subject: [PATCH 17/21] :memo: update api docs --- docs/api/permission.md | 2 +- docs/api/rule.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/permission.md b/docs/api/permission.md index 3faf39d8..18ba75cb 100644 --- a/docs/api/permission.md +++ b/docs/api/permission.md @@ -42,7 +42,7 @@ Permission(async_function, run_sync(sync_function)) * **参数** - * `*checkers: T_PermissionChecker`: PermissionChecker + * `*checkers: Union[T_PermissionChecker, Handler]`: PermissionChecker diff --git a/docs/api/rule.md b/docs/api/rule.md index f11525dc..ee33cd91 100644 --- a/docs/api/rule.md +++ b/docs/api/rule.md @@ -42,7 +42,7 @@ Rule(async_function, run_sync(sync_function)) * **参数** - * `*checkers: T_RuleChecker`: RuleChecker + * `*checkers: Union[T_RuleChecker, Handler]`: RuleChecker From 760ac693c0ab8a17e2a3161c44d5cee595e29078 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 21 Nov 2021 16:12:36 +0800 Subject: [PATCH 18/21] :fire: remove dependency override provider --- nonebot/config.py | 3 +-- nonebot/dependencies/__init__.py | 18 +------------- nonebot/dependencies/models.py | 2 +- nonebot/drivers/__init__.py | 5 ---- nonebot/handler.py | 6 +---- nonebot/matcher.py | 7 ++---- nonebot/message.py | 24 +++++-------------- nonebot/permission.py | 10 +++----- nonebot/plugins/echo.py | 2 +- nonebot/plugins/single_session.py | 6 ++--- nonebot/py.typed | 1 - nonebot/rule.py | 12 ++++------ nonebot/typing.py | 4 ++-- .../nonebot/adapters/cqhttp/event.py | 5 ++-- .../nonebot/adapters/cqhttp/exception.py | 13 ++++++---- 15 files changed, 35 insertions(+), 83 deletions(-) diff --git a/nonebot/config.py b/nonebot/config.py index 05baf53b..82fca0d3 100644 --- a/nonebot/config.py +++ b/nonebot/config.py @@ -67,8 +67,7 @@ class CustomEnvSettings(EnvSettingsSource): env_val = settings.__config__.json_loads(env_val) except ValueError as e: raise SettingsError( - f'error parsing JSON for "{env_name}"' # type: ignore - ) from e + f'error parsing JSON for "{env_name}"') from e d[field.alias] = env_val if env_file_vars: diff --git a/nonebot/dependencies/__init__.py b/nonebot/dependencies/__init__.py index f863d5d9..7d0138a8 100644 --- a/nonebot/dependencies/__init__.py +++ b/nonebot/dependencies/__init__.py @@ -121,7 +121,6 @@ async def solve_dependencies( _dependent: Dependent, _stack: Optional[AsyncExitStack] = None, _sub_dependents: Optional[List[Dependent]] = None, - _dependency_overrides_provider: Optional[Any] = None, _dependency_cache: Optional[T_DependencyCache] = None, **params: Any) -> Tuple[Dict[str, Any], T_DependencyCache]: values: Dict[str, Any] = {} @@ -136,24 +135,9 @@ async def solve_dependencies( sub_dependent.cache_key) func = sub_dependent.func - # dependency overrides - use_sub_dependant = sub_dependent - if (_dependency_overrides_provider and hasattr( - _dependency_overrides_provider, "dependency_overrides")): - original_call = sub_dependent.func - func = getattr(_dependency_overrides_provider, - "dependency_overrides", - {}).get(original_call, original_call) - use_sub_dependant = get_dependent( - func=func, - name=sub_dependent.name, - allow_types=sub_dependent.allow_types, - ) - # solve sub dependency with current cache solved_result = await solve_dependencies( - _dependent=use_sub_dependant, - _dependency_overrides_provider=_dependency_overrides_provider, + _dependent=sub_dependent, _dependency_cache=dependency_cache, **params) sub_values, sub_dependency_cache = solved_result diff --git a/nonebot/dependencies/models.py b/nonebot/dependencies/models.py index 4431d11c..36ee5af0 100644 --- a/nonebot/dependencies/models.py +++ b/nonebot/dependencies/models.py @@ -1,6 +1,6 @@ import abc import inspect -from typing import Any, List, Type, Callable, Optional +from typing import Any, List, Type, Optional from pydantic.fields import FieldInfo, ModelField diff --git a/nonebot/drivers/__init__.py b/nonebot/drivers/__init__.py index 74c42ff6..d08f6b0c 100644 --- a/nonebot/drivers/__init__.py +++ b/nonebot/drivers/__init__.py @@ -40,11 +40,6 @@ class Driver(abc.ABC): :类型: ``Set[T_BotDisconnectionHook]`` :说明: Bot 连接断开时执行的函数 """ - dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {} - """ - :类型: ``Dict[Callable[..., Any], Callable[..., Any]]`` - :说明: Depends 函数的替换表 - """ def __init__(self, env: Env, config: Config): """ diff --git a/nonebot/handler.py b/nonebot/handler.py index a39003b1..6203bebd 100644 --- a/nonebot/handler.py +++ b/nonebot/handler.py @@ -23,8 +23,7 @@ class Handler: *, name: Optional[str] = None, dependencies: Optional[List[DependsWrapper]] = None, - allow_types: Optional[List[Type[Param]]] = None, - dependency_overrides_provider: Optional[Any] = None): + allow_types: Optional[List[Type[Param]]] = None): """ :说明: @@ -36,7 +35,6 @@ class Handler: * ``name: Optional[str]``: 事件处理器名称。默认为函数名。 * ``dependencies: Optional[List[DependsWrapper]]``: 额外的非参数依赖注入。 * ``allow_types: Optional[List[Type[Param]]]``: 允许的参数类型。 - * ``dependency_overrides_provider: Optional[Any]``: 依赖注入覆盖提供者。 """ self.func = func """ @@ -63,7 +61,6 @@ class Handler: if dependencies: for depends in dependencies: self.cache_dependent(depends) - self.dependency_overrides_provider = dependency_overrides_provider self.dependent = get_dependent(func=func, allow_types=self.allow_types) def __repr__(self) -> str: @@ -87,7 +84,6 @@ class Handler: self.sub_dependents[dependency.dependency] # type: ignore for dependency in self.dependencies ], - _dependency_overrides_provider=self.dependency_overrides_provider, _dependency_cache=_dependency_cache, **params) diff --git a/nonebot/matcher.py b/nonebot/matcher.py index f68c78de..7eeda5ab 100644 --- a/nonebot/matcher.py +++ b/nonebot/matcher.py @@ -13,10 +13,10 @@ from contextlib import AsyncExitStack from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Callable, NoReturn, Optional) +from nonebot import params from nonebot.rule import Rule from nonebot.log import logger from nonebot.handler import Handler -from nonebot import params, get_driver from nonebot.dependencies import DependsWrapper from nonebot.permission import USER, Permission from nonebot.adapters import (Bot, Event, Message, MessageSegment, @@ -238,9 +238,7 @@ class Matcher(metaclass=MatcherMeta): permission or Permission(), "handlers": [ handler if isinstance(handler, Handler) else Handler( - handler, - dependency_overrides_provider=get_driver(), - allow_types=cls.HANDLER_PARAM_TYPES) + handler, allow_types=cls.HANDLER_PARAM_TYPES) for handler in handlers ] if handlers else [], "temp": @@ -372,7 +370,6 @@ class Matcher(metaclass=MatcherMeta): dependencies: Optional[List[DependsWrapper]] = None) -> Handler: handler_ = Handler(handler, dependencies=dependencies, - dependency_overrides_provider=get_driver(), allow_types=cls.HANDLER_PARAM_TYPES) cls.handlers.append(handler_) return handler_ diff --git a/nonebot/message.py b/nonebot/message.py index 3c00423b..bedf778c 100644 --- a/nonebot/message.py +++ b/nonebot/message.py @@ -8,13 +8,13 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供 import asyncio from datetime import datetime from contextlib import AsyncExitStack -from typing import TYPE_CHECKING, Any, Set, Dict, Type, Callable, Optional +from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional +from nonebot import params from nonebot.log import logger from nonebot.rule import TrieRule from nonebot.handler import Handler from nonebot.utils import escape_tag -from nonebot import params, get_driver from nonebot.matcher import Matcher, matchers from nonebot.exception import NoLogException, StopPropagation, IgnoredException from nonebot.typing import (T_State, T_DependencyCache, T_RunPreProcessor, @@ -45,10 +45,7 @@ def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor: 事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。 """ - _event_preprocessors.add( - Handler(func, - allow_types=EVENT_PCS_PARAMS, - dependency_overrides_provider=get_driver())) + _event_preprocessors.add(Handler(func, allow_types=EVENT_PCS_PARAMS)) return func @@ -58,10 +55,7 @@ def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor: 事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。 """ - _event_postprocessors.add( - Handler(func, - allow_types=EVENT_PCS_PARAMS, - dependency_overrides_provider=get_driver())) + _event_postprocessors.add(Handler(func, allow_types=EVENT_PCS_PARAMS)) return func @@ -71,10 +65,7 @@ def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor: 运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。 """ - _run_preprocessors.add( - Handler(func, - allow_types=RUN_PREPCS_PARAMS, - dependency_overrides_provider=get_driver())) + _run_preprocessors.add(Handler(func, allow_types=RUN_PREPCS_PARAMS)) return func @@ -84,10 +75,7 @@ def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor: 运行后处理。装饰一个函数,使它在每次事件响应器运行后执行。 """ - _run_postprocessors.add( - Handler(func, - allow_types=RUN_POSTPCS_PARAMS, - dependency_overrides_provider=get_driver())) + _run_postprocessors.add(Handler(func, allow_types=RUN_POSTPCS_PARAMS)) return func diff --git a/nonebot/permission.py b/nonebot/permission.py index 48781e32..e52058cb 100644 --- a/nonebot/permission.py +++ b/nonebot/permission.py @@ -2,7 +2,7 @@ r""" 权限 ==== -每个 ``Matcher`` 拥有一个 ``Permission`` ,其中是 **异步** ``PermissionChecker`` 的集合,只要有一个 ``PermissionChecker`` 检查结果为 ``True`` 时就会继续运行。 +每个 ``Matcher`` 拥有一个 ``Permission`` ,其中是 ``PermissionChecker`` 的集合,只要有一个 ``PermissionChecker`` 检查结果为 ``True`` 时就会继续运行。 \:\:\:tip 提示 ``PermissionChecker`` 既可以是 async function 也可以是 sync function @@ -41,9 +41,7 @@ class Permission: params.BotParam, params.EventParam ] - def __init__(self, - *checkers: Union[T_PermissionChecker, Handler], - dependency_overrides_provider: Optional[Any] = None) -> None: + def __init__(self, *checkers: Union[T_PermissionChecker, Handler]) -> None: """ :参数: @@ -52,9 +50,7 @@ class Permission: self.checkers = set( checker if isinstance(checker, Handler) else Handler( - checker, - allow_types=self.HANDLER_PARAM_TYPES, - dependency_overrides_provider=dependency_overrides_provider) + checker, allow_types=self.HANDLER_PARAM_TYPES) for checker in checkers) """ :说明: diff --git a/nonebot/plugins/echo.py b/nonebot/plugins/echo.py index 440aa4bf..763fd715 100644 --- a/nonebot/plugins/echo.py +++ b/nonebot/plugins/echo.py @@ -26,4 +26,4 @@ echo = on_command("echo", to_me()) @echo.handle() async def echo_escape(event: MessageEvent): - await say.send(message=event.get_message()) + await echo.send(message=event.get_message()) diff --git a/nonebot/plugins/single_session.py b/nonebot/plugins/single_session.py index 81a9a9d1..7bb4d52e 100644 --- a/nonebot/plugins/single_session.py +++ b/nonebot/plugins/single_session.py @@ -1,8 +1,6 @@ -from typing import Dict, Optional +from typing import Dict -from nonebot.typing import T_State -from nonebot.matcher import Matcher -from nonebot.adapters import Bot, Event +from nonebot.adapters import Event from nonebot.message import (IgnoredException, run_preprocessor, run_postprocessor) diff --git a/nonebot/py.typed b/nonebot/py.typed index 8b137891..e69de29b 100644 --- a/nonebot/py.typed +++ b/nonebot/py.typed @@ -1 +0,0 @@ - diff --git a/nonebot/rule.py b/nonebot/rule.py index 177b59d7..1621aef6 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -2,10 +2,10 @@ r""" 规则 ==== -每个事件响应器 ``Matcher`` 拥有一个匹配规则 ``Rule`` ,其中是 **异步** ``RuleChecker`` 的集合,只有当所有 ``RuleChecker`` 检查结果为 ``True`` 时继续运行。 +每个事件响应器 ``Matcher`` 拥有一个匹配规则 ``Rule`` ,其中是 ``RuleChecker`` 的集合,只有当所有 ``RuleChecker`` 检查结果为 ``True`` 时继续运行。 \:\:\:tip 提示 -``RuleChecker`` 既可以是 async function 也可以是 sync function,但在最终会被 ``nonebot.utils.run_sync`` 转换为 async function +``RuleChecker`` 既可以是 async function 也可以是 sync function \:\:\: """ @@ -68,9 +68,7 @@ class Rule: params.BotParam, params.EventParam, params.StateParam ] - def __init__(self, - *checkers: Union[T_RuleChecker, Handler], - dependency_overrides_provider: Optional[Any] = None) -> None: + def __init__(self, *checkers: Union[T_RuleChecker, Handler]) -> None: """ :参数: @@ -79,9 +77,7 @@ class Rule: """ self.checkers = set( checker if isinstance(checker, Handler) else Handler( - checker, - allow_types=self.HANDLER_PARAM_TYPES, - dependency_overrides_provider=dependency_overrides_provider) + checker, allow_types=self.HANDLER_PARAM_TYPES) for checker in checkers) """ :说明: diff --git a/nonebot/typing.py b/nonebot/typing.py index ac5a2335..3394cd6f 100644 --- a/nonebot/typing.py +++ b/nonebot/typing.py @@ -17,7 +17,7 @@ .. _typing: https://docs.python.org/3/library/typing.html """ -from collections.abc import Callable as BaseCallable + from typing import (TYPE_CHECKING, Any, Dict, Union, TypeVar, Callable, NoReturn, Optional, Awaitable) @@ -25,7 +25,7 @@ if TYPE_CHECKING: from nonebot.adapters import Bot, Event from nonebot.permission import Permission -T_Wrapped = TypeVar("T_Wrapped", bound=BaseCallable) +T_Wrapped = TypeVar("T_Wrapped", bound=Callable) def overrides(InterfaceClass: object): diff --git a/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/event.py b/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/event.py index cfa61315..690a8437 100644 --- a/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/event.py +++ b/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/event.py @@ -5,13 +5,12 @@ from typing import TYPE_CHECKING, List, Type, Optional from pydantic import BaseModel from pygtrie import StringTrie +from .message import Message from nonebot.typing import overrides from nonebot.utils import escape_tag -from nonebot.exception import NoLogException +from .exception import NoLogException from nonebot.adapters import Event as BaseEvent -from .message import Message - if TYPE_CHECKING: from .bot import Bot diff --git a/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/exception.py b/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/exception.py index af3ae6cd..4d3fd1e9 100644 --- a/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/exception.py +++ b/packages/nonebot-adapter-cqhttp/nonebot/adapters/cqhttp/exception.py @@ -1,9 +1,10 @@ from typing import Optional -from nonebot.exception import (AdapterException, ActionFailed as - BaseActionFailed, NetworkError as - BaseNetworkError, ApiNotAvailable as - BaseApiNotAvailable) +from nonebot.exception import AdapterException +from nonebot.exception import ActionFailed as BaseActionFailed +from nonebot.exception import NetworkError as BaseNetworkError +from nonebot.exception import NoLogException as BaseNoLogException +from nonebot.exception import ApiNotAvailable as BaseApiNotAvailable class CQHTTPAdapterException(AdapterException): @@ -12,6 +13,10 @@ class CQHTTPAdapterException(AdapterException): super().__init__("cqhttp") +class NoLogException(BaseNoLogException, CQHTTPAdapterException): + pass + + class ActionFailed(BaseActionFailed, CQHTTPAdapterException): """ :说明: From 9a29966e2d42eaee2ee86255fc480a7c51ee5593 Mon Sep 17 00:00:00 2001 From: nonebot Date: Sun, 21 Nov 2021 08:13:56 +0000 Subject: [PATCH 19/21] :memo: update api docs --- docs/api/drivers/README.md | 15 --------------- docs/api/handler.md | 5 +---- docs/api/permission.md | 4 ++-- docs/api/rule.md | 6 +++--- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/docs/api/drivers/README.md b/docs/api/drivers/README.md index d67b9bd8..9f8ee3ee 100644 --- a/docs/api/drivers/README.md +++ b/docs/api/drivers/README.md @@ -62,21 +62,6 @@ Driver 基类。 -### `dependency_overrides` - - -* **类型** - - `Dict[Callable[..., Any], Callable[..., Any]]` - - - -* **说明** - - Depends 函数的替换表 - - - ### `__init__(env, config)` diff --git a/docs/api/handler.md b/docs/api/handler.md index 8d36478b..6f319435 100644 --- a/docs/api/handler.md +++ b/docs/api/handler.md @@ -17,7 +17,7 @@ sidebarDepth: 0 事件处理器类。支持依赖注入。 -### `__init__(func, *, name=None, dependencies=None, allow_types=None, dependency_overrides_provider=None)` +### `__init__(func, *, name=None, dependencies=None, allow_types=None)` * **说明** @@ -41,9 +41,6 @@ sidebarDepth: 0 * `allow_types: Optional[List[Type[Param]]]`: 允许的参数类型。 - * `dependency_overrides_provider: Optional[Any]`: 依赖注入覆盖提供者。 - - ### `func` diff --git a/docs/api/permission.md b/docs/api/permission.md index 18ba75cb..f2ee1973 100644 --- a/docs/api/permission.md +++ b/docs/api/permission.md @@ -7,7 +7,7 @@ sidebarDepth: 0 ## 权限 -每个 `Matcher` 拥有一个 `Permission` ,其中是 **异步** `PermissionChecker` 的集合,只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。 +每个 `Matcher` 拥有一个 `Permission` ,其中是 `PermissionChecker` 的集合,只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。 :::tip 提示 `PermissionChecker` 既可以是 async function 也可以是 sync function @@ -36,7 +36,7 @@ Permission(async_function, run_sync(sync_function)) ``` -### `__init__(*checkers, dependency_overrides_provider=None)` +### `__init__(*checkers)` * **参数** diff --git a/docs/api/rule.md b/docs/api/rule.md index ee33cd91..872cb73d 100644 --- a/docs/api/rule.md +++ b/docs/api/rule.md @@ -7,10 +7,10 @@ sidebarDepth: 0 ## 规则 -每个事件响应器 `Matcher` 拥有一个匹配规则 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。 +每个事件响应器 `Matcher` 拥有一个匹配规则 `Rule` ,其中是 `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。 :::tip 提示 -`RuleChecker` 既可以是 async function 也可以是 sync function,但在最终会被 `nonebot.utils.run_sync` 转换为 async function +`RuleChecker` 既可以是 async function 也可以是 sync function ::: @@ -36,7 +36,7 @@ Rule(async_function, run_sync(sync_function)) ``` -### `__init__(*checkers, dependency_overrides_provider=None)` +### `__init__(*checkers)` * **参数** From 23c237cb2a5517e137892e6377db28a691fdcd27 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 21 Nov 2021 17:09:31 +0800 Subject: [PATCH 20/21] :bulb: add depends example --- docs/api/dependencies.md | 15 +++++++++++++++ nonebot/dependencies/__init__.py | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/docs/api/dependencies.md b/docs/api/dependencies.md index d88aabaa..a93902dc 100644 --- a/docs/api/dependencies.md +++ b/docs/api/dependencies.md @@ -26,3 +26,18 @@ sidebarDepth: 0 * `use_cache: bool = True`: 是否使用缓存。默认为 `True`。 + + +```python +def depend_func() -> Any: + return ... + +def depend_gen_func(): + try: + yield ... + finally: + ... + +async def handler(param_name: Any = Depends(depend_func), gen: Any = Depends(depend_gen_func)): + ... +``` diff --git a/nonebot/dependencies/__init__.py b/nonebot/dependencies/__init__.py index 7d0138a8..eb502e95 100644 --- a/nonebot/dependencies/__init__.py +++ b/nonebot/dependencies/__init__.py @@ -203,5 +203,19 @@ def Depends(dependency: Optional[T_Handler] = None, * ``dependency: Optional[Callable[..., Any]] = None``: 依赖函数。默认为参数的类型注释。 * ``use_cache: bool = True``: 是否使用缓存。默认为 ``True``。 + + .. code-block:: python + + def depend_func() -> Any: + return ... + + def depend_gen_func(): + try: + yield ... + finally: + ... + + async def handler(param_name: Any = Depends(depend_func), gen: Any = Depends(depend_gen_func)): + ... """ return DependsWrapper(dependency=dependency, use_cache=use_cache) From 3120abacb37d4b59d69c605f8c11872a11dda823 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Mon, 22 Nov 2021 11:38:42 +0800 Subject: [PATCH 21/21] :wheelchair: allow extra param with default value --- nonebot/dependencies/__init__.py | 31 +++++++++++++++++++++---------- nonebot/dependencies/models.py | 6 ------ nonebot/matcher.py | 2 +- nonebot/message.py | 9 ++++++--- nonebot/params.py | 12 ++++++++++++ nonebot/permission.py | 5 ++--- nonebot/rule.py | 6 +++--- 7 files changed, 45 insertions(+), 26 deletions(-) diff --git a/nonebot/dependencies/__init__.py b/nonebot/dependencies/__init__.py index eb502e95..44f568f7 100644 --- a/nonebot/dependencies/__init__.py +++ b/nonebot/dependencies/__init__.py @@ -11,8 +11,8 @@ from typing import Any, Dict, List, Type, Tuple, Callable, Optional, cast from contextlib import AsyncExitStack, contextmanager, asynccontextmanager from pydantic import BaseConfig -from pydantic.fields import Required, ModelField from pydantic.schema import get_annotation_from_field_info +from pydantic.fields import Required, Undefined, ModelField from nonebot.log import logger from .models import Param as Param @@ -90,16 +90,25 @@ def get_dependent(*, dependent.dependencies.append(sub_dependent) continue - for allow_type in dependent.allow_types: - if allow_type._check(param_name, param): - field_info = allow_type(param.default) - break + default_value = Required + if param.default != param.empty: + default_value = param.default + + if isinstance(default_value, Param): + field_info = default_value + default_value = field_info.default else: - raise ValueError( - f"Unknown parameter {param_name} for function {func} with type {param.annotation}" - ) + for allow_type in dependent.allow_types: + if allow_type._check(param_name, param): + field_info = allow_type(default_value) + break + else: + raise ValueError( + f"Unknown parameter {param_name} for function {func} with type {param.annotation}" + ) annotation: Any = Any + required = default_value == Required if param.annotation != param.empty: annotation = param.annotation annotation = get_annotation_from_field_info(annotation, field_info, @@ -109,8 +118,8 @@ def get_dependent(*, type_=annotation, class_validators=None, model_config=CustomConfig, - default=Required, - required=True, + default=None if required else default_value, + required=required, field_info=field_info)) return dependent @@ -176,6 +185,8 @@ async def solve_dependencies( assert isinstance(field_info, Param), "Params must be subclasses of Param" value = field_info._solve(**params) + if value == Undefined: + value = field.get_default() _, errs_ = field.validate(value, values, loc=(str(field_info), field.alias)) diff --git a/nonebot/dependencies/models.py b/nonebot/dependencies/models.py index 36ee5af0..9acba4db 100644 --- a/nonebot/dependencies/models.py +++ b/nonebot/dependencies/models.py @@ -10,12 +10,6 @@ from nonebot.typing import T_Handler class Param(abc.ABC, FieldInfo): - def __repr__(self) -> str: - return f"{self.__class__.__name__}" - - def __str__(self) -> str: - return repr(self) - @classmethod @abc.abstractmethod def _check(cls, name: str, param: inspect.Parameter) -> bool: diff --git a/nonebot/matcher.py b/nonebot/matcher.py index 7eeda5ab..cbaab224 100644 --- a/nonebot/matcher.py +++ b/nonebot/matcher.py @@ -158,7 +158,7 @@ class Matcher(metaclass=MatcherMeta): HANDLER_PARAM_TYPES = [ params.BotParam, params.EventParam, params.StateParam, - params.MatcherParam + params.MatcherParam, params.DefaultParam ] def __init__(self): diff --git a/nonebot/message.py b/nonebot/message.py index bedf778c..87184362 100644 --- a/nonebot/message.py +++ b/nonebot/message.py @@ -29,13 +29,16 @@ _event_postprocessors: Set[Handler] = set() _run_preprocessors: Set[Handler] = set() _run_postprocessors: Set[Handler] = set() -EVENT_PCS_PARAMS = [params.BotParam, params.EventParam, params.StateParam] +EVENT_PCS_PARAMS = [ + params.BotParam, params.EventParam, params.StateParam, params.DefaultParam +] RUN_PREPCS_PARAMS = [ - params.MatcherParam, params.BotParam, params.EventParam, params.StateParam + params.MatcherParam, params.BotParam, params.EventParam, params.StateParam, + params.DefaultParam ] RUN_POSTPCS_PARAMS = [ params.MatcherParam, params.ExceptionParam, params.BotParam, - params.EventParam, params.StateParam + params.EventParam, params.StateParam, params.DefaultParam ] diff --git a/nonebot/params.py b/nonebot/params.py index 8b644f91..06fb6987 100644 --- a/nonebot/params.py +++ b/nonebot/params.py @@ -1,6 +1,8 @@ import inspect from typing import Any, Dict, Optional +from pydantic.fields import Undefined + from nonebot.typing import T_State from nonebot.dependencies import Param from nonebot.adapters import Bot, Event @@ -69,4 +71,14 @@ class ExceptionParam(Param): return exception +class DefaultParam(Param): + + @classmethod + def _check(cls, name: str, param: inspect.Parameter) -> bool: + return param.default != param.empty + + def _solve(self, **kwargs: Any) -> Any: + return Undefined + + from nonebot.matcher import Matcher diff --git a/nonebot/permission.py b/nonebot/permission.py index e52058cb..4e7e89da 100644 --- a/nonebot/permission.py +++ b/nonebot/permission.py @@ -15,7 +15,6 @@ from typing import Any, Dict, List, Type, Union, Callable, NoReturn, Optional from nonebot import params from nonebot.handler import Handler -from nonebot.dependencies import Param from nonebot.adapters import Bot, Event from nonebot.typing import T_PermissionChecker @@ -37,8 +36,8 @@ class Permission: """ __slots__ = ("checkers",) - HANDLER_PARAM_TYPES: List[Type[Param]] = [ - params.BotParam, params.EventParam + HANDLER_PARAM_TYPES = [ + params.BotParam, params.EventParam, params.DefaultParam ] def __init__(self, *checkers: Union[T_PermissionChecker, Handler]) -> None: diff --git a/nonebot/rule.py b/nonebot/rule.py index 1621aef6..2059ceac 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -25,7 +25,6 @@ from pygtrie import CharTrie from nonebot.log import logger from nonebot.handler import Handler from nonebot import params, get_driver -from nonebot.dependencies import Param from nonebot.exception import ParserExit from nonebot.typing import T_State, T_RuleChecker from nonebot.adapters import Bot, Event, MessageSegment @@ -64,8 +63,9 @@ class Rule: """ __slots__ = ("checkers",) - HANDLER_PARAM_TYPES: List[Type[Param]] = [ - params.BotParam, params.EventParam, params.StateParam + HANDLER_PARAM_TYPES = [ + params.BotParam, params.EventParam, params.StateParam, + params.DefaultParam ] def __init__(self, *checkers: Union[T_RuleChecker, Handler]) -> None: