🔀 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:
path: ~/.cache/pypoetry/virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Set up dependencies
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`
基类:`nonebot.exception.NoneBotException`
@ -61,57 +43,21 @@ sidebarDepth: 0
## _exception_ `PausedException`
## _exception_ `IgnoredException`
基类:`nonebot.exception.NoneBotException`
* **说明**
指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler`
可用于用户输入新信息。
指示 NoneBot 应该忽略该事件。可由 PreProcessor 抛出。
* **用法**
* **参数**
可以在 `Handler` 中通过 `Matcher.pause()` 抛出。
## _exception_ `RejectedException`
基类:`nonebot.exception.NoneBotException`
* **说明**
指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`
可用于用户重新输入。
* **用法**
可以在 `Handler` 中通过 `Matcher.reject()` 抛出。
## _exception_ `FinishedException`
基类:`nonebot.exception.NoneBotException`
* **说明**
指示 NoneBot 结束当前 `Handler` 且后续 `Handler` 不再被运行。
可用于结束用户会话。
* **用法**
可以在 `Handler` 中通过 `Matcher.finish()` 抛出。
* `reason`: 忽略事件的原因
@ -132,6 +78,88 @@ sidebarDepth: 0
## _exception_ `MatcherException`
基类:`nonebot.exception.NoneBotException`
* **说明**
所有 Matcher 发生的异常基类。
## _exception_ `SkippedException`
基类:`nonebot.exception.MatcherException`
* **说明**
指示 NoneBot 立即结束当前 `Handler` 的处理,继续处理下一个 `Handler`
* **用法**
可以在 `Handler` 中通过 `Matcher.skip()` 抛出。
## _exception_ `PausedException`
基类:`nonebot.exception.MatcherException`
* **说明**
指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler`
可用于用户输入新信息。
* **用法**
可以在 `Handler` 中通过 `Matcher.pause()` 抛出。
## _exception_ `RejectedException`
基类:`nonebot.exception.MatcherException`
* **说明**
指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`
可用于用户重新输入。
* **用法**
可以在 `Handler` 中通过 `Matcher.reject()` 抛出。
## _exception_ `FinishedException`
基类:`nonebot.exception.MatcherException`
* **说明**
指示 NoneBot 结束当前 `Handler` 且后续 `Handler` 不再被运行。
可用于结束用户会话。
* **用法**
可以在 `Handler` 中通过 `Matcher.finish()` 抛出。
## _exception_ `AdapterException`
基类:`nonebot.exception.NoneBotException`
@ -152,7 +180,7 @@ sidebarDepth: 0
## _exception_ `NoLogException`
基类:`Exception`
基类:`nonebot.exception.AdapterException`
* **说明**

View File

@ -14,12 +14,32 @@ sidebarDepth: 0
基类:`object`
事件处理函数类
事件处理器类。支持依赖注入。
### `__init__(func)`
### `__init__(func, *, name=None, dependencies=None, allow_types=None)`
* **说明**
装饰一个函数为事件处理器。
* **参数**
* `func: Callable[..., Any]`: 事件处理函数。
* `name: Optional[str]`: 事件处理器名称。默认为函数名。
* `dependencies: Optional[List[DependsWrapper]]`: 额外的非参数依赖注入。
* `allow_types: Optional[List[Type[Param]]]`: 允许的参数类型。
装饰事件处理函数以便根据动态参数运行
### `func`
@ -27,7 +47,7 @@ sidebarDepth: 0
* **类型**
`T_Handler`
`Callable[..., Any]`
@ -37,75 +57,45 @@ sidebarDepth: 0
### `signature`
### `name`
* **类型**
`inspect.Signature`
`str`
* **说明**
事件处理函数
事件处理函数名
### _property_ `bot_type`
### `allow_types`
* **类型**
`Union[Type["Bot"], inspect.Parameter.empty]`
`List[Type[Param]]`
* **说明**
事件处理函数接受的 Bot 对象类型
事件处理器允许的参数类型
### _property_ `event_type`
### `dependencies`
* **类型**
`Optional[Union[Type[Event], inspect.Parameter.empty]]`
`List[DependsWrapper]`
* **说明**
事件处理函数接受的 event 类型 / 不需要 event 参数
### _property_ `state_type`
* **类型**
`Optional[Union[T_State, inspect.Parameter.empty]]`
* **说明**
事件处理函数是否接受 state 参数
### _property_ `matcher_type`
* **类型**
`Optional[Union[Type["Matcher"], inspect.Parameter.empty]]`
* **说明**
事件处理函数是否接受 matcher 参数
事件处理器的额外依赖

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

View File

@ -7,7 +7,7 @@ sidebarDepth: 0
## 权限
每个 `Matcher` 拥有一个 `Permission` ,其中是 **异步** `PermissionChecker` 的集合,只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
每个 `Matcher` 拥有一个 `Permission` ,其中是 `PermissionChecker` 的集合,只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。
:::tip 提示
`PermissionChecker` 既可以是 async function 也可以是 sync function
@ -42,7 +42,7 @@ Permission(async_function, run_sync(sync_function))
* **参数**
* `*checkers: Callable[[Bot, Event], Awaitable[bool]]`: **异步** PermissionChecker
* `*checkers: Union[T_PermissionChecker, Handler]`: PermissionChecker
@ -58,11 +58,11 @@ Permission(async_function, run_sync(sync_function))
* **类型**
* `Set[Callable[[Bot, Event], Awaitable[bool]]]`
* `Set[Handler]`
### _async_ `__call__(bot, event)`
### _async_ `__call__(bot, event, stack=None, dependency_cache=None)`
* **说明**
@ -80,6 +80,12 @@ Permission(async_function, run_sync(sync_function))
* `event: Event`: Event 对象
* `stack: Optional[AsyncExitStack]`: 异步上下文栈
* `dependency_cache: Optional[Dict[Callable[..., Any], Any]]`: 依赖缓存
* **返回**

View File

@ -7,10 +7,10 @@ sidebarDepth: 0
## 规则
每个事件响应器 `Matcher` 拥有一个匹配规则 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
每个事件响应器 `Matcher` 拥有一个匹配规则 `Rule` ,其中是 `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。
:::tip 提示
`RuleChecker` 既可以是 async function 也可以是 sync function,但在最终会被 `nonebot.utils.run_sync` 转换为 async function
`RuleChecker` 既可以是 async function 也可以是 sync function
:::
@ -42,7 +42,7 @@ Rule(async_function, run_sync(sync_function))
* **参数**
* `*checkers: Callable[[Bot, Event, T_State], Awaitable[bool]]`: **异步** RuleChecker
* `*checkers: Union[T_RuleChecker, Handler]`: RuleChecker
@ -58,11 +58,11 @@ Rule(async_function, run_sync(sync_function))
* **类型**
* `Set[Callable[[Bot, Event, T_State], Awaitable[bool]]]`
* `Set[Handler]`
### _async_ `__call__(bot, event, state)`
### _async_ `__call__(bot, event, state, stack=None, dependency_cache=None)`
* **说明**
@ -83,6 +83,12 @@ Rule(async_function, run_sync(sync_function))
* `state: T_State`: 当前 State
* `stack: Optional[AsyncExitStack]`: 异步上下文栈
* `dependency_cache: Optional[Dict[Callable[..., Any], Any]]`: 依赖缓存
* **返回**

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

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)
import nonebot.params as params
from nonebot.plugin import export as export
from nonebot.plugin import require as require
from nonebot.plugin import on_regex as on_regex

