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/docs/api/dependencies.md b/docs/api/dependencies.md new file mode 100644 index 00000000..a93902dc --- /dev/null +++ b/docs/api/dependencies.md @@ -0,0 +1,43 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.handler 模块 + +## 依赖注入处理模块 + +该模块实现了依赖注入的定义与处理。 + + +## `Depends(dependency=None, *, use_cache=True)` + + +* **说明** + + 参数依赖注入装饰器 + + + +* **参数** + + + * `dependency: Optional[Callable[..., Any]] = None`: 依赖函数。默认为参数的类型注释。 + + + * `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/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 dc2ab74f..6f319435 100644 --- a/docs/api/handler.md +++ b/docs/api/handler.md @@ -14,12 +14,32 @@ sidebarDepth: 0 基类:`object` -事件处理函数类 +事件处理器类。支持依赖注入。 -### `__init__(func)` +### `__init__(func, *, name=None, dependencies=None, allow_types=None)` + + +* **说明** + + 装饰一个函数为事件处理器。 + + + +* **参数** + + + * `func: Callable[..., Any]`: 事件处理函数。 + + + * `name: Optional[str]`: 事件处理器名称。默认为函数名。 + + + * `dependencies: Optional[List[DependsWrapper]]`: 额外的非参数依赖注入。 + + + * `allow_types: Optional[List[Type[Param]]]`: 允许的参数类型。 -装饰事件处理函数以便根据动态参数运行 ### `func` @@ -27,7 +47,7 @@ sidebarDepth: 0 * **类型** - `T_Handler` + `Callable[..., Any]` @@ -37,75 +57,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/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..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 @@ -42,7 +42,7 @@ Permission(async_function, run_sync(sync_function)) * **参数** - * `*checkers: Callable[[Bot, Event], Awaitable[bool]]`: **异步** PermissionChecker + * `*checkers: Union[T_PermissionChecker, Handler]`: 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 0ee615df..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 ::: @@ -42,7 +42,7 @@ Rule(async_function, run_sync(sync_function)) * **参数** - * `*checkers: Callable[[Bot, Event, T_State], Awaitable[bool]]`: **异步** RuleChecker + * `*checkers: Union[T_RuleChecker, Handler]`: 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)` * **说明** @@ -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 6d339e12..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) @@ -179,7 +240,20 @@ sidebarDepth: 0 * **类型** - `Callable[[Bot, Event, T_State], Union[bool, Awaitable[bool]]]` + `Callable[..., Union[bool, Awaitable[bool]]]` + + + +* **依赖参数** + + + * `BotParam`: Bot 对象 + + + * `EventParam`: Event 对象 + + + * `StateParam`: State 对象 @@ -195,7 +269,17 @@ sidebarDepth: 0 * **类型** - `Callable[[Bot, Event], Union[bool, Awaitable[bool]]]` + `Callable[..., Union[bool, Awaitable[bool]]]` + + + +* **依赖参数** + + + * `BotParam`: Bot 对象 + + + * `EventParam`: Event 对象 @@ -211,23 +295,29 @@ 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[..., Any]` * **说明** - Handler 即事件的处理函数。 + Handler 处理函数。 + + + + +## `T_DependencyCache` + + +* **类型** + + `Dict[T_Handler, Any]` + + + +* **说明** + + 依赖缓存, 用于存储依赖函数的返回值 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]]` 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/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 new file mode 100644 index 00000000..44f568f7 --- /dev/null +++ b/nonebot/dependencies/__init__.py @@ -0,0 +1,232 @@ +""" +依赖注入处理模块 +=============== + +该模块实现了依赖注入的定义与处理。 +""" + +import inspect +from itertools import chain +from typing import Any, Dict, List, Type, Tuple, Callable, Optional, cast +from contextlib import AsyncExitStack, contextmanager, asynccontextmanager + +from pydantic import BaseConfig +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 +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 (CacheLock, run_sync, is_gen_callable, + run_sync_ctx_manager, is_async_gen_callable, + is_coroutine_callable) + +cache_lock = CacheLock() + + +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, + allow_types=allow_types) + + +def get_parameterless_sub_dependant( + *, + depends: DependsWrapper, + allow_types: Optional[List[Type[Param]]] = None) -> Dependent: + assert callable( + depends.dependency + ), "A parameter-less dependency must have a callable dependency" + return get_sub_dependant(depends=depends, + dependency=depends.dependency, + allow_types=allow_types) + + +def get_sub_dependant( + *, + depends: DependsWrapper, + dependency: T_Handler, + name: Optional[str] = None, + allow_types: Optional[List[Type[Param]]] = 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: T_Handler, + name: Optional[str] = None, + use_cache: bool = True, + allow_types: Optional[List[Type[Param]]] = None) -> Dependent: + signature = get_typed_signature(func) + params = signature.parameters + 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, DependsWrapper): + sub_dependent = get_param_sub_dependent(param=param, + allow_types=allow_types) + dependent.dependencies.append(sub_dependent) + continue + + 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: + 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, + param_name) + dependent.params.append( + ModelField(name=param_name, + type_=annotation, + class_validators=None, + model_config=CustomConfig, + default=None if required else default_value, + required=required, + field_info=field_info)) + + return dependent + + +async def solve_dependencies( + *, + _dependent: Dependent, + _stack: Optional[AsyncExitStack] = None, + _sub_dependents: Optional[List[Dependent]] = None, + _dependency_cache: Optional[T_DependencyCache] = None, + **params: Any) -> Tuple[Dict[str, Any], T_DependencyCache]: + values: Dict[str, Any] = {} + dependency_cache = {} if _dependency_cache is None else _dependency_cache + + # solve sub dependencies + sub_dependent: Dependent + 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) + func = sub_dependent.func + + # solve sub dependency with current cache + solved_result = await solve_dependencies( + _dependent=sub_dependent, + _dependency_cache=dependency_cache, + **params) + sub_values, sub_dependency_cache = solved_result + # update cache? + # dependency_cache.update(sub_dependency_cache) + + # run dependency function + 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: + 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 + + # usual dependency + 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(**params) + if value == Undefined: + value = field.get_default() + _, errs_ = field.validate(value, + values, + loc=(str(field_info), field.alias)) + if errs_: + logger.debug( + f"{field_info} " + f"type {type(value)} not match depends {_dependent.func} " + f"annotation {field._type_display()}, ignored") + raise SkippedException + else: + values[field.name] = value + + return values, dependency_cache + + +def Depends(dependency: Optional[T_Handler] = None, + *, + use_cache: bool = True) -> Any: + """ + :说明: + + 参数依赖注入装饰器 + + :参数: + + * ``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) diff --git a/nonebot/dependencies/models.py b/nonebot/dependencies/models.py new file mode 100644 index 00000000..9acba4db --- /dev/null +++ b/nonebot/dependencies/models.py @@ -0,0 +1,54 @@ +import abc +import inspect +from typing import Any, List, Type, Optional + +from pydantic.fields import FieldInfo, ModelField + +from nonebot.utils import get_name +from nonebot.typing import T_Handler + + +class Param(abc.ABC, FieldInfo): + + @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[T_Handler] = 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[T_Handler] = 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/dependencies/utils.py b/nonebot/dependencies/utils.py new file mode 100644 index 00000000..ade7fd97 --- /dev/null +++ b/nonebot/dependencies/utils.py @@ -0,0 +1,37 @@ +import inspect +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(func: T_Handler) -> inspect.Signature: + signature = inspect.signature(func) + globalns = getattr(func, "__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) + 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 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 055e6f88..6203bebd 100644 --- a/nonebot/handler.py +++ b/nonebot/handler.py @@ -5,172 +5,114 @@ 该模块实现事件处理函数的封装,以实现动态参数等功能。 """ -import inspect -from typing import _eval_type # type: ignore -from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Optional, - ForwardRef) +import asyncio +from contextlib import AsyncExitStack +from typing import Any, Dict, List, Type, Callable, Optional -from nonebot.log import logger -from nonebot.typing import T_State, T_Handler - -if TYPE_CHECKING: - from nonebot.matcher import Matcher - from nonebot.adapters import Bot, Event +from nonebot.utils import get_name, run_sync +from nonebot.dependencies import (Param, Dependent, DependsWrapper, + get_dependent, solve_dependencies, + get_parameterless_sub_dependant) class Handler: - """事件处理函数类""" + """事件处理器类。支持依赖注入。""" - def __init__(self, func: T_Handler): - """装饰事件处理函数以便根据动态参数运行""" - self.func: T_Handler = func + def __init__(self, + func: Callable[..., Any], + *, + name: Optional[str] = None, + dependencies: Optional[List[DependsWrapper]] = None, + allow_types: Optional[List[Type[Param]]] = None): """ - :类型: ``T_Handler`` + :说明: + + 装饰一个函数为事件处理器。 + + :参数: + + * ``func: Callable[..., Any]``: 事件处理函数。 + * ``name: Optional[str]``: 事件处理器名称。默认为函数名。 + * ``dependencies: Optional[List[DependsWrapper]]``: 额外的非参数依赖注入。 + * ``allow_types: Optional[List[Type[Param]]]``: 允许的参数类型。 + """ + self.func = func + """ + :类型: ``Callable[..., Any]`` :说明: 事件处理函数 """ - self.signature: inspect.Signature = self.get_signature() + self.name = get_name(func) if name is None else name """ - :类型: ``inspect.Signature`` - :说明: 事件处理函数签名 + :类型: ``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: + self.cache_dependent(depends) + self.dependent = get_dependent(func=func, allow_types=self.allow_types) + def __repr__(self) -> str: - return (f"") + 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 + async def __call__(self, + *, + _stack: Optional[AsyncExitStack] = None, + _dependency_cache: Optional[Dict[Callable[..., Any], + Any]] = None, + **params) -> Any: + values, _ = await solve_dependencies( + _dependent=self.dependent, + _stack=_stack, + _sub_dependents=[ + self.sub_dependents[dependency.dependency] # type: ignore + for dependency in self.dependencies + ], + _dependency_cache=_dependency_cache, + **params) - 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 + if asyncio.iscoroutinefunction(self.func): + return await self.func(**values) + else: + return await run_sync(self.func)(**values) - 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 - }) + def cache_dependent(self, dependency: DependsWrapper): + 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, allow_types=self.allow_types) + self.sub_dependents[dependency.dependency] = sub_dependant - @property - def bot_type(self) -> Union[Type["Bot"], inspect.Parameter.empty]: - """ - :类型: ``Union[Type["Bot"], inspect.Parameter.empty]`` - :说明: 事件处理函数接受的 Bot 对象类型""" - return self.signature.parameters["bot"].annotation + def prepend_dependency(self, dependency: DependsWrapper): + self.cache_dependent(dependency) + self.dependencies.insert(0, dependency) - @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 + def append_dependency(self, dependency: DependsWrapper): + self.cache_dependent(dependency) + self.dependencies.append(dependency) - @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'") + def remove_dependency(self, dependency: DependsWrapper): + 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/matcher.py b/nonebot/matcher.py index b8a874f2..cbaab224 100644 --- a/nonebot/matcher.py +++ b/nonebot/matcher.py @@ -5,35 +5,39 @@ 该模块实现事件响应器的创建与运行,并提供一些快捷方法来帮助用户更好的与机器人进行对话 。 """ -from functools import wraps 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) +from nonebot import params from nonebot.rule import Rule from nonebot.log import logger from nonebot.handler import Handler -from nonebot.adapters import MessageTemplate +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) + SkippedException, FinishedException, + RejectedException) from nonebot.typing import (T_State, T_Handler, T_ArgsParser, T_TypeUpdater, - T_StateFactory, T_PermissionUpdater) + T_StateFactory, T_DependencyCache, + T_PermissionUpdater) 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") @@ -152,6 +156,11 @@ class Matcher(metaclass=MatcherMeta): :说明: 事件响应器权限更新函数 """ + HANDLER_PARAM_TYPES = [ + params.BotParam, params.EventParam, params.StateParam, + params.MatcherParam, params.DefaultParam + ] + def __init__(self): """实例化 Matcher 以便运行""" self.handlers = self.handlers.copy() @@ -228,8 +237,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, allow_types=cls.HANDLER_PARAM_TYPES) for handler in handlers ] if handlers else [], "temp": @@ -258,7 +267,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: """ :说明: @@ -275,11 +290,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: """ :说明: @@ -297,7 +318,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: @@ -343,8 +364,13 @@ 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[DependsWrapper]] = None) -> Handler: + handler_ = Handler(handler, + dependencies=dependencies, + allow_types=cls.HANDLER_PARAM_TYPES) cls.handlers.append(handler_) return handler_ @@ -378,22 +404,22 @@ 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 + 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: - 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) + + depend = DependsWrapper(_receive) + + if cls.handlers and cls.handlers[-1].func is func: + func_handler = cls.handlers[-1] + func_handler.prepend_dependency(depend) + else: + cls.append_handler( + func, dependencies=[depend] if cls.handlers else []) return func @@ -403,7 +429,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]: @@ -419,8 +445,12 @@ class Matcher(metaclass=MatcherMeta): * ``args_parser: Optional[T_ArgsParser]``: 可选参数解析函数,空则使用默认解析函数 """ - async def _key_getter(bot: "Bot", event: "Event", state: T_State): + 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): @@ -428,52 +458,40 @@ class Matcher(metaclass=MatcherMeta): else: _prompt = prompt await bot.send(event=event, message=_prompt) - raise PausedException + raise RejectedException else: - state["_skip_key"] = True + state[f"_{key}_parsed"] = 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"] + 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: - # 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) + state[key] = str(event.get_message()) + state[f"_{key}_parsed"] = True 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"] + get_depend = DependsWrapper(_key_getter) + parser_depend = DependsWrapper(_key_parser) - wrapper_handler = cls.append_handler(wrapper) - - 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) + 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: + 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: """ :说明: @@ -496,7 +514,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: """ @@ -522,7 +540,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: """ @@ -548,8 +566,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: """ :说明: @@ -564,6 +582,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: @@ -581,7 +601,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) @@ -594,7 +619,15 @@ 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) + try: + await handler(matcher=self, + bot=bot, + event=event, + state=self.state, + _stack=stack, + _dependency_cache=dependency_cache) + except SkippedException: + pass except RejectedException: self.handlers.insert(0, handler) # type: ignore @@ -610,11 +643,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) @@ -647,11 +677,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 b853e481..87184362 100644 --- a/nonebot/message.py +++ b/nonebot/message.py @@ -7,23 +7,39 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供 import asyncio from datetime import datetime -from typing import TYPE_CHECKING, Set, Type, Optional +from contextlib import AsyncExitStack +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.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 -_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, params.DefaultParam +] +RUN_PREPCS_PARAMS = [ + params.MatcherParam, params.BotParam, params.EventParam, params.StateParam, + params.DefaultParam +] +RUN_POSTPCS_PARAMS = [ + params.MatcherParam, params.ExceptionParam, params.BotParam, + params.EventParam, params.StateParam, params.DefaultParam +] def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor: @@ -31,16 +47,8 @@ def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor: :说明: 事件预处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之前执行。 - - :参数: - - 事件预处理函数接收三个参数。 - - * ``bot: Bot``: Bot 对象 - * ``event: Event``: Event 对象 - * ``state: T_State``: 当前 State """ - _event_preprocessors.add(func) + _event_preprocessors.add(Handler(func, allow_types=EVENT_PCS_PARAMS)) return func @@ -49,16 +57,8 @@ def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor: :说明: 事件后处理。装饰一个函数,使它在每次接收到事件并分发给各响应器之后执行。 - - :参数: - - 事件后处理函数接收三个参数。 - - * ``bot: Bot``: Bot 对象 - * ``event: Event``: Event 对象 - * ``state: T_State``: 当前事件运行前 State """ - _event_postprocessors.add(func) + _event_postprocessors.add(Handler(func, allow_types=EVENT_PCS_PARAMS)) return func @@ -67,17 +67,8 @@ def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor: :说明: 运行预处理。装饰一个函数,使它在每次事件响应器运行前执行。 - - :参数: - - 运行预处理函数接收四个参数。 - - * ``matcher: Matcher``: 当前要运行的事件响应器 - * ``bot: Bot``: Bot 对象 - * ``event: Event``: Event 对象 - * ``state: T_State``: 当前 State """ - _run_preprocessors.add(func) + _run_preprocessors.add(Handler(func, allow_types=RUN_PREPCS_PARAMS)) return func @@ -86,23 +77,19 @@ 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(func) + _run_postprocessors.add(Handler(func, allow_types=RUN_POSTPCS_PARAMS)) 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) @@ -112,7 +99,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( @@ -125,17 +114,29 @@ 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, bot, event, state), _run_preprocessors)) + map( + lambda x: x(matcher=matcher, + bot=bot, + event=event, + state=state, + _stack=stack, + _dependency_cache=dependency_cache), + _run_preprocessors)) if coros: try: await asyncio.gather(*coros) @@ -153,7 +154,7 @@ async def _run_matcher(Matcher: Type[Matcher], bot: "Bot", event: "Event", 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." @@ -161,7 +162,14 @@ async def _run_matcher(Matcher: Type[Matcher], bot: "Bot", event: "Event", exception = e coros = list( - map(lambda x: x(matcher, exception, bot, event, state), + map( + lambda x: x(matcher=matcher, + exception=exception, + bot=bot, + event=event, + state=state, + _stack=stack, + _dependency_cache=dependency_cache), _run_postprocessors)) if coros: try: @@ -203,59 +211,79 @@ async def handle_event(bot: "Bot", event: "Event") -> None: if show_log: 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 + state: Dict[Any, Any] = {} + dependency_cache: T_DependencyCache = {} - # 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." + async with AsyncExitStack() as stack: + coros = list( + map( + lambda x: x(bot=bot, + event=event, + state=state, + _stack=stack, + _dependency_cache=dependency_cache), + _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(), + stack, dependency_cache) + for matcher in matchers[priority] + ] + + results = await asyncio.gather(*pending_tasks, + return_exceptions=True) + + for result in results: + if not isinstance(result, Exception): + continue + 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=bot, + event=event, + state=state, + _stack=stack, + _dependency_cache=dependency_cache), + _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/params.py b/nonebot/params.py new file mode 100644 index 00000000..06fb6987 --- /dev/null +++ b/nonebot/params.py @@ -0,0 +1,84 @@ +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 +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) or (param.annotation == param.empty and + name == "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) or (param.annotation == param.empty and + name == "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) or (param.annotation == param.empty and + name == "state") + + 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) or (param.annotation == param.empty and + name == "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) or (param.annotation == param.empty and + name == "exception") + + def _solve(self, + exception: Optional[Exception] = None, + **kwargs: Any) -> Any: + 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 675f546d..4e7e89da 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 @@ -10,14 +10,14 @@ r""" """ import asyncio -from typing import TYPE_CHECKING, 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.adapters import Bot, Event from nonebot.typing import T_PermissionChecker -if TYPE_CHECKING: - from nonebot.adapters import Bot, Event - class Permission: """ @@ -36,15 +36,21 @@ class Permission: """ __slots__ = ("checkers",) - def __init__( - self, *checkers: Callable[["Bot", "Event"], - Awaitable[bool]]) -> None: + HANDLER_PARAM_TYPES = [ + params.BotParam, params.EventParam, params.DefaultParam + ] + + def __init__(self, *checkers: Union[T_PermissionChecker, Handler]) -> None: """ :参数: - * ``*checkers: Callable[[Bot, Event], Awaitable[bool]]``: **异步** PermissionChecker + * ``*checkers: Union[T_PermissionChecker, Handler]``: PermissionChecker """ - self.checkers = set(checkers) + + self.checkers = set( + checker if isinstance(checker, Handler) else Handler( + checker, allow_types=self.HANDLER_PARAM_TYPES) + for checker in checkers) """ :说明: @@ -52,10 +58,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: """ :说明: @@ -65,6 +77,8 @@ class Permission: * ``bot: Bot``: Bot 对象 * ``event: Event``: Event 对象 + * ``stack: Optional[AsyncExitStack]``: 异步上下文栈 + * ``dependency_cache: Optional[Dict[Callable[..., Any], Any]]``: 依赖缓存 :返回: @@ -73,7 +87,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: @@ -82,31 +100,27 @@ 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: +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" @@ -140,14 +154,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 e4da8345..5b0f2979 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 nonebot.adapters import Event from nonebot.handler import Handler from nonebot.matcher import Matcher from .manager import _current_plugin 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) - -if TYPE_CHECKING: - from nonebot.adapters import Bot, Event +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: @@ -376,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) @@ -433,12 +430,11 @@ 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__( - 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/plugins/echo.py b/nonebot/plugins/echo.py index 5e61fd95..763fd715 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 echo.send(message=event.get_message()) diff --git a/nonebot/plugins/single_session.py b/nonebot/plugins/single_session.py index 8a8b3cfb..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) @@ -10,7 +8,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 +22,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/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 863339df..2059ceac 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 \:\:\: """ @@ -14,20 +14,36 @@ 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 (TYPE_CHECKING, 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 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 -if TYPE_CHECKING: - from nonebot.adapters import Bot, Event +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: @@ -47,16 +63,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, + params.DefaultParam + ] + + def __init__(self, *checkers: Union[T_RuleChecker, Handler]) -> None: """ :参数: - * ``*checkers: Callable[[Bot, Event, T_State], Awaitable[bool]]``: **异步** RuleChecker + * ``*checkers: Union[T_RuleChecker, Handler]``: RuleChecker """ - self.checkers = set(checkers) + self.checkers = set( + checker if isinstance(checker, Handler) else Handler( + checker, allow_types=self.HANDLER_PARAM_TYPES) + for checker in checkers) """ :说明: @@ -64,11 +86,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: """ :说明: @@ -79,26 +107,31 @@ 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( - *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 + return Rule(*self.checkers, *other.checkers) else: - checkers.add(run_sync(other)) - return Rule(*checkers) + return Rule(*self.checkers, other) def __or__(self, other) -> NoReturn: raise RuntimeError("Or operation between rules is not allowed.") @@ -123,58 +156,28 @@ class TrieRule: cls.suffix[suffix[::-1]] = value @classmethod - def get_value(cls, bot: "Bot", event: "Event", - state: T_State) -> Tuple[Dict[str, Any], Dict[str, Any]]: + def get_value(cls, bot: Bot, event: Event, + 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, ...]], @@ -195,7 +198,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 +225,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 +245,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() @@ -290,8 +293,8 @@ 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: - return state["_prefix"]["command"] in commands + async def _command(state: T_State) -> bool: + return state[PREFIX_KEY][CMD_KEY] in commands return Rule(_command) @@ -310,7 +313,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)) @@ -376,19 +379,18 @@ 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: - if state["_prefix"]["command"] in commands: + 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"]["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 @@ -417,14 +419,14 @@ 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())) 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 @@ -432,6 +434,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: """ :说明: @@ -443,7 +449,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 273661eb..3394cd6f 100644 --- a/nonebot/typing.py +++ b/nonebot/typing.py @@ -17,16 +17,15 @@ .. _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) if TYPE_CHECKING: - from nonebot.matcher import Matcher 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): @@ -90,77 +89,109 @@ T_CalledAPIHook = Callable[ ``bot.call_api`` 后执行的函数,参数分别为 bot, exception, api, data, result """ -T_EventPreProcessor = Callable[["Bot", "Event", T_State], 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[["Bot", "Event", T_State], 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[["Matcher", "Bot", "Event", T_State], - 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[ - ["Matcher", Optional[Exception], "Bot", "Event", T_State], 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) :说明: 事件响应器运行前预处理函数 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]]]`` + +:依赖参数: + + * ``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 = 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[..., Any] """ -:类型: - - * ``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[..., 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]]] diff --git a/nonebot/utils.py b/nonebot/utils.py index 8183986d..32eca0db 100644 --- a/nonebot/utils.py +++ b/nonebot/utils.py @@ -1,13 +1,23 @@ import re import json import asyncio +import inspect +import collections import dataclasses from functools import wraps, partial -from typing import Any, Callable, Optional, Awaitable +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, Deque, Tuple, Union, 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: """ @@ -26,7 +36,48 @@ 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 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) + if inspect.isclass(func): + return False + func_ = getattr(func, "__call__", None) + 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]]: """ :说明: @@ -34,15 +85,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) @@ -51,6 +102,98 @@ def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: 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__ + 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/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): """ :说明: 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/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 13e3204d..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 = "51f4f0ce5ced234a65cae790c4f57486e42d7120972657a3f51e733cb4e7c639" +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 6d9a8330..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,6 +28,7 @@ pygtrie = "^2.4.1" tomlkit = "^0.7.0" fastapi = "^0.70.0" websockets = ">=9.1" +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"] } diff --git a/tests/test_plugins/test_depends.py b/tests/test_plugins/test_depends.py new file mode 100644 index 00000000..e23c9c4e --- /dev/null +++ b/tests/test_plugins/test_depends.py @@ -0,0 +1,28 @@ +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 + + +@test.got("a", prompt="a") +@test.got("b", prompt="b") +@test.receive() +@test.got("c", prompt="c") +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}") diff --git a/tests/test_plugins/test_processor.py b/tests/test_plugins/test_processor.py index b5aaa298..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.matcher 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 -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)