🔀 Merge pull request #588

Feature: `Handler` Dependency Injection
This commit is contained in:
Ju4tCode 2021-11-22 21:16:36 +08:00 committed by GitHub
commit 602185a34e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1700 additions and 933 deletions

View File

@ -25,8 +25,6 @@ jobs:
with: with:
path: ~/.cache/pypoetry/virtualenvs path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Set up dependencies - name: Set up dependencies
run: | run: |

43
docs/api/dependencies.md Normal file
View File

@ -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)):
...
```

View File

@ -22,24 +22,6 @@ sidebarDepth: 0
## _exception_ `IgnoredException`
基类:`nonebot.exception.NoneBotException`
* **说明**
指示 NoneBot 应该忽略该事件。可由 PreProcessor 抛出。
* **参数**
* `reason`: 忽略事件的原因
## _exception_ `ParserExit` ## _exception_ `ParserExit`
基类:`nonebot.exception.NoneBotException` 基类:`nonebot.exception.NoneBotException`
@ -61,57 +43,21 @@ sidebarDepth: 0
## _exception_ `PausedException` ## _exception_ `IgnoredException`
基类:`nonebot.exception.NoneBotException` 基类:`nonebot.exception.NoneBotException`
* **说明** * **说明**
指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler` 指示 NoneBot 应该忽略该事件。可由 PreProcessor 抛出。
可用于用户输入新信息。
* **用法** * **参数**
可以在 `Handler` 中通过 `Matcher.pause()` 抛出。
* `reason`: 忽略事件的原因
## _exception_ `RejectedException`
基类:`nonebot.exception.NoneBotException`
* **说明**
指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`
可用于用户重新输入。
* **用法**
可以在 `Handler` 中通过 `Matcher.reject()` 抛出。
## _exception_ `FinishedException`
基类:`nonebot.exception.NoneBotException`
* **说明**
指示 NoneBot 结束当前 `Handler` 且后续 `Handler` 不再被运行。
可用于结束用户会话。
* **用法**
可以在 `Handler` 中通过 `Matcher.finish()` 抛出。
@ -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` ## _exception_ `AdapterException`
基类:`nonebot.exception.NoneBotException` 基类:`nonebot.exception.NoneBotException`
@ -152,7 +180,7 @@ sidebarDepth: 0
## _exception_ `NoLogException` ## _exception_ `NoLogException`
基类:`Exception` 基类:`nonebot.exception.AdapterException`
* **说明** * **说明**

View File

@ -14,12 +14,32 @@ sidebarDepth: 0
基类:`object` 基类:`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` ### `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 参数

View File

@ -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)`
* **说明** * **说明**

View File

@ -19,21 +19,6 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供
* **参数**
事件预处理函数接收三个参数。
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
* `state: T_State`: 当前 State
## `event_postprocessor(func)` ## `event_postprocessor(func)`
@ -43,21 +28,6 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供
* **参数**
事件后处理函数接收三个参数。
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
* `state: T_State`: 当前事件运行前 State
## `run_preprocessor(func)` ## `run_preprocessor(func)`
@ -67,24 +37,6 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供
* **参数**
运行预处理函数接收四个参数。
* `matcher: Matcher`: 当前要运行的事件响应器
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
* `state: T_State`: 当前 State
## `run_postprocessor(func)` ## `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)` ## _async_ `handle_event(bot, event)`

View File

@ -7,7 +7,7 @@ sidebarDepth: 0
## 权限 ## 权限
每个 `Matcher` 拥有一个 `Permission` ,其中是 **异步** `PermissionChecker` 的集合,只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。 每个 `Matcher` 拥有一个 `Permission` ,其中是 `PermissionChecker` 的集合,只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
:::tip 提示 :::tip 提示
`PermissionChecker` 既可以是 async function 也可以是 sync function `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 对象 * `event: Event`: Event 对象
* `stack: Optional[AsyncExitStack]`: 异步上下文栈
* `dependency_cache: Optional[Dict[Callable[..., Any], Any]]`: 依赖缓存
* **返回** * **返回**

View File

@ -7,10 +7,10 @@ sidebarDepth: 0
## 规则 ## 规则
每个事件响应器 `Matcher` 拥有一个匹配规则 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。 每个事件响应器 `Matcher` 拥有一个匹配规则 `Rule` ,其中是 `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
:::tip 提示 :::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 * `state: T_State`: 当前 State
* `stack: Optional[AsyncExitStack]`: 异步上下文栈
* `dependency_cache: Optional[Dict[Callable[..., Any], Any]]`: 依赖缓存
* **返回** * **返回**

View File

@ -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[..., 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]]]`
* **说明** * **说明**
Handler 即事件的处理函数。 Handler 处理函数。
## `T_DependencyCache`
* **类型**
`Dict[T_Handler, Any]`
* **说明**
依赖缓存, 用于存储依赖函数的返回值

View File

@ -41,14 +41,14 @@ sidebarDepth: 0
* **参数** * **参数**
* `func: Callable[..., Any]`: 被装饰的同步函数 * `func: Callable[P, R]`: 被装饰的同步函数
* **返回** * **返回**
* `Callable[..., Awaitable[Any]]` * `Callable[P, Awaitable[R]]`

View File

@ -0,0 +1,12 @@
\-\-\-
contentSidebar: true
sidebarDepth: 0
\-\-\-
NoneBot.handler 模块
====================
.. automodule:: nonebot.dependencies
:members:
:private-members:
:show-inheritance:

View File

@ -278,6 +278,7 @@ def run(host: Optional[str] = None,
get_driver().run(host, port, *args, **kwargs) get_driver().run(host, port, *args, **kwargs)
import nonebot.params as params
from nonebot.plugin import export as export from nonebot.plugin import export as export
from nonebot.plugin import require as require from nonebot.plugin import require as require
from nonebot.plugin import on_regex as on_regex from nonebot.plugin import on_regex as on_regex

View File

@ -67,8 +67,7 @@ class CustomEnvSettings(EnvSettingsSource):
env_val = settings.__config__.json_loads(env_val) env_val = settings.__config__.json_loads(env_val)
except ValueError as e: except ValueError as e:
raise SettingsError( raise SettingsError(
f'error parsing JSON for "{env_name}"' # type: ignore f'error parsing JSON for "{env_name}"') from e
) from e
d[field.alias] = env_val d[field.alias] = env_val
if env_file_vars: if env_file_vars:

View File

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

View File

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

View File

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

View File

@ -248,6 +248,8 @@ class Driver(ForwardDriver):
await asyncio.sleep(3) await asyncio.sleep(3)
continue continue
setup_ = cast(HTTPPollingSetup, setup_)
if not bot: if not bot:
request = await _build_request(setup_) request = await _build_request(setup_)
if not request: if not request:
@ -264,7 +266,6 @@ class Driver(ForwardDriver):
bot.request = request bot.request = request
request = cast(HTTPRequest, request) request = cast(HTTPRequest, request)
setup_ = cast(HTTPPollingSetup, setup_)
headers = request.headers headers = request.headers
timeout = aiohttp.ClientTimeout(30) timeout = aiohttp.ClientTimeout(30)

View File

@ -409,6 +409,8 @@ class Driver(ReverseDriver, ForwardDriver):
await asyncio.sleep(3) await asyncio.sleep(3)
continue continue
setup_ = cast(HTTPPollingSetup, setup_)
if not bot: if not bot:
request = await _build_request(setup_) request = await _build_request(setup_)
if not request: if not request:
@ -423,7 +425,6 @@ class Driver(ReverseDriver, ForwardDriver):
continue continue
bot.request = request bot.request = request
setup_ = cast(HTTPPollingSetup, setup_)
request = cast(HTTPRequest, request) request = cast(HTTPRequest, request)
headers = request.headers headers = request.headers

View File

@ -6,6 +6,8 @@
这些异常并非所有需要用户处理 NoneBot 内部运行时被捕获并进行对应操作 这些异常并非所有需要用户处理 NoneBot 内部运行时被捕获并进行对应操作
""" """
from typing import Optional
class NoneBotException(Exception): class NoneBotException(Exception):
""" """
@ -13,9 +15,33 @@ class NoneBotException(Exception):
所有 NoneBot 发生的异常基类 所有 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"<ParserExit status={self.status} message={self.message}>"
def __str__(self):
return self.__repr__()
# Processor Exception
class IgnoredException(NoneBotException): class IgnoredException(NoneBotException):
""" """
:说明: :说明:
@ -37,71 +63,6 @@ class IgnoredException(NoneBotException):
return self.__repr__() 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"<ParserExit status={self.status} message={self.message}>"
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): class StopPropagation(NoneBotException):
""" """
:说明: :说明:
@ -112,9 +73,69 @@ class StopPropagation(NoneBotException):
``Matcher.block == True`` 时抛出 ``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): class AdapterException(NoneBotException):
""" """
:说明: :说明:
@ -130,7 +151,7 @@ class AdapterException(NoneBotException):
self.adapter_name = adapter_name self.adapter_name = adapter_name
class NoLogException(Exception): class NoLogException(AdapterException):
""" """
:说明: :说明:

View File