View File

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

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)
continue
setup_ = cast(HTTPPollingSetup, setup_)
if not bot:
request = await _build_request(setup_)
if not request:
@ -264,7 +266,6 @@ class Driver(ForwardDriver):
bot.request = request
request = cast(HTTPRequest, request)
setup_ = cast(HTTPPollingSetup, setup_)
headers = request.headers
timeout = aiohttp.ClientTimeout(30)

View File

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

View File

@ -6,6 +6,8 @@
这些异常并非所有需要用户处理 NoneBot 内部运行时被捕获并进行对应操作
"""
from typing import Optional
class NoneBotException(Exception):
"""
@ -13,9 +15,33 @@ class NoneBotException(Exception):
所有 NoneBot 发生的异常基类
"""
pass
# Rule Exception
class ParserExit(NoneBotException):
"""
:说明:
``shell command`` 处理消息失败时返回的异常
:参数:
* ``status``
* ``message``
"""
def __init__(self, status: int = 0, message: Optional[str] = None):
self.status = status
self.message = message
def __repr__(self):
return f"<ParserExit status={self.status} message={self.message}>"
def __str__(self):
return self.__repr__()
# Processor Exception
class IgnoredException(NoneBotException):
"""
:说明:
@ -37,71 +63,6 @@ class IgnoredException(NoneBotException):
return self.__repr__()
class ParserExit(NoneBotException):
"""
:说明:
``shell command`` 处理消息失败时返回的异常
:参数:
* ``status``
* ``message``
"""
def __init__(self, status=0, message=None):
self.status = status
self.message = message
def __repr__(self):
return f"<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):
"""
:说明:
@ -112,9 +73,69 @@ class StopPropagation(NoneBotException):
``Matcher.block == True`` 时抛出
"""
pass
# Matcher Exceptions
class MatcherException(NoneBotException):
"""
:说明:
所有 Matcher 发生的异常基类
"""
class SkippedException(MatcherException):
"""
:说明:
指示 NoneBot 立即结束当前 ``Handler`` 的处理继续处理下一个 ``Handler``
:用法:
可以在 ``Handler`` 中通过 ``Matcher.skip()`` 抛出
"""
class PausedException(MatcherException):
"""
:说明:
指示 NoneBot 结束当前 ``Handler`` 并等待下一条消息后继续下一个 ``Handler``
可用于用户输入新信息
:用法:
可以在 ``Handler`` 中通过 ``Matcher.pause()`` 抛出
"""
class RejectedException(MatcherException):
"""
:说明:
指示 NoneBot 结束当前 ``Handler`` 并等待下一条消息后重新运行当前 ``Handler``
可用于用户重新输入
:用法:
可以在 ``Handler`` 中通过 ``Matcher.reject()`` 抛出
"""
class FinishedException(MatcherException):
"""
:说明:
指示 NoneBot 结束当前 ``Handler`` 且后续 ``Handler`` 不再被运行
可用于结束用户会话
:用法:
可以在 ``Handler`` 中通过 ``Matcher.finish()`` 抛出
"""
# Adapter Exceptions
class AdapterException(NoneBotException):
"""
:说明:
@ -130,7 +151,7 @@ class AdapterException(NoneBotException):
self.adapter_name = adapter_name
class NoLogException(Exception):
class NoLogException(AdapterException):
"""
:说明:

View File

@ -5,172 +5,114 @@
该模块实现事件处理函数的封装以实现动态参数等功能
"""
import inspect
from typing import _eval_type # type: ignore
from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Optional,
ForwardRef)
import asyncio
from contextlib import AsyncExitStack
from typing import Any, Dict, List, Type, Callable, Optional
from nonebot.log import logger
from nonebot.typing import T_State, T_Handler
if TYPE_CHECKING:
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Event
from nonebot.utils import get_name, run_sync
from nonebot.dependencies import (Param, Dependent, DependsWrapper,
get_dependent, solve_dependencies,
get_parameterless_sub_dependant)
class Handler:
"""事件处理函数类"""
"""事件处理器类。支持依赖注入。"""
def __init__(self, func: T_Handler):
"""装饰事件处理函数以便根据动态参数运行"""
self.func: T_Handler = func
def __init__(self,
func: Callable[..., Any],
*,
name: Optional[str] = None,
dependencies: Optional[List[DependsWrapper]] = None,
allow_types: Optional[List[Type[Param]]] = None):
"""
:类型: ``T_Handler``
:说明:
装饰一个函数为事件处理器
:参数:
* ``func: Callable[..., Any]``: 事件处理函数
* ``name: Optional[str]``: 事件处理器名称默认为函数名
* ``dependencies: Optional[List[DependsWrapper]]``: 额外的非参数依赖注入
* ``allow_types: Optional[List[Type[Param]]]``: 允许的参数类型
"""
self.func = func
"""
:类型: ``Callable[..., Any]``
:说明: 事件处理函数
"""
self.signature: inspect.Signature = self.get_signature()
self.name = get_name(func) if name is None else name
"""
:类型: ``inspect.Signature``
:说明: 事件处理函数签名
:类型: ``str``
:说明: 事件处理函数名
"""
self.allow_types = allow_types or []
"""
:类型: ``List[Type[Param]]``
:说明: 事件处理器允许的参数类型
"""
self.dependencies = dependencies or []
"""
:类型: ``List[DependsWrapper]``
:说明: 事件处理器的额外依赖
"""
self.sub_dependents: Dict[Callable[..., Any], Dependent] = {}
if dependencies:
for depends in dependencies:
self.cache_dependent(depends)
self.dependent = get_dependent(func=func, allow_types=self.allow_types)
def __repr__(self) -> str:
return (f"<Handler {self.func.__name__}(bot: {self.bot_type}, "
f"event: {self.event_type}, state: {self.state_type}, "
f"matcher: {self.matcher_type})>")
return (
f"<Handler {self.name}({', '.join(map(str, self.dependent.params))})>"
)
def __str__(self) -> str:
return repr(self)
async def __call__(self, matcher: "Matcher", bot: "Bot", event: "Event",
state: T_State):
BotType = ((self.bot_type is not inspect.Parameter.empty) and
inspect.isclass(self.bot_type) and self.bot_type)
if BotType and not isinstance(bot, BotType):
logger.debug(
f"Matcher {matcher} bot type {type(bot)} not match annotation {BotType}, ignored"
)
return
async def __call__(self,
*,
_stack: Optional[AsyncExitStack] = None,
_dependency_cache: Optional[Dict[Callable[..., Any],
Any]] = None,
**params) -> Any:
values, _ = await solve_dependencies(
_dependent=self.dependent,
_stack=_stack,
_sub_dependents=[
self.sub_dependents[dependency.dependency] # type: ignore
for dependency in self.dependencies
],
_dependency_cache=_dependency_cache,
**params)
EventType = ((self.event_type is not inspect.Parameter.empty) and
inspect.isclass(self.event_type) and self.event_type)
if EventType and not isinstance(event, EventType):
logger.debug(
f"Matcher {matcher} event type {type(event)} not match annotation {EventType}, ignored"
)
return
if asyncio.iscoroutinefunction(self.func):
return await self.func(**values)
else:
return await run_sync(self.func)(**values)
args = {"bot": bot, "event": event, "state": state, "matcher": matcher}
await self.func(
**{
k: v
for k, v in args.items()
if self.signature.parameters.get(k, None) is not None
})
def cache_dependent(self, dependency: DependsWrapper):
if not dependency.dependency:
raise ValueError(f"{dependency} has no dependency")
if dependency.dependency in self.sub_dependents:
raise ValueError(f"{dependency} is already in dependencies")
sub_dependant = get_parameterless_sub_dependant(
depends=dependency, allow_types=self.allow_types)
self.sub_dependents[dependency.dependency] = sub_dependant
@property
def bot_type(self) -> Union[Type["Bot"], inspect.Parameter.empty]:
"""
:类型: ``Union[Type["Bot"], inspect.Parameter.empty]``
:说明: 事件处理函数接受的 Bot 对象类型"""
return self.signature.parameters["bot"].annotation
def prepend_dependency(self, dependency: DependsWrapper):
self.cache_dependent(dependency)
self.dependencies.insert(0, dependency)
@property
def event_type(
self) -> Optional[Union[Type["Event"], inspect.Parameter.empty]]:
"""
:类型: ``Optional[Union[Type[Event], inspect.Parameter.empty]]``
:说明: 事件处理函数接受的 event 类型 / 不需要 event 参数
"""
if "event" not in self.signature.parameters:
return None
return self.signature.parameters["event"].annotation
def append_dependency(self, dependency: DependsWrapper):
self.cache_dependent(dependency)
self.dependencies.append(dependency)
@property
def state_type(self) -> Optional[Union[T_State, inspect.Parameter.empty]]:
"""
:类型: ``Optional[Union[T_State, inspect.Parameter.empty]]``
:说明: 事件处理函数是否接受 state 参数
"""
if "state" not in self.signature.parameters:
return None
return self.signature.parameters["state"].annotation
@property
def matcher_type(
self) -> Optional[Union[Type["Matcher"], inspect.Parameter.empty]]:
"""
:类型: ``Optional[Union[Type["Matcher"], inspect.Parameter.empty]]``
:说明: 事件处理函数是否接受 matcher 参数
"""
if "matcher" not in self.signature.parameters:
return None
return self.signature.parameters["matcher"].annotation
def get_signature(self) -> inspect.Signature:
wrapped_signature = self._get_typed_signature()
signature = self._get_typed_signature(False)
self._check_params(signature)
self._check_bot_param(signature)
self._check_bot_param(wrapped_signature)
signature.parameters["bot"].replace(
annotation=wrapped_signature.parameters["bot"].annotation)
if "event" in wrapped_signature.parameters and "event" in signature.parameters:
signature.parameters["event"].replace(
annotation=wrapped_signature.parameters["event"].annotation)
return signature
def update_signature(
self, **kwargs: Union[None, Type["Bot"], Type["Event"], Type["Matcher"],
T_State, inspect.Parameter.empty]
) -> None:
params: List[inspect.Parameter] = []
for param in ["bot", "event", "state", "matcher"]:
sig = self.signature.parameters.get(param, None)
if param in kwargs:
sig = inspect.Parameter(param,
inspect.Parameter.POSITIONAL_OR_KEYWORD,
annotation=kwargs[param])
if sig:
params.append(sig)
self.signature = inspect.Signature(params)
def _get_typed_signature(self,
follow_wrapped: bool = True) -> inspect.Signature:
signature = inspect.signature(self.func, follow_wrapped=follow_wrapped)
globalns = getattr(self.func, "__globals__", {})
typed_params = [
inspect.Parameter(
name=param.name,
kind=param.kind,
default=param.default,
annotation=param.annotation if follow_wrapped else
self._get_typed_annotation(param, globalns),
) for param in signature.parameters.values()
]
typed_signature = inspect.Signature(typed_params)
return typed_signature
def _get_typed_annotation(self, param: inspect.Parameter,
globalns: Dict[str, Any]) -> Any:
try:
if isinstance(param.annotation, str):
return _eval_type(ForwardRef(param.annotation), globalns,
globalns)
else:
return param.annotation
except Exception:
return param.annotation
def _check_params(self, signature: inspect.Signature):
if not set(signature.parameters.keys()) <= {
"bot", "event", "state", "matcher"
}:
raise ValueError(
"Handler param names must in `bot`/`event`/`state`/`matcher`")
def _check_bot_param(self, signature: inspect.Signature):
if not any(
param.name == "bot" for param in signature.parameters.values()):
raise ValueError("Handler missing parameter 'bot'")
def remove_dependency(self, dependency: DependsWrapper):
if not dependency.dependency:
raise ValueError(f"{dependency} has no dependency")
if dependency.dependency in self.sub_dependents:
del self.sub_dependents[dependency.dependency]
if dependency in self.dependencies:
self.dependencies.remove(dependency)

View File

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

View File

@ -7,23 +7,39 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供
import asyncio
from datetime import datetime
from typing import TYPE_CHECKING, Set, Type, Optional
from contextlib import AsyncExitStack
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Optional
from nonebot import params
from nonebot.log import logger
from nonebot.rule import TrieRule
from nonebot.handler import Handler
from nonebot.utils import escape_tag
from nonebot.matcher import Matcher, matchers
from nonebot.exception import NoLogException, StopPropagation, IgnoredException
from nonebot.typing import (T_State, T_RunPreProcessor, T_RunPostProcessor,
T_EventPreProcessor, T_EventPostProcessor)
from nonebot.typing import (T_State, T_DependencyCache, T_RunPreProcessor,
T_RunPostProcessor, T_EventPreProcessor,
T_EventPostProcessor)
if TYPE_CHECKING:
from nonebot.adapters import Bot, Event
_event_preprocessors: Set[T_EventPreProcessor] = set()
_event_postprocessors: Set[T_EventPostProcessor] = set()
_run_preprocessors: Set[T_RunPreProcessor] = set()
_run_postprocessors: Set[T_RunPostProcessor] = set()
_event_preprocessors: Set[Handler] = set()
_event_postprocessors: Set[Handler] = set()
_run_preprocessors: Set[Handler] = set()
_run_postprocessors: Set[Handler] = set()
EVENT_PCS_PARAMS = [
params.BotParam, params.EventParam, params.StateParam, params.DefaultParam
]
RUN_PREPCS_PARAMS = [
params.MatcherParam, params.BotParam, params.EventParam, params.StateParam,
params.DefaultParam
]
RUN_POSTPCS_PARAMS = [
params.MatcherParam, params.ExceptionParam, params.BotParam,
params.EventParam, params.StateParam, params.DefaultParam
]
def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
@ -31,16 +47,8 @@ def event_preprocessor(func: T_EventPreProcessor) -> T_EventPreProcessor:
:说明:
事件预处理装饰一个函数使它在每次接收到事件并分发给各响应器之前执行
:参数:
事件预处理函数接收三个参数
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
* ``state: T_State``: 当前 State
"""
_event_preprocessors.add(func)
_event_preprocessors.add(Handler(func, allow_types=EVENT_PCS_PARAMS))
return func
@ -49,16 +57,8 @@ def event_postprocessor(func: T_EventPostProcessor) -> T_EventPostProcessor:
:说明:
事件后处理装饰一个函数使它在每次接收到事件并分发给各响应器之后执行
:参数:
事件后处理函数接收三个参数
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
* ``state: T_State``: 当前事件运行前 State
"""
_event_postprocessors.add(func)
_event_postprocessors.add(Handler(func, allow_types=EVENT_PCS_PARAMS))
return func
@ -67,17 +67,8 @@ def run_preprocessor(func: T_RunPreProcessor) -> T_RunPreProcessor:
:说明:
运行预处理装饰一个函数使它在每次事件响应器运行前执行
:参数:
运行预处理函数接收四个参数
* ``matcher: Matcher``: 当前要运行的事件响应器
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
* ``state: T_State``: 当前 State
"""
_run_preprocessors.add(func)
_run_preprocessors.add(Handler(func, allow_types=RUN_PREPCS_PARAMS))
return func
@ -86,23 +77,19 @@ def run_postprocessor(func: T_RunPostProcessor) -> T_RunPostProcessor:
:说明:
运行后处理装饰一个函数使它在每次事件响应器运行后执行
:参数:
运行后处理函数接收五个参数
* ``matcher: Matcher``: 运行完毕的事件响应器
* ``exception: Optional[Exception]``: 事件响应器运行错误如果存在
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
* ``state: T_State``: 当前 State
"""
_run_postprocessors.add(func)
_run_postprocessors.add(Handler(func, allow_types=RUN_POSTPCS_PARAMS))
return func
async def _check_matcher(priority: int, Matcher: Type[Matcher], bot: "Bot",
event: "Event", state: T_State) -> None:
async def _check_matcher(
priority: int,
Matcher: Type[Matcher],
bot: "Bot",
event: "Event",
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None) -> None:
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
try:
matchers[priority].remove(Matcher)
@ -112,7 +99,9 @@ async def _check_matcher(priority: int, Matcher: Type[Matcher], bot: "Bot",
try:
if not await Matcher.check_perm(
bot, event) or not await Matcher.check_rule(bot, event, state):
bot, event, stack,
dependency_cache) or not await Matcher.check_rule(
bot, event, state, stack, dependency_cache):
return
except Exception as e:
logger.opt(colors=True, exception=e).error(
@ -125,17 +114,29 @@ async def _check_matcher(priority: int, Matcher: Type[Matcher], bot: "Bot",
except Exception:
pass
await _run_matcher(Matcher, bot, event, state)
await _run_matcher(Matcher, bot, event, state, stack, dependency_cache)
async def _run_matcher(Matcher: Type[Matcher], bot: "Bot", event: "Event",
state: T_State) -> None:
async def _run_matcher(
Matcher: Type[Matcher],
bot: "Bot",
event: "Event",
state: T_State,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[T_DependencyCache] = None) -> None:
logger.info(f"Event will be handled by {Matcher}")
matcher = Matcher()
coros = list(
map(lambda x: x(matcher, bot, event, state), _run_preprocessors))
map(
lambda x: x(matcher=matcher,
bot=bot,
event=event,
state=state,
_stack=stack,
_dependency_cache=dependency_cache),
_run_preprocessors))
if coros:
try:
await asyncio.gather(*coros)
@ -153,7 +154,7 @@ async def _run_matcher(Matcher: Type[Matcher], bot: "Bot", event: "Event",
try:
logger.debug(f"Running matcher {matcher}")
await matcher.run(bot, event, state)
await matcher.run(bot, event, state, stack, dependency_cache)
except Exception as e:
logger.opt(colors=True, exception=e).error(
f"<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
coros = list(
map(lambda x: x(matcher, exception, bot, event, state),
map(
lambda x: x(matcher=matcher,
exception=exception,
bot=bot,
event=event,
state=state,
_stack=stack,
_dependency_cache=dependency_cache),
_run_postprocessors))
if coros:
try:
@ -203,59 +211,79 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
if show_log:
logger.opt(colors=True).success(log_msg)
state = {}
coros = list(map(lambda x: x(bot, event, state), _event_preprocessors))
if coros:
try:
if show_log:
logger.debug("Running PreProcessors...")
await asyncio.gather(*coros)
except IgnoredException as e:
logger.opt(colors=True).info(
f"Event {escape_tag(event.get_event_name())} is <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
state: Dict[Any, Any] = {}
dependency_cache: T_DependencyCache = {}
# Trie Match
_, _ = TrieRule.get_value(bot, event, state)
break_flag = False
for priority in sorted(matchers.keys()):
if break_flag:
break
if show_log:
logger.debug(f"Checking for matchers in priority {priority}...")
pending_tasks = [
_check_matcher(priority, matcher, bot, event, state.copy())
for matcher in matchers[priority]
]
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
for result in results:
if not isinstance(result, Exception):
continue
if isinstance(result, StopPropagation):
break_flag = True
logger.debug("Stop event propagation")
else:
logger.opt(colors=True, exception=result).error(
"<r><bg #f8bbd0>Error when checking Matcher.</bg #f8bbd0></r>"
async with AsyncExitStack() as stack:
coros = list(
map(
lambda x: x(bot=bot,
event=event,
state=state,
_stack=stack,
_dependency_cache=dependency_cache),
_event_preprocessors))
if coros:
try:
if show_log:
logger.debug("Running PreProcessors...")
await asyncio.gather(*coros)
except IgnoredException as e:
logger.opt(colors=True).info(
f"Event {escape_tag(event.get_event_name())} is <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
_, _ = TrieRule.get_value(bot, event, state)
break_flag = False
for priority in sorted(matchers.keys()):
if break_flag:
break
coros = list(map(lambda x: x(bot, event, state), _event_postprocessors))
if coros:
try:
if show_log:
logger.debug("Running PostProcessors...")
await asyncio.gather(*coros)
except Exception as e:
logger.opt(colors=True, exception=e).error(
"<r><bg #f8bbd0>Error when running EventPostProcessors</bg #f8bbd0></r>"
)
logger.debug(f"Checking for matchers in priority {priority}...")
pending_tasks = [
_check_matcher(priority, matcher, bot, event, state.copy(),
stack, dependency_cache)
for matcher in matchers[priority]
]
results = await asyncio.gather(*pending_tasks,
return_exceptions=True)
for result in results:
if not isinstance(result, Exception):
continue
if isinstance(result, StopPropagation):
break_flag = True
logger.debug("Stop event propagation")
else:
logger.opt(colors=True, exception=result).error(
"<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 提示
``PermissionChecker`` 既可以是 async function 也可以是 sync function
@ -10,14 +10,14 @@ r"""
"""
import asyncio
from typing import TYPE_CHECKING, Union, Callable, NoReturn, Optional, Awaitable
from contextlib import AsyncExitStack
from typing import Any, Dict, List, Type, Union, Callable, NoReturn, Optional
from nonebot.utils import run_sync
from nonebot import params
from nonebot.handler import Handler
from nonebot.adapters import Bot, Event
from nonebot.typing import T_PermissionChecker
if TYPE_CHECKING:
from nonebot.adapters import Bot, Event
class Permission:
"""
@ -36,15 +36,21 @@ class Permission:
"""
__slots__ = ("checkers",)
def __init__(
self, *checkers: Callable[["Bot", "Event"],
Awaitable[bool]]) -> None:
HANDLER_PARAM_TYPES = [
params.BotParam, params.EventParam, params.DefaultParam
]
def __init__(self, *checkers: Union[T_PermissionChecker, Handler]) -> None:
"""
:参数:
* ``*checkers: Callable[[Bot, Event], Awaitable[bool]]``: **异步** PermissionChecker
* ``*checkers: Union[T_PermissionChecker, Handler]``: PermissionChecker
"""
self.checkers = set(checkers)
self.checkers = set(
checker if isinstance(checker, Handler) else Handler(
checker, allow_types=self.HANDLER_PARAM_TYPES)
for checker in checkers)
"""
:说明:
@ -52,10 +58,16 @@ class Permission:
:类型:
* ``Set[Callable[[Bot, Event], Awaitable[bool]]]``
* ``Set[Handler]``
"""
async def __call__(self, bot: "Bot", event: "Event") -> bool:
async def __call__(
self,
bot: Bot,
event: Event,
stack: Optional[AsyncExitStack] = None,
dependency_cache: Optional[Dict[Callable[..., Any],
Any]] = None) -> bool:
"""
:说明:
@ -65,6 +77,8 @@ class Permission:
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
* ``stack: Optional[AsyncExitStack]``: 异步上下文栈
* ``dependency_cache: Optional[Dict[Callable[..., Any], Any]]``: 依赖缓存
:返回:
@ -73,7 +87,11 @@ class Permission:
if not self.checkers:
return True
results = await asyncio.gather(
*map(lambda c: c(bot, event), self.checkers))
*(checker(bot=bot,
event=event,
_stack=stack,
_dependency_cache=dependency_cache)
for checker in self.checkers))
return any(results)
def __and__(self, other) -> NoReturn:
@ -82,31 +100,27 @@ class Permission:
def __or__(
self, other: Optional[Union["Permission",
T_PermissionChecker]]) -> "Permission":
checkers = self.checkers.copy()
if other is None:
return self
elif isinstance(other, Permission):
checkers |= other.checkers
elif asyncio.iscoroutinefunction(other):
checkers.add(other) # type: ignore
return Permission(*self.checkers, *other.checkers)
else:
checkers.add(run_sync(other))
return Permission(*checkers)
return Permission(*self.checkers, other)
async def _message(bot: "Bot", event: "Event") -> bool:
async def _message(event: Event) -> bool:
return event.get_type() == "message"
async def _notice(bot: "Bot", event: "Event") -> bool:
async def _notice(event: Event) -> bool:
return event.get_type() == "notice"
async def _request(bot: "Bot", event: "Event") -> bool:
async def _request(event: Event) -> bool:
return event.get_type() == "request"
async def _metaevent(bot: "Bot", event: "Event") -> bool:
async def _metaevent(event: Event) -> bool:
return event.get_type() == "meta_event"
@ -140,14 +154,14 @@ def USER(*user: str, perm: Optional[Permission] = None):
* ``perm: Optional[Permission]``: 需要同时满足的权限
"""
async def _user(bot: "Bot", event: "Event") -> bool:
async def _user(bot: Bot, event: Event) -> bool:
return bool(event.get_session_id() in user and
(perm is None or await perm(bot, event)))
return Permission(_user)
async def _superuser(bot: "Bot", event: "Event") -> bool:
async def _superuser(bot: Bot, event: Event) -> bool:
return (event.get_type() == "message" and
event.get_user_id() in bot.config.superusers)

View File

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

View File

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

View File

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

View File

@ -1 +0,0 @@

View File

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

View File

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

View File

@ -1,13 +1,23 @@
import re
import json
import asyncio
import inspect
import collections
import dataclasses
from functools import wraps, partial
from typing import Any, Callable, Optional, Awaitable
from contextlib import asynccontextmanager
from typing_extensions import GenericAlias # type: ignore
from typing_extensions import ParamSpec, get_args, get_origin
from typing import (Any, Type, Deque, Tuple, Union, TypeVar, Callable, Optional,
Awaitable, AsyncGenerator, ContextManager)
from nonebot.log import logger
from nonebot.typing import overrides
P = ParamSpec("P")
R = TypeVar("R")
T = TypeVar("T")
def escape_tag(s: str) -> str:
"""
@ -26,7 +36,48 @@ def escape_tag(s: str) -> str:
return re.sub(r"</?((?:[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)
async def _wrapper(*args: Any, **kwargs: Any) -> Any:
async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
loop = asyncio.get_running_loop()
pfunc = partial(func, *args, **kwargs)
result = await loop.run_in_executor(None, pfunc)
@ -51,6 +102,98 @@ def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]:
return _wrapper
@asynccontextmanager
async def run_sync_ctx_manager(
cm: ContextManager[T],) -> AsyncGenerator[T, None]:
try:
yield await run_sync(cm.__enter__)()
except Exception as e:
ok = await run_sync(cm.__exit__)(type(e), e, None)
if not ok:
raise e
else:
await run_sync(cm.__exit__)(None, None, None)
def get_name(obj: Any) -> str:
if inspect.isfunction(obj) or inspect.isclass(obj):
return obj.__name__
return obj.__class__.__name__
class CacheLock:
def __init__(self):
self._waiters: Optional[Deque[asyncio.Future]] = None
self._locked = False
def __repr__(self):
extra = "locked" if self._locked else "unlocked"
if self._waiters:
extra = f"{extra}, waiters: {len(self._waiters)}"
return f"<{self.__class__.__name__} [{extra}]>"
async def __aenter__(self):
await self.acquire()
return None
async def __aexit__(self, exc_type, exc, tb):
self.release()
def locked(self):
return self._locked
async def acquire(self):
if (not self._locked and (self._waiters is None or
all(w.cancelled() for w in self._waiters))):
self._locked = True
return True
if self._waiters is None:
self._waiters = collections.deque()
loop = asyncio.get_running_loop()
future = loop.create_future()
self._waiters.append(future)
# Finally block should be called before the CancelledError
# handling as we don't want CancelledError to call
# _wake_up_first() and attempt to wake up itself.
try:
try:
await future
finally:
self._waiters.remove(future)
except asyncio.CancelledError:
if not self._locked:
self._wake_up_first()
raise
self._locked = True
return True
def release(self):
if self._locked:
self._locked = False
self._wake_up_first()
else:
raise RuntimeError("Lock is not acquired.")
def _wake_up_first(self):
if not self._waiters:
return
try:
future = next(iter(self._waiters))
except StopIteration:
return
# .done() necessarily means that a waiter will wake up later on and
# either take the lock, or, if it was cancelled and lock wasn't
# taken already, will hit this again and wake up a new waiter.
if not future.done():
future.set_result(True)
class DataclassEncoder(json.JSONEncoder):
"""
:说明:

View File

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

View File

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

View File

@ -1,26 +1,21 @@
from typing import TYPE_CHECKING
from nonebot.adapters import Event
from nonebot.permission import Permission
from .event import PrivateMessageEvent, GroupMessageEvent
if TYPE_CHECKING:
from nonebot.adapters import Bot, Event
from .event import GroupMessageEvent, PrivateMessageEvent
async def _private(bot: "Bot", event: "Event") -> bool:
async def _private(event: Event) -> bool:
return isinstance(event, PrivateMessageEvent)
async def _private_friend(bot: "Bot", event: "Event") -> bool:
async def _private_friend(event: Event) -> bool:
return isinstance(event, PrivateMessageEvent) and event.sub_type == "friend"
async def _private_group(bot: "Bot", event: "Event") -> bool:
async def _private_group(event: Event) -> bool:
return isinstance(event, PrivateMessageEvent) and event.sub_type == "group"
async def _private_other(bot: "Bot", event: "Event") -> bool:
async def _private_other(event: Event) -> bool:
return isinstance(event, PrivateMessageEvent) and event.sub_type == "other"
@ -42,20 +37,20 @@ PRIVATE_OTHER = Permission(_private_other)
"""
async def _group(bot: "Bot", event: "Event") -> bool:
async def _group(event: Event) -> bool:
return isinstance(event, GroupMessageEvent)
async def _group_member(bot: "Bot", event: "Event") -> bool:
async def _group_member(event: Event) -> bool:
return isinstance(event,
GroupMessageEvent) and event.sender.role == "member"
async def _group_admin(bot: "Bot", event: "Event") -> bool:
async def _group_admin(event: Event) -> bool:
return isinstance(event, GroupMessageEvent) and event.sender.role == "admin"
async def _group_owner(bot: "Bot", event: "Event") -> bool:
async def _group_owner(event: Event) -> bool:
return isinstance(event, GroupMessageEvent) and event.sender.role == "owner"

View File

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

257
poetry.lock generated
View File

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

View File

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

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