@ -5,172 +5,114 @@
该模块实现事件处理函数的封装以实现动态参数等功能 该模块实现事件处理函数的封装以实现动态参数等功能
""" """
import inspect import asyncio
from typing import _eval_type # type: ignore from contextlib import AsyncExitStack
from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Optional, from typing import Any, Dict, List, Type, Callable, Optional
ForwardRef)
from nonebot.log import logger from nonebot.utils import get_name, run_sync
from nonebot.typing import T_State, T_Handler from nonebot.dependencies import (Param, Dependent, DependsWrapper,
get_dependent, solve_dependencies,
if TYPE_CHECKING: get_parameterless_sub_dependant)
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event
class Handler: class Handler:
"""事件处理函数类""" """事件处理器类。支持依赖注入。"""
def __init__(self, func: T_Handler): def __init__(self,
"""装饰事件处理函数以便根据动态参数运行""" func: Callable[..., Any],
self.func: T_Handler = func *,
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: def __repr__(self) -> str:
return (f"<Handler {self.func.__name__}(bot: {self.bot_type}, " return (
f"event: {self.event_type}, state: {self.state_type}, " f"<Handler {self.name}({', '.join(map(str, self.dependent.params))})>"
f"matcher: {self.matcher_type})>") )
def __str__(self) -> str: def __str__(self) -> str:
return repr(self) return repr(self)
async def __call__(self, matcher: "Matcher", bot: "Bot", event: "Event", async def __call__(self,
state: T_State): *,
BotType = ((self.bot_type is not inspect.Parameter.empty) and _stack: Optional[AsyncExitStack] = None,
inspect.isclass(self.bot_type) and self.bot_type) _dependency_cache: Optional[Dict[Callable[..., Any],
if BotType and not isinstance(bot, BotType): Any]] = None,
logger.debug( **params) -> Any:
f"Matcher {matcher} bot type {type(bot)} not match annotation {BotType}, ignored" values, _ = await solve_dependencies(
) _dependent=self.dependent,
return _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 if asyncio.iscoroutinefunction(self.func):
inspect.isclass(self.event_type) and self.event_type) return await self.func(**values)
if EventType and not isinstance(event, EventType): else:
logger.debug( return await run_sync(self.func)(**values)
f"Matcher {matcher} event type {type(event)} not match annotation {EventType}, ignored"
)
return
args = {"bot": bot, "event": event, "state": state, "matcher": matcher} def cache_dependent(self, dependency: DependsWrapper):
await self.func( if not dependency.dependency:
**{ raise ValueError(f"{dependency} has no dependency")
k: v if dependency.dependency in self.sub_dependents:
for k, v in args.items() raise ValueError(f"{dependency} is already in dependencies")
if self.signature.parameters.get(k, None) is not None sub_dependant = get_parameterless_sub_dependant(
}) depends=dependency, allow_types=self.allow_types)
self.sub_dependents[dependency.dependency] = sub_dependant
@property def prepend_dependency(self, dependency: DependsWrapper):
def bot_type(self) -> Union[Type["Bot"], inspect.Parameter.empty]: self.cache_dependent(dependency)
""" self.dependencies.insert(0, dependency)
:类型: ``Union[Type["Bot"], inspect.Parameter.empty]``
:说明: 事件处理函数接受的 Bot 对象类型"""
return self.signature.parameters["bot"].annotation
@property def append_dependency(self, dependency: DependsWrapper):
def event_type( self.cache_dependent(dependency)
self) -> Optional[Union[Type["Event"], inspect.Parameter.empty]]: self.dependencies.append(dependency)
"""
:类型: ``Optional[Union[Type[Event], inspect.Parameter.empty]]``
:说明: 事件处理函数接受的 event 类型 / 不需要 event 参数
"""
if "event" not in self.signature.parameters:
return None
return self.signature.parameters["event"].annotation
@property def remove_dependency(self, dependency: DependsWrapper):
def state_type(self) -> Optional[Union[T_State, inspect.Parameter.empty]]: if not dependency.dependency:
""" raise ValueError(f"{dependency} has no dependency")
:类型: ``Optional[Union[T_State, inspect.Parameter.empty]]`` if dependency.dependency in self.sub_dependents:
:说明: 事件处理函数是否接受 state 参数 del self.sub_dependents[dependency.dependency]
""" if dependency in self.dependencies:
if "state" not in self.signature.parameters: self.dependencies.remove(dependency)
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'")

View File

@ -5,35 +5,39 @@
该模块实现事件响应器的创建与运行并提供一些快捷方法来帮助用户更好的与机器人进行对话 该模块实现事件响应器的创建与运行并提供一些快捷方法来帮助用户更好的与机器人进行对话
""" """
from functools import wraps
from types import ModuleType from types import ModuleType
from datetime import datetime from datetime import datetime
from contextvars import ContextVar from contextvars import ContextVar
from collections import defaultdict from collections import defaultdict
from contextlib import AsyncExitStack
from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Callable, from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Callable,
NoReturn, Optional) NoReturn, Optional)
from nonebot import params
from nonebot.rule import Rule from nonebot.rule import Rule
from nonebot.log import logger from nonebot.log import logger
from nonebot.handler import Handler from nonebot.handler import Handler
from nonebot.adapters import MessageTemplate from nonebot.dependencies import DependsWrapper
from nonebot.permission import USER, Permission from nonebot.permission import USER, Permission
from nonebot.adapters import (Bot, Event, Message, MessageSegment,
MessageTemplate)
from nonebot.exception import (PausedException, StopPropagation, from nonebot.exception import (PausedException, StopPropagation,
FinishedException, RejectedException) SkippedException, FinishedException,
RejectedException)
from nonebot.typing import (T_State, T_Handler, T_ArgsParser, T_TypeUpdater, 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: if TYPE_CHECKING:
from nonebot.plugin import Plugin from nonebot.plugin import Plugin
from nonebot.adapters import Bot, Event, Message, MessageSegment
matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list) matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list)
""" """
:类型: ``Dict[int, List[Type[Matcher]]]`` :类型: ``Dict[int, List[Type[Matcher]]]``
:说明: 用于存储当前所有的事件响应器 :说明: 用于存储当前所有的事件响应器
""" """
current_bot: ContextVar["Bot"] = ContextVar("current_bot") current_bot: ContextVar[Bot] = ContextVar("current_bot")
current_event: ContextVar["Event"] = ContextVar("current_event") current_event: ContextVar[Event] = ContextVar("current_event")
current_state: ContextVar[T_State] = ContextVar("current_state") 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): def __init__(self):
"""实例化 Matcher 以便运行""" """实例化 Matcher 以便运行"""
self.handlers = self.handlers.copy() self.handlers = self.handlers.copy()
@ -228,8 +237,8 @@ class Matcher(metaclass=MatcherMeta):
"permission": "permission":
permission or Permission(), permission or Permission(),
"handlers": [ "handlers": [
handler handler if isinstance(handler, Handler) else Handler(
if isinstance(handler, Handler) else Handler(handler) handler, allow_types=cls.HANDLER_PARAM_TYPES)
for handler in handlers for handler in handlers
] if handlers else [], ] if handlers else [],
"temp": "temp":
@ -258,7 +267,13 @@ class Matcher(metaclass=MatcherMeta):
return NewMatcher return NewMatcher
@classmethod @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() event_type = event.get_type()
return (event_type == (cls.type or event_type) and return (event_type == (cls.type or event_type) and
await cls.permission(bot, event)) await cls.permission(bot, event, stack, dependency_cache))
@classmethod @classmethod
async def check_rule(cls, bot: "Bot", event: "Event", async def check_rule(
state: T_State) -> bool: 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() event_type = event.get_type()
return (event_type == (cls.type or event_type) and 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 @classmethod
def args_parser(cls, func: T_ArgsParser) -> T_ArgsParser: def args_parser(cls, func: T_ArgsParser) -> T_ArgsParser:
@ -343,8 +364,13 @@ class Matcher(metaclass=MatcherMeta):
return func return func
@classmethod @classmethod
def append_handler(cls, handler: T_Handler) -> Handler: def append_handler(
handler_ = Handler(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_) cls.handlers.append(handler_)
return handler_ return handler_
@ -378,22 +404,22 @@ class Matcher(metaclass=MatcherMeta):
* *
""" """
async def _receive(bot: "Bot", event: "Event") -> NoReturn: async def _receive(state: T_State) -> Union[None, NoReturn]:
raise PausedException if state.get(_receive):
return
if cls.handlers: state[_receive] = True
# 已有前置handlers则接受一条新的消息否则视为接收初始消息 raise RejectedException
receive_handler = cls.append_handler(_receive)
else:
receive_handler = None
def _decorator(func: T_Handler) -> T_Handler: def _decorator(func: T_Handler) -> T_Handler:
if not cls.handlers or cls.handlers[-1] is not func:
func_handler = cls.append_handler(func) depend = DependsWrapper(_receive)
if receive_handler:
receive_handler.update_signature( if cls.handlers and cls.handlers[-1].func is func:
bot=func_handler.bot_type, func_handler = cls.handlers[-1]
event=func_handler.event_type) func_handler.prepend_dependency(depend)
else:
cls.append_handler(
func, dependencies=[depend] if cls.handlers else [])
return func return func
@ -403,7 +429,7 @@ class Matcher(metaclass=MatcherMeta):
def got( def got(
cls, cls,
key: str, key: str,
prompt: Optional[Union[str, "Message", "MessageSegment", prompt: Optional[Union[str, Message, MessageSegment,
MessageTemplate]] = None, MessageTemplate]] = None,
args_parser: Optional[T_ArgsParser] = None args_parser: Optional[T_ArgsParser] = None
) -> Callable[[T_Handler], T_Handler]: ) -> Callable[[T_Handler], T_Handler]:
@ -419,8 +445,12 @@ class Matcher(metaclass=MatcherMeta):
* ``args_parser: Optional[T_ArgsParser]``: 可选参数解析函数空则使用默认解析函数 * ``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["_current_key"] = key
state[f"_{key}_prompted"] = True
if key not in state: if key not in state:
if prompt is not None: if prompt is not None:
if isinstance(prompt, MessageTemplate): if isinstance(prompt, MessageTemplate):
@ -428,52 +458,40 @@ class Matcher(metaclass=MatcherMeta):
else: else:
_prompt = prompt _prompt = prompt
await bot.send(event=event, message=_prompt) await bot.send(event=event, message=_prompt)
raise PausedException raise RejectedException
else: else:
state["_skip_key"] = True state[f"_{key}_parsed"] = True
async def _key_parser(bot: "Bot", event: "Event", state: T_State): async def _key_parser(bot: Bot, event: Event, state: T_State):
if key in state and state.get("_skip_key"): if key in state and state.get(f"_{key}_parsed"):
del state["_skip_key"]
return return
parser = args_parser or cls._default_parser parser = args_parser or cls._default_parser
if parser: if parser:
# parser = cast(T_ArgsParser["Bot", "Event"], parser)
await parser(bot, event, state) await parser(bot, event, state)
else: else:
state[state["_current_key"]] = str(event.get_message()) state[key] = str(event.get_message())
state[f"_{key}_parsed"] = True
getter_handler = cls.append_handler(_key_getter)
parser_handler = cls.append_handler(_key_parser)
def _decorator(func: T_Handler) -> T_Handler: 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) get_depend = DependsWrapper(_key_getter)
async def wrapper(bot: "Bot", event: "Event", state: T_State, parser_depend = DependsWrapper(_key_parser)
matcher: Matcher):
await parser(matcher, bot, event, state)
await func_handler(matcher, bot, event, state)
if "_current_key" in state:
del state["_current_key"]
wrapper_handler = cls.append_handler(wrapper) if cls.handlers and cls.handlers[-1].func is func:
func_handler = cls.handlers[-1]
getter_handler.update_signature( func_handler.prepend_dependency(parser_depend)
bot=wrapper_handler.bot_type, func_handler.prepend_dependency(get_depend)
event=wrapper_handler.event_type) else:
parser_handler.update_signature( cls.append_handler(func,
bot=wrapper_handler.bot_type, dependencies=[get_depend, parser_depend])
event=wrapper_handler.event_type)
return func return func
return _decorator return _decorator
@classmethod @classmethod
async def send(cls, message: Union[str, "Message", "MessageSegment", async def send(cls, message: Union[str, Message, MessageSegment,
MessageTemplate], **kwargs) -> Any: MessageTemplate], **kwargs) -> Any:
""" """
:说明: :说明:
@ -496,7 +514,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod @classmethod
async def finish(cls, async def finish(cls,
message: Optional[Union[str, "Message", "MessageSegment", message: Optional[Union[str, Message, MessageSegment,
MessageTemplate]] = None, MessageTemplate]] = None,
**kwargs) -> NoReturn: **kwargs) -> NoReturn:
""" """
@ -522,7 +540,7 @@ class Matcher(metaclass=MatcherMeta):
@classmethod @classmethod
async def pause(cls, async def pause(cls,
prompt: Optional[Union[str, "Message", "MessageSegment", prompt: Optional[Union[str, Message, MessageSegment,
MessageTemplate]] = None, MessageTemplate]] = None,
**kwargs) -> NoReturn: **kwargs) -> NoReturn:
""" """
@ -548,8 +566,8 @@ class Matcher(metaclass=MatcherMeta):
@classmethod @classmethod
async def reject(cls, async def reject(cls,
prompt: Optional[Union[str, "Message", prompt: Optional[Union[str, Message,
"MessageSegment"]] = None, MessageSegment]] = None,
**kwargs) -> NoReturn: **kwargs) -> NoReturn:
""" """
:说明: :说明:
@ -564,6 +582,8 @@ class Matcher(metaclass=MatcherMeta):
bot = current_bot.get() bot = current_bot.get()
event = current_event.get() event = current_event.get()
state = current_state.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): if isinstance(prompt, MessageTemplate):
_prompt = prompt.format(**state) _prompt = prompt.format(**state)
else: else:
@ -581,7 +601,12 @@ class Matcher(metaclass=MatcherMeta):
self.block = True self.block = True
# 运行handlers # 运行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) b_t = current_bot.set(bot)
e_t = current_event.set(event) e_t = current_event.set(event)
s_t = current_state.set(self.state) s_t = current_state.set(self.state)
@ -594,7 +619,15 @@ class Matcher(metaclass=MatcherMeta):
while self.handlers: while self.handlers:
handler = self.handlers.pop(0) handler = self.handlers.pop(0)
logger.debug(f"Running handler {handler}") 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: except RejectedException:
self.handlers.insert(0, handler) # type: ignore self.handlers.insert(0, handler) # type: ignore
@ -610,11 +643,8 @@ class Matcher(metaclass=MatcherMeta):
updater = self.__class__._default_permission_updater updater = self.__class__._default_permission_updater
if updater: if updater:
permission = await updater( permission = await updater(bot, event, self.state,
bot, self.permission)
event,
self.state, # type: ignore
self.permission)
else: else:
permission = USER(event.get_session_id(), perm=self.permission) permission = USER(event.get_session_id(), perm=self.permission)
@ -647,11 +677,8 @@ class Matcher(metaclass=MatcherMeta):
updater = self.__class__._default_permission_updater updater = self.__class__._default_permission_updater
if updater: if updater:
permission = await updater( permission = await updater(bot, event, self.state,
bot, self.permission)
event,
self.state, # type: ignore
self.permission)
else: else:
permission = USER(event.get_session_id(), perm=self.permission) permission = USER(event.get_session_id(), perm=self.permission)

View File

@ -7,23 +7,39 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供
import asyncio import asyncio
from datetime import datetime 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.log import logger
from nonebot.rule import TrieRule from nonebot.rule import TrieRule
from nonebot.handler import Handler
from nonebot.utils import escape_tag from nonebot.utils import escape_tag
from nonebot.matcher import Matcher, matchers from nonebot.matcher import Matcher, matchers
from nonebot.exception import NoLogException, StopPropagation, IgnoredException from nonebot.exception import NoLogException, StopPropagation, IgnoredException
from nonebot.typing import (T_State, T_RunPreProcessor, T_RunPostProcessor, from nonebot.typing import (T_State, T_DependencyCache, T_RunPreProcessor,
T_EventPreProcessor, T_EventPostProcessor) T_RunPostProcessor, T_EventPreProcessor,
T_EventPostProcessor)
if TYPE_CHECKING: if TYPE_CHECKING:
from nonebot.adapters import Bot, Event from nonebot.adapters import Bot, Event
_event_preprocessors: Set[T_EventPreProcessor] = set() _event_preprocessors: Set[Handler] = set()
_event_postprocessors: Set[T_EventPostProcessor] = set() _event_postprocessors: Set[Handler] = set()
_run_preprocessors: Set[T_RunPreProcessor] = set() _run_preprocessors: Set[Handler] = set()
_run_postprocessors: Set[T_RunPostProcessor] = 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: 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 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 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 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 return func
async def _check_matcher(priority: int, Matcher: Type[Matcher], bot: "Bot", async def _check_matcher(
event: "Event", state: T_State) -> None: 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: if Matcher.expire_time and datetime.now() > Matcher.expire_time:
try: try:
matchers[priority].remove(Matcher) matchers[priority].remove(Matcher)
@ -112,7 +99,9 @@ async def _check_matcher(priority: int, Matcher: Type[Matcher], bot: "Bot",
try: try:
if not await Matcher.check_perm( 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 return
except Exception as e: except Exception as e:
logger.opt(colors=True, exception=e).error( logger.opt(colors=True, exception=e).error(
@ -125,17 +114,29 @@ async def _check_matcher(priority: int, Matcher: Type[Matcher], bot: "Bot",
except Exception: except Exception:
pass 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", async def _run_matcher(
state: T_State) -> None: 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}") logger.info(f"Event will be handled by {Matcher}")
matcher = Matcher() matcher = Matcher()
coros = list( 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: if coros:
try: try:
await asyncio.gather(*coros) await asyncio.gather(*coros)
@ -153,7 +154,7 @@ async def _run_matcher(Matcher: Type[Matcher], bot: "Bot", event: "Event",
try: try:
logger.debug(f"Running matcher {matcher}") 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: except Exception as e:
logger.opt(colors=True, exception=e).error( logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Running matcher {matcher} failed.</bg #f8bbd0></r>" f"<r><bg #f8bbd0>Running matcher {matcher} failed.</bg #f8bbd0></r>"
@ -161,7 +162,14 @@ async def _run_matcher(Matcher: Type[Matcher], bot: "Bot", event: "Event",
exception = e exception = e
coros = list( 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)) _run_postprocessors))
if coros: if coros:
try: try:
@ -203,59 +211,79 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
if show_log: if show_log:
logger.opt(colors=True).success(log_msg) logger.opt(colors=True).success(log_msg)
state = {} state: Dict[Any, Any] = {}
coros = list(map(lambda x: x(bot, event, state), _event_preprocessors)) dependency_cache: T_DependencyCache = {}
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 <b>ignored</b>")
return
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
"Event ignored!</bg #f8bbd0></r>")
return
# Trie Match async with AsyncExitStack() as stack:
_, _ = TrieRule.get_value(bot, event, state) coros = list(
map(
break_flag = False lambda x: x(bot=bot,
for priority in sorted(matchers.keys()): event=event,
if break_flag: state=state,
break _stack=stack,
_dependency_cache=dependency_cache),
if show_log: _event_preprocessors))
logger.debug(f"Checking for matchers in priority {priority}...") if coros:
try:
pending_tasks = [ if show_log:
_check_matcher(priority, matcher, bot, event, state.copy()) logger.debug("Running PreProcessors...")
for matcher in matchers[priority] await asyncio.gather(*coros)
] except IgnoredException as e:
logger.opt(colors=True).info(
results = await asyncio.gather(*pending_tasks, return_exceptions=True) f"Event {escape_tag(event.get_event_name())} is <b>ignored</b>"
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(
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
) )
return
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
"Event ignored!</bg #f8bbd0></r>")
return
# Trie Match
_, _ = 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: if show_log:
logger.debug("Running PostProcessors...") logger.debug(f"Checking for matchers in priority {priority}...")
await asyncio.gather(*coros)
except Exception as e: pending_tasks = [
logger.opt(colors=True, exception=e).error( _check_matcher(priority, matcher, bot, event, state.copy(),
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>" 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(
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
)
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(
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
)

84
nonebot/params.py Normal file
View File

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

View File

@ -2,7 +2,7 @@ r"""
权限 权限
==== ====
每个 ``Matcher`` 拥有一个 ``Permission`` 其中是 **异步** ``PermissionChecker`` 的集合只要有一个 ``PermissionChecker`` 检查结果为 ``True`` 时就会继续运行 每个 ``Matcher`` 拥有一个 ``Permission`` 其中是 ``PermissionChecker`` 的集合只要有一个 ``PermissionChecker`` 检查结果为 ``True`` 时就会继续运行
\:\:\:tip 提示 \:\:\:tip 提示
``PermissionChecker`` 既可以是 async function 也可以是 sync function ``PermissionChecker`` 既可以是 async function 也可以是 sync function
@ -10,14 +10,14 @@ r"""
""" """
import asyncio 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 from nonebot.typing import T_PermissionChecker
if TYPE_CHECKING:
from nonebot.adapters import Bot, Event
class Permission: class Permission:
""" """
@ -36,15 +36,21 @@ class Permission:
""" """
__slots__ = ("checkers",) __slots__ = ("checkers",)
def __init__( HANDLER_PARAM_TYPES = [
self, *checkers: Callable[["Bot", "Event"], params.BotParam, params.EventParam, params.DefaultParam
Awaitable[bool]]) -> None: ]
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 对象 * ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象 * ``event: Event``: Event 对象
* ``stack: Optional[AsyncExitStack]``: 异步上下文栈
* ``dependency_cache: Optional[Dict[Callable[..., Any], Any]]``: 依赖缓存
:返回: :返回:
@ -73,7 +87,11 @@ class Permission:
if not self.checkers: if not self.checkers:
return True return True
results = await asyncio.gather( 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) return any(results)
def __and__(self, other) -> NoReturn: def __and__(self, other) -> NoReturn:
@ -82,31 +100,27 @@ class Permission:
def __or__( def __or__(
self, other: Optional[Union["Permission", self, other: Optional[Union["Permission",
T_PermissionChecker]]) -> "Permission": T_PermissionChecker]]) -> "Permission":
checkers = self.checkers.copy()
if other is None: if other is None:
return self return self
elif isinstance(other, Permission): elif isinstance(other, Permission):
checkers |= other.checkers return Permission(*self.checkers, *other.checkers)
elif asyncio.iscoroutinefunction(other):
checkers.add(other) # type: ignore
else: else:
checkers.add(run_sync(other)) return Permission(*self.checkers, other)
return Permission(*checkers)
async def _message(bot: "Bot", event: "Event") -> bool: async def _message(event: Event) -> bool:
return event.get_type() == "message" return event.get_type() == "message"
async def _notice(bot: "Bot", event: "Event") -> bool: async def _notice(event: Event) -> bool:
return event.get_type() == "notice" return event.get_type() == "notice"
async def _request(bot: "Bot", event: "Event") -> bool: async def _request(event: Event) -> bool:
return event.get_type() == "request" 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" return event.get_type() == "meta_event"
@ -140,14 +154,14 @@ def USER(*user: str, perm: Optional[Permission] = None):
* ``perm: Optional[Permission]``: 需要同时满足的权限 * ``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 return bool(event.get_session_id() in user and
(perm is None or await perm(bot, event))) (perm is None or await perm(bot, event)))
return Permission(_user) 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 return (event.get_type() == "message" and
event.get_user_id() in bot.config.superusers) event.get_user_id() in bot.config.superusers)

View File

@ -2,19 +2,16 @@ import re
import sys import sys
import inspect import inspect
from types import ModuleType from types import ModuleType
from typing import (TYPE_CHECKING, Any, Set, Dict, List, Type, Tuple, Union, from typing import Any, Set, Dict, List, Type, Tuple, Union, Optional
Optional)
from nonebot.adapters import Event
from nonebot.handler import Handler from nonebot.handler import Handler
from nonebot.matcher import Matcher from nonebot.matcher import Matcher
from .manager import _current_plugin from .manager import _current_plugin
from nonebot.permission import Permission from nonebot.permission import Permission
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_StateFactory from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_StateFactory
from nonebot.rule import (Rule, ArgumentParser, regex, command, keyword, from nonebot.rule import (PREFIX_KEY, RAW_CMD_KEY, Rule, ArgumentParser, regex,
endswith, startswith, shell_command) command, keyword, endswith, startswith, shell_command)
if TYPE_CHECKING:
from nonebot.adapters import Bot, Event
def _store_matcher(matcher: Type[Matcher]) -> None: def _store_matcher(matcher: Type[Matcher]) -> None:
@ -376,16 +373,16 @@ def on_command(cmd: Union[str, Tuple[str, ...]],
- ``Type[Matcher]`` - ``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() message = event.get_message()
if len(message) < 1: if len(message) < 1:
return return
segment = message.pop(0) segment = message.pop(0)
segment_text = str(segment).lstrip() 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 return
new_message = message.__class__( 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): for new_segment in reversed(new_message):
message.insert(0, new_segment) message.insert(0, new_segment)
@ -433,12 +430,11 @@ def on_shell_command(cmd: Union[str, Tuple[str, ...]],
- ``Type[Matcher]`` - ``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() message = event.get_message()
segment = message.pop(0) segment = message.pop(0)
new_message = message.__class__( new_message = message.__class__(
str(segment) str(segment)[len(state[PREFIX_KEY][RAW_CMD_KEY]):].strip())
[len(state["_prefix"]["raw_command"]):].strip()) # type: ignore
for new_segment in reversed(new_message): for new_segment in reversed(new_message):
message.insert(0, new_segment) message.insert(0, new_segment)

View File

@ -3,14 +3,14 @@ from functools import reduce
from nonebot.rule import to_me from nonebot.rule import to_me
from nonebot.plugin import on_command from nonebot.plugin import on_command
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from nonebot.adapters.cqhttp import (Bot, Message, MessageEvent, MessageSegment, from nonebot.adapters.cqhttp import (Message, MessageEvent, MessageSegment,
unescape) unescape)
say = on_command("say", to_me(), permission=SUPERUSER) say = on_command("say", to_me(), permission=SUPERUSER)
@say.handle() @say.handle()
async def say_unescape(bot: Bot, event: MessageEvent): async def say_unescape(event: MessageEvent):
def _unescape(message: Message, segment: MessageSegment): def _unescape(message: Message, segment: MessageSegment):
if segment.is_text(): if segment.is_text():
@ -18,12 +18,12 @@ async def say_unescape(bot: Bot, event: MessageEvent):
return message.append(segment) return message.append(segment)
message = reduce(_unescape, event.get_message(), Message()) # type: ignore 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 = on_command("echo", to_me())
@echo.handle() @echo.handle()
async def echo_escape(bot: Bot, event: MessageEvent): async def echo_escape(event: MessageEvent):
await bot.send(message=event.get_message(), event=event) await echo.send(message=event.get_message())

View File

@ -1,8 +1,6 @@
from typing import Dict, Optional from typing import Dict
from nonebot.typing import T_State from nonebot.adapters import Event
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event
from nonebot.message import (IgnoredException, run_preprocessor, from nonebot.message import (IgnoredException, run_preprocessor,
run_postprocessor) run_postprocessor)
@ -10,7 +8,7 @@ _running_matcher: Dict[str, int] = {}
@run_preprocessor @run_preprocessor
async def preprocess(matcher: Matcher, bot: Bot, event: Event, state: T_State): async def preprocess(event: Event):
try: try:
session_id = event.get_session_id() session_id = event.get_session_id()
except Exception: except Exception:
@ -24,8 +22,7 @@ async def preprocess(matcher: Matcher, bot: Bot, event: Event, state: T_State):
@run_postprocessor @run_postprocessor
async def postprocess(matcher: Matcher, exception: Optional[Exception], async def postprocess(event: Event):
bot: Bot, event: Event, state: T_State):
try: try:
session_id = event.get_session_id() session_id = event.get_session_id()
except Exception: except Exception:

View File

@ -1 +0,0 @@

View File

@ -2,10 +2,10 @@ r"""
规则 规则
==== ====
每个事件响应器 ``Matcher`` 拥有一个匹配规则 ``Rule`` 其中是 **异步** ``RuleChecker`` 的集合只有当所有 ``RuleChecker`` 检查结果为 ``True`` 时继续运行 每个事件响应器 ``Matcher`` 拥有一个匹配规则 ``Rule`` 其中是 ``RuleChecker`` 的集合只有当所有 ``RuleChecker`` 检查结果为 ``True`` 时继续运行
\:\:\:tip 提示 \:\:\: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 import asyncio
from itertools import product from itertools import product
from argparse import Namespace from argparse import Namespace
from contextlib import AsyncExitStack
from typing_extensions import TypedDict
from argparse import ArgumentParser as ArgParser from argparse import ArgumentParser as ArgParser
from typing import (TYPE_CHECKING, Any, Dict, Tuple, Union, Callable, NoReturn, from typing import (Any, Dict, List, Type, Tuple, Union, Callable, NoReturn,
Optional, Sequence, Awaitable) Optional, Sequence)
from pygtrie import CharTrie from pygtrie import CharTrie
from nonebot import get_driver
from nonebot.log import logger 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.exception import ParserExit
from nonebot.typing import T_State, T_RuleChecker from nonebot.typing import T_State, T_RuleChecker
from nonebot.adapters import Bot, Event, MessageSegment
if TYPE_CHECKING: PREFIX_KEY = "_prefix"
from nonebot.adapters import Bot, Event 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: class Rule:
@ -47,16 +63,22 @@ class Rule:
""" """
__slots__ = ("checkers",) __slots__ = ("checkers",)
def __init__( HANDLER_PARAM_TYPES = [
self, *checkers: Callable[["Bot", "Event", T_State], params.BotParam, params.EventParam, params.StateParam,
Awaitable[bool]]) -> None: 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", async def __call__(
state: T_State) -> bool: 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 对象 * ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象 * ``event: Event``: Event 对象
* ``state: T_State``: 当前 State * ``state: T_State``: 当前 State
* ``stack: Optional[AsyncExitStack]``: 异步上下文栈
* ``dependency_cache: Optional[Dict[Callable[..., Any], Any]]``: 依赖缓存
:返回: :返回:
- ``bool`` - ``bool``
""" """
if not self.checkers:
return True
results = await asyncio.gather( 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) return all(results)
def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule": def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
checkers = self.checkers.copy()
if other is None: if other is None:
return self return self
elif isinstance(other, Rule): elif isinstance(other, Rule):
checkers |= other.checkers return Rule(*self.checkers, *other.checkers)
elif asyncio.iscoroutinefunction(other):
checkers.add(other) # type: ignore
else: else:
checkers.add(run_sync(other)) return Rule(*self.checkers, other)
return Rule(*checkers)
def __or__(self, other) -> NoReturn: def __or__(self, other) -> NoReturn:
raise RuntimeError("Or operation between rules is not allowed.") raise RuntimeError("Or operation between rules is not allowed.")
@ -123,58 +156,28 @@ class TrieRule:
cls.suffix[suffix[::-1]] = value cls.suffix[suffix[::-1]] = value
@classmethod @classmethod
def get_value(cls, bot: "Bot", event: "Event", def get_value(cls, bot: Bot, event: Event,
state: T_State) -> Tuple[Dict[str, Any], Dict[str, Any]]: 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": if event.get_type() != "message":
state["_prefix"] = {"raw_command": None, "command": None} return prefix, suffix
state["_suffix"] = {"raw_command": None, "command": None}
return {
"raw_command": None,
"command": None
}, {
"raw_command": None,
"command": None
}
prefix = None
suffix = None
message = event.get_message() message = event.get_message()
message_seg = message[0] message_seg: MessageSegment = message[0]
if message_seg.is_text(): if message_seg.is_text():
prefix = cls.prefix.longest_prefix(str(message_seg).lstrip()) pf = cls.prefix.longest_prefix(str(message_seg).lstrip())
message_seg_r = message[-1] prefix[RAW_CMD_KEY] = pf.key
prefix[CMD_KEY] = pf.value
message_seg_r: MessageSegment = message[-1]
if message_seg_r.is_text(): if message_seg_r.is_text():
suffix = cls.suffix.longest_prefix( sf = cls.suffix.longest_prefix(str(message_seg_r).rstrip()[::-1])
str(message_seg_r).rstrip()[::-1]) suffix[RAW_CMD_KEY] = sf.key
suffix[CMD_KEY] = sf.value
state["_prefix"] = { return prefix, suffix
"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
})
def startswith(msg: Union[str, Tuple[str, ...]], 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)})", f"^(?:{'|'.join(re.escape(prefix) for prefix in msg)})",
re.IGNORECASE if ignorecase else 0) 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": if event.get_type() != "message":
return False return False
text = event.get_plaintext() text = event.get_plaintext()
@ -222,7 +225,7 @@ def endswith(msg: Union[str, Tuple[str, ...]],
f"(?:{'|'.join(re.escape(prefix) for prefix in msg)})$", f"(?:{'|'.join(re.escape(prefix) for prefix in msg)})$",
re.IGNORECASE if ignorecase else 0) 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": if event.get_type() != "message":
return False return False
text = event.get_plaintext() text = event.get_plaintext()
@ -242,7 +245,7 @@ def keyword(*keywords: str) -> Rule:
* ``*keywords: str``: 关键词 * ``*keywords: str``: 关键词
""" """
async def _keyword(bot: "Bot", event: "Event", state: T_State) -> bool: async def _keyword(event: Event) -> bool:
if event.get_type() != "message": if event.get_type() != "message":
return False return False
text = event.get_plaintext() 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): for start, sep in product(command_start, command_sep):
TrieRule.add_prefix(f"{start}{sep.join(command)}", command) TrieRule.add_prefix(f"{start}{sep.join(command)}", command)
async def _command(bot: "Bot", event: "Event", state: T_State) -> bool: async def _command(state: T_State) -> bool:
return state["_prefix"]["command"] in commands return state[PREFIX_KEY][CMD_KEY] in commands
return Rule(_command) return Rule(_command)
@ -310,7 +313,7 @@ class ArgumentParser(ArgParser):
old_message += message old_message += message
setattr(self, "message", old_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, raise ParserExit(status=status,
message=message or getattr(self, "message", None)) 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): for start, sep in product(command_start, command_sep):
TrieRule.add_prefix(f"{start}{sep.join(command)}", command) TrieRule.add_prefix(f"{start}{sep.join(command)}", command)
async def _shell_command(bot: "Bot", event: "Event", async def _shell_command(event: Event, state: T_State) -> bool:
state: T_State) -> bool: if state[PREFIX_KEY][CMD_KEY] in commands:
if state["_prefix"]["command"] in commands:
message = str(event.get_message()) message = str(event.get_message())
strip_message = message[len(state["_prefix"]["raw_command"] strip_message = message[len(state[PREFIX_KEY][RAW_CMD_KEY]
):].lstrip() ):].lstrip()
state["argv"] = shlex.split(strip_message) state[SHELL_ARGV] = shlex.split(strip_message)
if parser: if parser:
try: try:
args = parser.parse_args(state["argv"]) args = parser.parse_args(state[SHELL_ARGV])
state["args"] = args state[SHELL_ARGS] = args
except ParserExit as e: except ParserExit as e:
state["args"] = e state[SHELL_ARGS] = e
return True return True
else: else:
return False return False
@ -417,14 +419,14 @@ def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
pattern = re.compile(regex, flags) 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": if event.get_type() != "message":
return False return False
matched = pattern.search(str(event.get_message())) matched = pattern.search(str(event.get_message()))
if matched: if matched:
state["_matched"] = matched.group() state[REGEX_MATCHED] = matched.group()
state["_matched_groups"] = matched.groups() state[REGEX_GROUP] = matched.groups()
state["_matched_dict"] = matched.groupdict() state[REGEX_DICT] = matched.groupdict()
return True return True
else: else:
return False return False
@ -432,6 +434,10 @@ def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
return Rule(_regex) return Rule(_regex)
async def _to_me(event: Event) -> bool:
return event.is_tome()
def to_me() -> Rule: 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) return Rule(_to_me)

View File

@ -17,16 +17,15 @@
.. _typing: .. _typing:
https://docs.python.org/3/library/typing.html 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, from typing import (TYPE_CHECKING, Any, Dict, Union, TypeVar, Callable,
NoReturn, Optional, Awaitable) NoReturn, Optional, Awaitable)
if TYPE_CHECKING: if TYPE_CHECKING:
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event from nonebot.adapters import Bot, Event
from nonebot.permission import Permission from nonebot.permission import Permission
T_Wrapped = TypeVar("T_Wrapped", bound=BaseCallable) T_Wrapped = TypeVar("T_Wrapped", bound=Callable)
def overrides(InterfaceClass: object): def overrides(InterfaceClass: object):
@ -90,77 +89,109 @@ T_CalledAPIHook = Callable[
``bot.call_api`` 后执行的函数参数分别为 bot, exception, api, data, result ``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 类型 事件预处理函数 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 类型 事件预处理函数 EventPostProcessor 类型
""" """
T_RunPreProcessor = Callable[["Matcher", "Bot", "Event", T_State], T_RunPreProcessor = Callable[..., Union[None, Awaitable[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 类型 事件响应器运行前预处理函数 RunPreProcessor 类型
""" """
T_RunPostProcessor = Callable[ T_RunPostProcessor = Callable[..., Union[None, Awaitable[None]]]
["Matcher", Optional[Exception], "Bot", "Event", T_State], 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 类型第二个参数为运行时产生的错误如果存在 事件响应器运行前预处理函数 RunPostProcessor 类型第二个参数为运行时产生的错误如果存在
""" """
T_RuleChecker = Callable[["Bot", "Event", T_State], Union[bool, T_RuleChecker = Callable[..., Union[bool, Awaitable[bool]]]
Awaitable[bool]]]
""" """
:类型: ``Callable[[Bot, Event, T_State], Union[bool, Awaitable[bool]]]`` :类型: ``Callable[..., Union[bool, Awaitable[bool]]]``
:依赖参数:
* ``BotParam``: Bot 对象
* ``EventParam``: Event 对象
* ``StateParam``: State 对象
:说明: :说明:
RuleChecker 即判断是否响应事件的处理函数 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 即判断是否响应消息的处理函数 RuleChecker 即判断是否响应消息的处理函数
""" """
T_Handler = Union[Callable[[Any, Any, Any, Any], Union[Awaitable[None], T_Handler = Callable[..., Any]
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]]]]
""" """
:类型: :类型: ``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]]]``
:说明: :说明:
Handler 即事件的处理函数 Handler 处理函数
"""
T_DependencyCache = Dict[T_Handler, Any]
"""
:类型: ``Dict[T_Handler, Any]``
:说明:
依赖缓存, 用于存储依赖函数的返回值
""" """
T_ArgsParser = Callable[["Bot", "Event", T_State], Union[Awaitable[None], T_ArgsParser = Callable[["Bot", "Event", T_State], Union[Awaitable[None],
Awaitable[NoReturn]]] Awaitable[NoReturn]]]

View File

@ -1,13 +1,23 @@
import re import re
import json import json
import asyncio import asyncio
import inspect
import collections
import dataclasses import dataclasses
from functools import wraps, partial 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.log import logger
from nonebot.typing import overrides from nonebot.typing import overrides
P = ParamSpec("P")
R = TypeVar("R")
T = TypeVar("T")
def escape_tag(s: str) -> str: def escape_tag(s: str) -> str:
""" """
@ -26,7 +36,48 @@ def escape_tag(s: str) -> str:
return re.sub(r"</?((?:[fb]g\s)?[^<>\s]*)>", r"\\\g<0>", s) return re.sub(r"</?((?:[fb]g\s)?[^<>\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) @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() loop = asyncio.get_running_loop()
pfunc = partial(func, *args, **kwargs) pfunc = partial(func, *args, **kwargs)
result = await loop.run_in_executor(None, pfunc) result = await loop.run_in_executor(None, pfunc)
@ -51,6 +102,98 @@ def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]:
return _wrapper 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): class DataclassEncoder(json.JSONEncoder):
""" """
:说明: :说明:

View File

@ -5,13 +5,12 @@ from typing import TYPE_CHECKING, List, Type, Optional
from pydantic import BaseModel from pydantic import BaseModel
from pygtrie import StringTrie from pygtrie import StringTrie
from .message import Message
from nonebot.typing import overrides from nonebot.typing import overrides
from nonebot.utils import escape_tag from nonebot.utils import escape_tag
from nonebot.exception import NoLogException from .exception import NoLogException
from nonebot.adapters import Event as BaseEvent from nonebot.adapters import Event as BaseEvent
from .message import Message
if TYPE_CHECKING: if TYPE_CHECKING:
from .bot import Bot from .bot import Bot

View File

@ -1,9 +1,10 @@
from typing import Optional from typing import Optional
from nonebot.exception import (AdapterException, ActionFailed as from nonebot.exception import AdapterException
BaseActionFailed, NetworkError as from nonebot.exception import ActionFailed as BaseActionFailed
BaseNetworkError, ApiNotAvailable as from nonebot.exception import NetworkError as BaseNetworkError
BaseApiNotAvailable) from nonebot.exception import NoLogException as BaseNoLogException
from nonebot.exception import ApiNotAvailable as BaseApiNotAvailable
class CQHTTPAdapterException(AdapterException): class CQHTTPAdapterException(AdapterException):
@ -12,6 +13,10 @@ class CQHTTPAdapterException(AdapterException):
super().__init__("cqhttp") super().__init__("cqhttp")
class NoLogException(BaseNoLogException, CQHTTPAdapterException):
pass
class ActionFailed(BaseActionFailed, CQHTTPAdapterException): class ActionFailed(BaseActionFailed, CQHTTPAdapterException):
""" """
:说明: :说明:

View File

@ -1,26 +1,21 @@
from typing import TYPE_CHECKING from nonebot.adapters import Event
from nonebot.permission import Permission from nonebot.permission import Permission
from .event import GroupMessageEvent, PrivateMessageEvent
from .event import PrivateMessageEvent, GroupMessageEvent
if TYPE_CHECKING:
from nonebot.adapters import Bot, Event
async def _private(bot: "Bot", event: "Event") -> bool: async def _private(event: Event) -> bool:
return isinstance(event, PrivateMessageEvent) 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" 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" 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" 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) return isinstance(event, GroupMessageEvent)
async def _group_member(bot: "Bot", event: "Event") -> bool: async def _group_member(event: Event) -> bool:
return isinstance(event, return isinstance(event,
GroupMessageEvent) and event.sender.role == "member" 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" 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" return isinstance(event, GroupMessageEvent) and event.sender.role == "owner"

View File

@ -4,11 +4,13 @@ sidebar: auto
# 更新日志 # 更新日志
## v2.0.0a17 ## v2.0.0b1
- 新增 `MessageTemplate` 对于 `str` 普通模板的支持 - 新增 `MessageTemplate` 对于 `str` 普通模板的支持
- 移除插件加载的 `NameSpace` 模式 - 移除插件加载的 `NameSpace` 模式
- 修改 toml 加载插件时的键名为 `tool.nonebot` 以符合规范 - 修改 toml 加载插件时的键名为 `tool.nonebot` 以符合规范
- 新增 Handler 依赖注入支持
- 统一 `Processor`, `Rule`, `Permission` 使用 `Handler`
## v2.0.0a16 ## v2.0.0a16

257
poetry.lock generated
View File

@ -33,7 +33,7 @@ python-versions = ">=3.6,<4.0"
[[package]] [[package]]
name = "aiohttp" name = "aiohttp"
version = "3.8.0" version = "3.8.1"
description = "Async http client/server framework (asyncio)" description = "Async http client/server framework (asyncio)"
category = "main" category = "main"
optional = true optional = true
@ -297,7 +297,7 @@ python-versions = ">=3.5"
[[package]] [[package]]
name = "httpcore" name = "httpcore"
version = "0.13.7" version = "0.14.2"
description = "A minimal low-level HTTP client." description = "A minimal low-level HTTP client."
category = "main" category = "main"
optional = false optional = false
@ -305,6 +305,7 @@ python-versions = ">=3.6"
[package.dependencies] [package.dependencies]
anyio = ">=3.0.0,<4.0.0" anyio = ">=3.0.0,<4.0.0"
certifi = "*"
h11 = ">=0.11,<0.13" h11 = ">=0.11,<0.13"
sniffio = ">=1.0.0,<2.0.0" sniffio = ">=1.0.0,<2.0.0"
@ -324,7 +325,7 @@ test = ["Cython (==0.29.22)"]
[[package]] [[package]]
name = "httpx" name = "httpx"
version = "0.20.0" version = "0.21.1"
description = "The next generation HTTP client." description = "The next generation HTTP client."
category = "main" category = "main"
optional = false optional = false
@ -334,7 +335,7 @@ python-versions = ">=3.6"
certifi = "*" certifi = "*"
charset-normalizer = "*" charset-normalizer = "*"
h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""} 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"]} rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
sniffio = "*" sniffio = "*"
@ -771,7 +772,7 @@ python-versions = ">=3.5"
[[package]] [[package]]
name = "snowballstemmer" name = "snowballstemmer"
version = "2.1.0" version = "2.2.0"
description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
category = "dev" category = "dev"
optional = false optional = false
@ -828,7 +829,7 @@ yapf = "*"
type = "git" type = "git"
url = "https://github.com/nonebot/sphinx-markdown-builder.git" url = "https://github.com/nonebot/sphinx-markdown-builder.git"
reference = "master" reference = "master"
resolved_reference = "7a8c8a66dfe42436b4584d1d13f5d0127fc83301" resolved_reference = "2204923f5938a8f7354c6a69ed58079edd180a43"
[[package]] [[package]]
name = "sphinxcontrib-applehelp" name = "sphinxcontrib-applehelp"
@ -934,11 +935,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "3.10.0.2" version = "4.0.0"
description = "Backported and Experimental Type Hints for Python 3.5+" description = "Backported and Experimental Type Hints for Python 3.6+"
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = ">=3.6"
[[package]] [[package]]
name = "unify" name = "unify"
@ -1019,7 +1020,7 @@ python-versions = ">=3.5"
[[package]] [[package]]
name = "websockets" name = "websockets"
version = "10.0" version = "10.1"
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
category = "main" category = "main"
optional = false optional = false
@ -1099,7 +1100,7 @@ quart = ["Quart"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7.3" python-versions = "^3.7.3"
content-hash = "51f4f0ce5ced234a65cae790c4f57486e42d7120972657a3f51e733cb4e7c639" content-hash = "537c91f98fd6598dbce8c2942530f18dee0858a896b6f393a684252a77dc76c6"
[metadata.files] [metadata.files]
aiocache = [ aiocache = [
@ -1115,78 +1116,78 @@ aiofiles = [
{file = "aiofiles-0.7.0.tar.gz", hash = "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4"}, {file = "aiofiles-0.7.0.tar.gz", hash = "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4"},
] ]
aiohttp = [ aiohttp = [
{file = "aiohttp-3.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:48f218a5257b6bc16bcf26a91d97ecea0c7d29c811a90d965f3dd97c20f016d6"}, {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"},
{file = "aiohttp-3.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2fee4d656a7cc9ab47771b2a9e8fad8a9a33331c1b59c3057ecf0ac858f5bfe"}, {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"},
{file = "aiohttp-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:688a1eb8c1a5f7e795c7cb67e0fe600194e6723ba35f138dfae0db20c0cb8f94"}, {file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"},
{file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ba09bb3dcb0b7ec936a485db2b64be44fe14cdce0a5eac56f50e55da3627385"}, {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"},
{file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7715daf84f10bcebc083ad137e3eced3e1c8e7fa1f096ade9a8d02b08f0d91c"}, {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"},
{file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e3f81fbbc170418e22918a9585fd7281bbc11d027064d62aa4b507552c92671"}, {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"},
{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.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"},
{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.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.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a84c335337b676d832c1e2bc47c3a97531b46b82de9f959dafb315cbcbe0dfcd"}, {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"},
{file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88d4917c30fcd7f6404fb1dc713fa21de59d3063dcc048f4a8a1a90e6bbbd739"}, {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"},
{file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b76669b7c058b8020b11008283c3b8e9c61bfd978807c45862956119b77ece45"}, {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"},
{file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:84fe1732648c1bc303a70faa67cbc2f7f2e810c8a5bca94f6db7818e722e4c0a"}, {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"},
{file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:730b7c2b7382194d9985ffdc32ab317e893bca21e0665cb1186bdfbb4089d990"}, {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"},
{file = "aiohttp-3.8.0-cp310-cp310-win32.whl", hash = "sha256:0a96473a1f61d7920a9099bc8e729dc8282539d25f79c12573ee0fdb9c8b66a8"}, {file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"},
{file = "aiohttp-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:764c7c6aa1f78bd77bd9674fc07d1ec44654da1818d0eef9fb48aa8371a3c847"}, {file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"},
{file = "aiohttp-3.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9951c2696c4357703001e1fe6edc6ae8e97553ac630492ea1bf64b429cb712a3"}, {file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"},
{file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0af379221975054162959e00daf21159ff69a712fc42ed0052caddbd70d52ff4"}, {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"},
{file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9689af0f0a89e5032426c143fa3683b0451f06c83bf3b1e27902bd33acfae769"}, {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"},
{file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe4a327da0c6b6e59f2e474ae79d6ee7745ac3279fd15f200044602fa31e3d79"}, {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"},
{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.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"},
{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.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.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:09754a0d5eaab66c37591f2f8fac8f9781a5f61d51aa852a3261c4805ca6b984"}, {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"},
{file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:adf0cb251b1b842c9dee5cfcdf880ba0aae32e841b8d0e6b6feeaef002a267c5"}, {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"},
{file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a4759e85a191de58e0ea468ab6fd9c03941986eee436e0518d7a9291fab122c8"}, {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"},
{file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:28369fe331a59d80393ec82df3d43307c7461bfaf9217999e33e2acc7984ff7c"}, {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"},
{file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2f44d1b1c740a9e2275160d77c73a11f61e8a916191c572876baa7b282bcc934"}, {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"},
{file = "aiohttp-3.8.0-cp36-cp36m-win32.whl", hash = "sha256:e27cde1e8d17b09730801ce97b6e0c444ba2a1f06348b169fd931b51d3402f0d"}, {file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"},
{file = "aiohttp-3.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:15a660d06092b7c92ed17c1dbe6c1eab0a02963992d60e3e8b9d5fa7fa81f01e"}, {file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"},
{file = "aiohttp-3.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:257f4fad1714d26d562572095c8c5cd271d5a333252795cb7a002dca41fdbad7"}, {file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"},
{file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6074a3b2fa2d0c9bf0963f8dfc85e1e54a26114cc8594126bc52d3fa061c40e"}, {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"},
{file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a315ceb813208ef32bdd6ec3a85cbe3cb3be9bbda5fd030c234592fa9116993"}, {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"},
{file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a52b141ff3b923a9166595de6e3768a027546e75052ffba267d95b54267f4ab"}, {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"},
{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.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"},
{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.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.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9e8723c3256641e141cd18f6ce478d54a004138b9f1a36e41083b36d9ecc5fc5"}, {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"},
{file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:14a6f026eca80dfa3d52e86be89feb5cd878f6f4a6adb34457e2c689fd85229b"}, {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"},
{file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c62d4791a8212c885b97a63ef5f3974b2cd41930f0cd224ada9c6ee6654f8150"}, {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"},
{file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:90a97c2ed2830e7974cbe45f0838de0aefc1c123313f7c402e21c29ec063fbb4"}, {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"},
{file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dcc4d5dd5fba3affaf4fd08f00ef156407573de8c63338787614ccc64f96b321"}, {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"},
{file = "aiohttp-3.8.0-cp37-cp37m-win32.whl", hash = "sha256:de42f513ed7a997bc821bddab356b72e55e8396b1b7ba1bf39926d538a76a90f"}, {file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"},
{file = "aiohttp-3.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7d76e8a83396e06abe3df569b25bd3fc88bf78b7baa2c8e4cf4aaf5983af66a3"}, {file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"},
{file = "aiohttp-3.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d79174d96446a02664e2bffc95e7b6fa93b9e6d8314536c5840dff130d0878b"}, {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"},
{file = "aiohttp-3.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a6551057a846bf72c7a04f73de3fcaca269c0bd85afe475ceb59d261c6a938c"}, {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"},
{file = "aiohttp-3.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:871d4fdc56288caa58b1094c20f2364215f7400411f76783ea19ad13be7c8e19"}, {file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"},
{file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ba08a71caa42eef64357257878fb17f3fba3fba6e81a51d170e32321569e079"}, {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"},
{file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51f90dabd9933b1621260b32c2f0d05d36923c7a5a909eb823e429dba0fd2f3e"}, {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"},
{file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f348ebd20554e8bc26e8ef3ed8a134110c0f4bf015b3b4da6a4ddf34e0515b19"}, {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"},
{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.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"},
{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.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.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:577cc2c7b807b174814dac2d02e673728f2e46c7f90ceda3a70ea4bb6d90b769"}, {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"},
{file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6b79f6c31e68b6dafc0317ec453c83c86dd8db1f8f0c6f28e97186563fca87a0"}, {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"},
{file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2bdd655732e38b40f8a8344d330cfae3c727fb257585df923316aabbd489ccb8"}, {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"},
{file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:63fa57a0708573d3c059f7b5527617bd0c291e4559298473df238d502e4ab98c"}, {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"},
{file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d3f90ee275b1d7c942e65b5c44c8fb52d55502a0b9a679837d71be2bd8927661"}, {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"},
{file = "aiohttp-3.8.0-cp38-cp38-win32.whl", hash = "sha256:fa818609357dde5c4a94a64c097c6404ad996b1d38ca977a72834b682830a722"}, {file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"},
{file = "aiohttp-3.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:097ecf52f6b9859b025c1e36401f8aa4573552e887d1b91b4b999d68d0b5a3b3"}, {file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"},
{file = "aiohttp-3.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:be03a7483ad9ea60388f930160bb3728467dd0af538aa5edc60962ee700a0bdc"}, {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"},
{file = "aiohttp-3.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:78d51e35ed163783d721b6f2ce8ce3f82fccfe471e8e50a10fba13a766d31f5a"}, {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"},
{file = "aiohttp-3.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bda75d73e7400e81077b0910c9a60bf9771f715420d7e35fa7739ae95555f195"}, {file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"},
{file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:707adc30ea6918fba725c3cb3fe782d271ba352b22d7ae54a7f9f2e8a8488c41"}, {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"},
{file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f58aa995b905ab82fe228acd38538e7dc1509e01508dcf307dad5046399130f"}, {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"},
{file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c996eb91bfbdab1e01e2c02e7ff678c51e2b28e3a04e26e41691991cc55795"}, {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"},
{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.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"},
{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.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.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:949a605ef3907254b122f845baa0920407080cdb1f73aa64f8d47df4a7f4c4f9"}, {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"},
{file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0d7b056fd3972d353cb4bc305c03f9381583766b7f8c7f1c44478dba69099e33"}, {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"},
{file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f1d39a744101bf4043fa0926b3ead616607578192d0a169974fb5265ab1e9d2"}, {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"},
{file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:67ca7032dfac8d001023fadafc812d9f48bf8a8c3bb15412d9cdcf92267593f4"}, {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"},
{file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cb751ef712570d3bda9a73fd765ff3e1aba943ec5d52a54a0c2e89c7eef9da1e"}, {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"},
{file = "aiohttp-3.8.0-cp39-cp39-win32.whl", hash = "sha256:6d3e027fe291b77f6be9630114a0200b2c52004ef20b94dc50ca59849cd623b3"}, {file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"},
{file = "aiohttp-3.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:3c5e9981e449d54308c6824f172ec8ab63eb9c5f922920970249efee83f7e919"}, {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"},
{file = "aiohttp-3.8.0.tar.gz", hash = "sha256:d3b19d8d183bcfd68b25beebab8dc3308282fe2ca3d6ea3cb4cd101b3c279f8d"}, {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"},
] ]
aiosignal = [ aiosignal = [
{file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {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"}, {file = "html2text-2020.1.16.tar.gz", hash = "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb"},
] ]
httpcore = [ httpcore = [
{file = "httpcore-0.13.7-py3-none-any.whl", hash = "sha256:369aa481b014cf046f7067fddd67d00560f2f00426e79569d99cb11245134af0"}, {file = "httpcore-0.14.2-py3-none-any.whl", hash = "sha256:47d7c8f755719d4a57be0b6e022897e9e963bf9ce4b15b9cc006a38a1cfa2932"},
{file = "httpcore-0.13.7.tar.gz", hash = "sha256:036f960468759e633574d7c121afba48af6419615d36ab8ede979f1ad6276fa3"}, {file = "httpcore-0.14.2.tar.gz", hash = "sha256:ff8f8b9434ec4823f95a30596fbe78039913e706d3e598b0b8955b1e1828e093"},
] ]
httptools = [ httptools = [
{file = "httptools-0.2.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:79dbc21f3612a78b28384e989b21872e2e3cf3968532601544696e4ed0007ce5"}, {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"}, {file = "httptools-0.2.0.tar.gz", hash = "sha256:94505026be56652d7a530ab03d89474dc6021019d6b8682281977163b3471ea0"},
] ]
httpx = [ httpx = [
{file = "httpx-0.20.0-py3-none-any.whl", hash = "sha256:33af5aad9bdc82ef1fc89219c1e36f5693bf9cd0ebe330884df563445682c0f8"}, {file = "httpx-0.21.1-py3-none-any.whl", hash = "sha256:208e5ef2ad4d105213463cfd541898ed9d11851b346473539a8425e644bb7c66"},
{file = "httpx-0.20.0.tar.gz", hash = "sha256:09606d630f070d07f9ff28104fbcea429ea0014c1e89ac90b4d8de8286c40e7b"}, {file = "httpx-0.21.1.tar.gz", hash = "sha256:02af20df486b78892a614a7ccd4e4e86a5409ec4981ab0e422c579a887acad83"},
] ]
hypercorn = [ hypercorn = [
{file = "Hypercorn-0.12.0-py3-none-any.whl", hash = "sha256:485a03dc171549dd802c5a2d4cce2d46daf077fbc06c7db90e0862ebc1bd07c9"}, {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"}, {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
] ]
snowballstemmer = [ snowballstemmer = [
{file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
{file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
] ]
sphinx = [ sphinx = [
{file = "Sphinx-4.3.0-py3-none-any.whl", hash = "sha256:7e2b30da5f39170efcd95c6270f07669d623c276521fee27ad6c380f49d2bf5b"}, {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"}, {file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"},
] ]
typing-extensions = [ typing-extensions = [
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, {file = "typing_extensions-4.0.0-py3-none-any.whl", hash = "sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9"},
{file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, {file = "typing_extensions-4.0.0.tar.gz", hash = "sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed"},
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
] ]
unify = [ unify = [
{file = "unify-0.5.tar.gz", hash = "sha256:8ddce812b2457212b7598fe574c9e6eb3ad69710f445391338270c7f8a71723c"}, {file = "unify-0.5.tar.gz", hash = "sha256:8ddce812b2457212b7598fe574c9e6eb3ad69710f445391338270c7f8a71723c"},
@ -1953,31 +1953,54 @@ watchgod = [
{file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"}, {file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"},
] ]
websockets = [ websockets = [
{file = "websockets-10.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cd8c6f2ec24aedace251017bc7a414525171d4e6578f914acab9349362def4da"}, {file = "websockets-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:38db6e2163b021642d0a43200ee2dec8f4980bdbda96db54fde72b283b54cbfc"},
{file = "websockets-10.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1f6b814cff6aadc4288297cb3a248614829c6e4ff5556593c44a115e9dd49939"}, {file = "websockets-10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1b60fd297adb9fc78375778a5220da7f07bf54d2a33ac781319650413fc6a60"},
{file = "websockets-10.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:01db0ecd1a0ca6702d02a5ed40413e18b7d22f94afb3bbe0d323bac86c42c1c8"}, {file = "websockets-10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3477146d1f87ead8df0f27e8960249f5248dceb7c2741e8bbec9aa5338d0c053"},
{file = "websockets-10.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:82b17524b1ce6ae7f7dd93e4d18e9b9474071e28b65dbf1dfe9b5767778db379"}, {file = "websockets-10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb01ea7b5f52e7125bdc3c5807aeaa2d08a0553979cf2d96a8b7803ea33e15e7"},
{file = "websockets-10.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:8bbf8660c3f833ddc8b1afab90213f2e672a9ddac6eecb3cde968e6b2807c1c7"}, {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.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b8176deb6be540a46695960a765a77c28ac8b2e3ef2ec95d50a4f5df901edb1c"}, {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.0-cp37-cp37m-win32.whl", hash = "sha256:706e200fc7f03bed99ad0574cd1ea8b0951477dd18cc978ccb190683c69dba76"}, {file = "websockets-10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5560558b0dace8312c46aa8915da977db02738ac8ecffbc61acfbfe103e10155"},
{file = "websockets-10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b2600e01c7ca6f840c42c747ffbe0254f319594ed108db847eb3d75f4aacb80"}, {file = "websockets-10.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:667c41351a6d8a34b53857ceb8343a45c85d438ee4fd835c279591db8aeb85be"},
{file = "websockets-10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:085bb8a6e780d30eaa1ba48ac7f3a6707f925edea787cfb761ce5a39e77ac09b"}, {file = "websockets-10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:468f0031fdbf4d643f89403a66383247eb82803430b14fa27ce2d44d2662ca37"},
{file = "websockets-10.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9a4d889162bd48588e80950e07fa5e039eee9deb76a58092e8c3ece96d7ef537"}, {file = "websockets-10.1-cp310-cp310-win32.whl", hash = "sha256:d0d81b46a5c87d443e40ce2272436da8e6092aa91f5fbeb60d1be9f11eff5b4c"},
{file = "websockets-10.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b4ade7569b6fd17912452f9c3757d96f8e4044016b6d22b3b8391e641ca50456"}, {file = "websockets-10.1-cp310-cp310-win_amd64.whl", hash = "sha256:b68b6caecb9a0c6db537aa79750d1b592a841e4f1a380c6196091e65b2ad35f9"},
{file = "websockets-10.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:2a43072e434c041a99f2e1eb9b692df0232a38c37c61d00e9f24db79474329e4"}, {file = "websockets-10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a249139abc62ef333e9e85064c27fefb113b16ffc5686cefc315bdaef3eefbc8"},
{file = "websockets-10.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7f79f02c7f9a8320aff7d3321cd1c7e3a7dbc15d922ac996cca827301ee75238"}, {file = "websockets-10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8877861e3dee38c8d302eee0d5dbefa6663de3b46dc6a888f70cd7e82562d1f7"},
{file = "websockets-10.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:1ac35426fe3e7d3d0fac3d63c8965c76ed67a8fd713937be072bf0ce22808539"}, {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.0-cp38-cp38-win32.whl", hash = "sha256:ff59c6bdb87b31f7e2d596f09353d5a38c8c8ff571b0e2238e8ee2d55ad68465"}, {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.0-cp38-cp38-win_amd64.whl", hash = "sha256:d67646ddd17a86117ae21c27005d83c1895c0cef5d7be548b7549646372f868a"}, {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9f304a22ece735a3da8a51309bc2c010e23961a8f675fae46fdf62541ed62123"},
{file = "websockets-10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:82bd921885231f4a30d9bc550552495b3fc36b1235add6d374e7c65c3babd805"}, {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:189ed478395967d6a98bb293abf04e8815349e17456a0a15511f1088b6cb26e4"},
{file = "websockets-10.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7d2e12e4f901f1bc062dfdf91831712c4106ed18a9a4cdb65e2e5f502124ca37"}, {file = "websockets-10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:08a42856158307e231b199671c4fce52df5786dd3d703f36b5d8ac76b206c485"},
{file = "websockets-10.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:71358c7816e2762f3e4af3adf0040f268e219f5a38cb3487a9d0fc2e554fef6a"}, {file = "websockets-10.1-cp37-cp37m-win32.whl", hash = "sha256:3ef6f73854cded34e78390dbdf40dfdcf0b89b55c0e282468ef92646fce8d13a"},
{file = "websockets-10.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:fe83b3ec9ef34063d86dfe1029160a85f24a5a94271036e5714a57acfdd089a1"}, {file = "websockets-10.1-cp37-cp37m-win_amd64.whl", hash = "sha256:89e985d40d407545d5f5e2e58e1fdf19a22bd2d8cd54d20a882e29f97e930a0a"},
{file = "websockets-10.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:eb282127e9c136f860c6068a4fba5756eb25e755baffb5940b6f1eae071928b2"}, {file = "websockets-10.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:002071169d2e44ce8eb9e5ebac9fbce142ba4b5146eef1cfb16b177a27662657"},
{file = "websockets-10.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:62160772314920397f9d219147f958b33fa27a12c662d4455c9ccbba9a07e474"}, {file = "websockets-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cfae282c2aa7f0c4be45df65c248481f3509f8c40ca8b15ed96c35668ae0ff69"},
{file = "websockets-10.0-cp39-cp39-win32.whl", hash = "sha256:e42a1f1e03437b017af341e9bbfdc09252cd48ef32a8c3c3ead769eab3b17368"}, {file = "websockets-10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:97b4b68a2ddaf5c4707ae79c110bfd874c5be3c6ac49261160fb243fa45d8bbb"},
{file = "websockets-10.0-cp39-cp39-win_amd64.whl", hash = "sha256:c5880442f5fc268f1ef6d37b2c152c114deccca73f48e3a8c48004d2f16f4567"}, {file = "websockets-10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c9407719f42cb77049975410490c58a705da6af541adb64716573e550e5c9db"},
{file = "websockets-10.0.tar.gz", hash = "sha256:c4fc9a1d242317892590abe5b61a9127f1a61740477bfb121743f290b8054002"}, {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 = [ werkzeug = [
{file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"}, {file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"},

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "nonebot2" name = "nonebot2"
version = "2.0.0-alpha.16" version = "2.0.0-beta.1"
description = "An asynchronous python bot framework." description = "An asynchronous python bot framework."
authors = ["yanyongyu <yyy@nonebot.dev>"] authors = ["yanyongyu <yyy@nonebot.dev>"]
license = "MIT" license = "MIT"
@ -28,6 +28,7 @@ pygtrie = "^2.4.1"
tomlkit = "^0.7.0" tomlkit = "^0.7.0"
fastapi = "^0.70.0" fastapi = "^0.70.0"
websockets = ">=9.1" websockets = ">=9.1"
typing-extensions = ">=3.10.0,<5.0.0"
Quart = { version = "^0.15.0", optional = true } Quart = { version = "^0.15.0", optional = true }
httpx = { version = ">=0.20.0, <1.0.0", extras = ["http2"] } httpx = { version = ">=0.20.0, <1.0.0", extras = ["http2"] }
pydantic = { version = "~1.8.0", extras = ["dotenv"] } pydantic = { version = "~1.8.0", extras = ["dotenv"] }

View File

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

View File

@ -1,15 +1,15 @@
from nonebot.adapters import Event
from nonebot.typing import T_State from nonebot.typing import T_State
from nonebot.matcher import Matcher from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event from nonebot.message import run_preprocessor, event_preprocessor
from nonebot.message import event_preprocessor, 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 state["preprocessed"] = True
print(type(event), event) print(type(event), event)
@run_preprocessor @run_preprocessor
async def run(matcher: Matcher, bot: Bot, event: Event, state: T_State): async def run(matcher: Matcher):
print(matcher) print(matcher)