Feature: 细化 driver 职责类型 (#2296)

This commit is contained in:
Ju4tCode 2023-08-26 11:03:24 +08:00 committed by GitHub
parent 807a86371d
commit 2e635370bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 632 additions and 284 deletions

View File

@ -53,7 +53,7 @@ from nonebot.config import Env, Config
from nonebot.log import logger as logger
from nonebot.adapters import Bot, Adapter
from nonebot.utils import escape_tag, resolve_dot_notation
from nonebot.drivers import Driver, ReverseDriver, combine_driver
from nonebot.drivers import Driver, ASGIMixin, combine_driver
try:
__version__ = version("nonebot2")
@ -149,13 +149,13 @@ def get_adapters() -> Dict[str, Adapter]:
def get_app() -> Any:
"""获取全局 {ref}`nonebot.drivers.ReverseDriver` 对应的 Server App 对象。
"""获取全局 {ref}`nonebot.drivers.ASGIMixin` 对应的 Server App 对象。
返回:
Server App 对象
异常:
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ASGIMixin` 类型
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
@ -165,21 +165,19 @@ def get_app() -> Any:
```
"""
driver = get_driver()
assert isinstance(
driver, ReverseDriver
), "app object is only available for reverse driver"
assert isinstance(driver, ASGIMixin), "app object is only available for asgi driver"
return driver.server_app
def get_asgi() -> Any:
"""获取全局 {ref}`nonebot.drivers.ReverseDriver` 对应
"""获取全局 {ref}`nonebot.drivers.ASGIMixin` 对应
[ASGI](https://asgi.readthedocs.io/) 对象
返回:
ASGI 对象
异常:
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ReverseDriver` 类型
AssertionError: 全局 Driver 对象不是 {ref}`nonebot.drivers.ASGIMixin` 类型
ValueError: 全局 {ref}`nonebot.drivers.Driver` 对象尚未初始化
({ref}`nonebot.init <nonebot.init>` 尚未调用)
@ -190,7 +188,7 @@ def get_asgi() -> Any:
"""
driver = get_driver()
assert isinstance(
driver, ReverseDriver
driver, ASGIMixin
), "asgi object is only available for reverse driver"
return driver.asgi

View File

@ -8,30 +8,40 @@ FrontMatter:
"""
from nonebot.internal.driver import URL as URL
from nonebot.internal.driver import Mixin as Mixin
from nonebot.internal.driver import Driver as Driver
from nonebot.internal.driver import Cookies as Cookies
from nonebot.internal.driver import Request as Request
from nonebot.internal.driver import Response as Response
from nonebot.internal.driver import ASGIMixin as ASGIMixin
from nonebot.internal.driver import WebSocket as WebSocket
from nonebot.internal.driver import HTTPVersion as HTTPVersion
from nonebot.internal.driver import ForwardMixin as ForwardMixin
from nonebot.internal.driver import ReverseMixin as ReverseMixin
from nonebot.internal.driver import ForwardDriver as ForwardDriver
from nonebot.internal.driver import ReverseDriver as ReverseDriver
from nonebot.internal.driver import combine_driver as combine_driver
from nonebot.internal.driver import HTTPClientMixin as HTTPClientMixin
from nonebot.internal.driver import HTTPServerSetup as HTTPServerSetup
from nonebot.internal.driver import WebSocketClientMixin as WebSocketClientMixin
from nonebot.internal.driver import WebSocketServerSetup as WebSocketServerSetup
__autodoc__ = {
"URL": True,
"Driver": True,
"Cookies": True,
"Request": True,
"Response": True,
"WebSocket": True,
"HTTPVersion": True,
"Driver": True,
"Mixin": True,
"ForwardMixin": True,
"ForwardDriver": True,
"HTTPClientMixin": True,
"WebSocketClientMixin": True,
"ReverseMixin": True,
"ReverseDriver": True,
"ASGIMixin": True,
"combine_driver": True,
"HTTPServerSetup": True,
"WebSocketServerSetup": True,

View File

@ -16,14 +16,19 @@ FrontMatter:
"""
from typing_extensions import override
from typing import Type, AsyncGenerator
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING, AsyncGenerator
from nonebot.drivers import Request, Response
from nonebot.exception import WebSocketClosed
from nonebot.drivers.none import Driver as NoneDriver
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import HTTPVersion, ForwardMixin, ForwardDriver, combine_driver
from nonebot.drivers import (
HTTPVersion,
HTTPClientMixin,
WebSocketClientMixin,
combine_driver,
)
try:
import aiohttp
@ -34,7 +39,7 @@ except ModuleNotFoundError as e: # pragma: no cover
) from e
class Mixin(ForwardMixin):
class Mixin(HTTPClientMixin, WebSocketClientMixin):
"""AIOHTTP Mixin"""
@property
@ -172,5 +177,11 @@ class WebSocket(BaseWebSocket):
await self.websocket.send_bytes(data)
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
if TYPE_CHECKING:
class Driver(Mixin, NoneDriver):
...
else:
Driver = combine_driver(NoneDriver, Mixin)
"""AIOHTTP Driver"""

View File

@ -25,12 +25,14 @@ from typing import Any, Dict, List, Tuple, Union, Optional
from pydantic import BaseSettings
from nonebot.config import Env
from nonebot.drivers import ASGIMixin
from nonebot.exception import WebSocketClosed
from nonebot.internal.driver import FileTypes
from nonebot.drivers import Driver as BaseDriver
from nonebot.config import Config as NoneBotConfig
from nonebot.drivers import Request as BaseRequest
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
from ._lifespan import LIFESPAN_FUNC, Lifespan
@ -87,7 +89,7 @@ class Config(BaseSettings):
extra = "ignore"
class Driver(ReverseDriver):
class Driver(BaseDriver, ASGIMixin):
"""FastAPI 驱动框架。"""
def __init__(self, env: Env, config: NoneBotConfig):
@ -179,7 +181,7 @@ class Driver(ReverseDriver):
**kwargs,
):
"""使用 `uvicorn` 启动 FastAPI"""
super().run(host, port, app, **kwargs)
super().run(host, port, app=app, **kwargs)
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,

View File

@ -15,18 +15,15 @@ FrontMatter:
description: nonebot.drivers.httpx 模块
"""
from typing import TYPE_CHECKING
from typing_extensions import override
from typing import Type, AsyncGenerator
from contextlib import asynccontextmanager
from nonebot.drivers.none import Driver as NoneDriver
from nonebot.drivers import (
Request,
Response,
WebSocket,
HTTPVersion,
ForwardMixin,
ForwardDriver,
HTTPClientMixin,
combine_driver,
)
@ -39,7 +36,7 @@ except ModuleNotFoundError as e: # pragma: no cover
) from e
class Mixin(ForwardMixin):
class Mixin(HTTPClientMixin):
"""HTTPX Mixin"""
@property
@ -72,12 +69,12 @@ class Mixin(ForwardMixin):
request=setup,
)
@override
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
async with super().websocket(setup) as ws:
yield ws
if TYPE_CHECKING:
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
class Driver(Mixin, NoneDriver):
...
else:
Driver = combine_driver(NoneDriver, Mixin)
"""HTTPX Driver"""

View File

@ -34,12 +34,14 @@ from typing import (
from pydantic import BaseSettings
from nonebot.config import Env
from nonebot.drivers import ASGIMixin
from nonebot.exception import WebSocketClosed
from nonebot.internal.driver import FileTypes
from nonebot.drivers import Driver as BaseDriver
from nonebot.config import Config as NoneBotConfig
from nonebot.drivers import Request as BaseRequest
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import ReverseDriver, HTTPServerSetup, WebSocketServerSetup
from nonebot.drivers import HTTPServerSetup, WebSocketServerSetup
try:
import uvicorn
@ -89,7 +91,7 @@ class Config(BaseSettings):
extra = "ignore"
class Driver(ReverseDriver):
class Driver(BaseDriver, ASGIMixin):
"""Quart 驱动框架"""
def __init__(self, env: Env, config: NoneBotConfig):

View File

@ -19,14 +19,14 @@ import logging
from functools import wraps
from contextlib import asynccontextmanager
from typing_extensions import ParamSpec, override
from typing import Type, Union, TypeVar, Callable, Awaitable, AsyncGenerator
from typing import TYPE_CHECKING, Union, TypeVar, Callable, Awaitable, AsyncGenerator
from nonebot.drivers import Request
from nonebot.log import LoguruHandler
from nonebot.drivers import Request, Response
from nonebot.exception import WebSocketClosed
from nonebot.drivers.none import Driver as NoneDriver
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.drivers import ForwardMixin, ForwardDriver, combine_driver
from nonebot.drivers import WebSocketClientMixin, combine_driver
try:
from websockets.exceptions import ConnectionClosed
@ -58,7 +58,7 @@ def catch_closed(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
return decorator
class Mixin(ForwardMixin):
class Mixin(WebSocketClientMixin):
"""Websockets Mixin"""
@property
@ -66,10 +66,6 @@ class Mixin(ForwardMixin):
def type(self) -> str:
return "websockets"
@override
async def request(self, setup: Request) -> Response:
return await super().request(setup)
@override
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator["WebSocket", None]:
@ -133,5 +129,11 @@ class WebSocket(BaseWebSocket):
await self.websocket.send(data)
Driver: Type[ForwardDriver] = combine_driver(NoneDriver, Mixin) # type: ignore
if TYPE_CHECKING:
class Driver(Mixin, NoneDriver):
...
else:
Driver = combine_driver(NoneDriver, Mixin)
"""Websockets Driver"""

View File

@ -7,10 +7,11 @@ from nonebot.internal.driver import (
Driver,
Request,
Response,
ASGIMixin,
WebSocket,
ForwardDriver,
ReverseDriver,
HTTPClientMixin,
HTTPServerSetup,
WebSocketClientMixin,
WebSocketServerSetup,
)
@ -72,26 +73,26 @@ class Adapter(abc.ABC):
def setup_http_server(self, setup: HTTPServerSetup):
"""设置一个 HTTP 服务器路由配置"""
if not isinstance(self.driver, ReverseDriver):
if not isinstance(self.driver, ASGIMixin):
raise TypeError("Current driver does not support http server")
self.driver.setup_http_server(setup)
def setup_websocket_server(self, setup: WebSocketServerSetup):
"""设置一个 WebSocket 服务器路由配置"""
if not isinstance(self.driver, ReverseDriver):
if not isinstance(self.driver, ASGIMixin):
raise TypeError("Current driver does not support websocket server")
self.driver.setup_websocket_server(setup)
async def request(self, setup: Request) -> Response:
"""进行一个 HTTP 客户端请求"""
if not isinstance(self.driver, ForwardDriver):
if not isinstance(self.driver, HTTPClientMixin):
raise TypeError("Current driver does not support http client")
return await self.driver.request(setup)
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
"""建立一个 WebSocket 客户端连接请求"""
if not isinstance(self.driver, ForwardDriver):
if not isinstance(self.driver, WebSocketClientMixin):
raise TypeError("Current driver does not support websocket client")
async with self.driver.websocket(setup) as ws:
yield ws

View File

@ -1,4 +1,5 @@
from .model import URL as URL
from .driver import Mixin as Mixin
from .model import RawURL as RawURL
from .driver import Driver as Driver
from .model import Cookies as Cookies
@ -8,6 +9,7 @@ from .model import Response as Response
from .model import DataTypes as DataTypes
from .model import FileTypes as FileTypes
from .model import WebSocket as WebSocket
from .driver import ASGIMixin as ASGIMixin
from .model import FilesTypes as FilesTypes
from .model import QueryTypes as QueryTypes
from .model import CookieTypes as CookieTypes
@ -17,9 +19,12 @@ from .model import HeaderTypes as HeaderTypes
from .model import SimpleQuery as SimpleQuery
from .model import ContentTypes as ContentTypes
from .driver import ForwardMixin as ForwardMixin
from .driver import ReverseMixin as ReverseMixin
from .model import QueryVariable as QueryVariable
from .driver import ForwardDriver as ForwardDriver
from .driver import ReverseDriver as ReverseDriver
from .driver import combine_driver as combine_driver
from .model import HTTPServerSetup as HTTPServerSetup
from .driver import HTTPClientMixin as HTTPClientMixin
from .model import WebSocketServerSetup as WebSocketServerSetup
from .driver import WebSocketClientMixin as WebSocketClientMixin

View File

@ -1,7 +1,19 @@
import abc
import asyncio
from typing_extensions import TypeAlias
from contextlib import AsyncExitStack, asynccontextmanager
from typing import TYPE_CHECKING, Any, Set, Dict, Type, Callable, AsyncGenerator
from typing import (
TYPE_CHECKING,
Any,
Set,
Dict,
Type,
Union,
TypeVar,
Callable,
AsyncGenerator,
overload,
)
from nonebot.log import logger
from nonebot.config import Env, Config
@ -21,11 +33,15 @@ if TYPE_CHECKING:
from nonebot.internal.adapter import Bot, Adapter
D = TypeVar("D", bound="Driver")
BOT_HOOK_PARAMS = [DependParam, BotParam, DefaultParam]
class Driver(abc.ABC):
"""Driver 基类。
"""驱动器基类。
驱动器控制框架的启动和停止适配器的注册以及机器人生命周期管理
参数:
env: 包含环境信息的 Env 对象
@ -45,6 +61,7 @@ class Driver(abc.ABC):
self.config: Config = config
"""全局配置对象"""
self._bots: Dict[str, "Bot"] = {}
self._bot_tasks: Set[asyncio.Task] = set()
def __repr__(self) -> str:
return (
@ -94,6 +111,8 @@ class Driver(abc.ABC):
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
)
self.on_shutdown(self._cleanup)
@abc.abstractmethod
def on_startup(self, func: Callable) -> Callable:
"""注册一个在驱动器启动时执行的函数"""
@ -156,7 +175,9 @@ class Driver(abc.ABC):
"</bg #f8bbd0></r>"
)
asyncio.create_task(_run_hook(bot))
task = asyncio.create_task(_run_hook(bot))
task.add_done_callback(self._bot_tasks.discard)
self._bot_tasks.add(task)
def _bot_disconnect(self, bot: "Bot") -> None:
"""在连接断开后,调用该函数来注销 bot 对象"""
@ -183,23 +204,49 @@ class Driver(abc.ABC):
"</bg #f8bbd0></r>"
)
asyncio.create_task(_run_hook(bot))
task = asyncio.create_task(_run_hook(bot))
task.add_done_callback(self._bot_tasks.discard)
self._bot_tasks.add(task)
async def _cleanup(self) -> None:
"""清理驱动器资源"""
if self._bot_tasks:
logger.opt(colors=True).debug(
"<y>Waiting for running bot connection hooks...</y>"
)
await asyncio.gather(*self._bot_tasks, return_exceptions=True)
class ForwardMixin(abc.ABC):
"""客户端混入基类。"""
class Mixin(abc.ABC):
"""可与其他驱动器共用的混入基类。"""
@property
@abc.abstractmethod
def type(self) -> str:
"""客户端驱动类型名称"""
"""混入驱动类型名称"""
raise NotImplementedError
class ForwardMixin(Mixin):
"""客户端混入基类。"""
class ReverseMixin(Mixin):
"""服务端混入基类。"""
class HTTPClientMixin(ForwardMixin):
"""HTTP 客户端混入基类。"""
@abc.abstractmethod
async def request(self, setup: Request) -> Response:
"""发送一个 HTTP 请求"""
raise NotImplementedError
class WebSocketClientMixin(ForwardMixin):
"""WebSocket 客户端混入基类。"""
@abc.abstractmethod
@asynccontextmanager
async def websocket(self, setup: Request) -> AsyncGenerator[WebSocket, None]:
@ -208,12 +255,11 @@ class ForwardMixin(abc.ABC):
yield # used for static type checking's generator detection
class ForwardDriver(Driver, ForwardMixin):
"""客户端基类。将客户端框架封装,以满足适配器使用。"""
class ASGIMixin(ReverseMixin):
"""ASGI 服务端基类。
class ReverseDriver(Driver):
"""服务端基类。将后端框架封装,以满足适配器使用。"""
将后端框架封装以满足适配器使用
"""
@property
@abc.abstractmethod
@ -238,18 +284,49 @@ class ReverseDriver(Driver):
raise NotImplementedError
def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Driver]:
ForwardDriver: TypeAlias = ForwardMixin
"""支持客户端请求的驱动器。
**Deprecated**请使用 {ref}`nonebot.drivers.ForwardMixin` 或其子类代替
"""
ReverseDriver: TypeAlias = ReverseMixin
"""支持服务端请求的驱动器。
**Deprecated**请使用 {ref}`nonebot.drivers.ReverseMixin` 或其子类代替
"""
if TYPE_CHECKING:
class CombinedDriver(Driver, Mixin):
...
@overload
def combine_driver(driver: Type[D]) -> Type[D]:
...
@overload
def combine_driver(driver: Type[D], *mixins: Type[Mixin]) -> Type["CombinedDriver"]:
...
def combine_driver(
driver: Type[D], *mixins: Type[Mixin]
) -> Union[Type[D], Type["CombinedDriver"]]:
"""将一个驱动器和多个混入类合并。"""
# check first
assert issubclass(driver, Driver), "`driver` must be subclass of Driver"
assert all(
issubclass(m, ForwardMixin) for m in mixins
), "`mixins` must be subclass of ForwardMixin"
issubclass(m, Mixin) for m in mixins
), "`mixins` must be subclass of Mixin"
if not mixins:
return driver
def type_(self: ForwardDriver) -> str:
def type_(self: "CombinedDriver") -> str:
return (
driver.type.__get__(self)
+ "+"
@ -257,5 +334,5 @@ def combine_driver(driver: Type[Driver], *mixins: Type[ForwardMixin]) -> Type[Dr
)
return type(
"CombinedDriver", (*mixins, driver, ForwardDriver), {"type": property(type_)}
"CombinedDriver", (*mixins, driver), {"type": property(type_)}
) # type: ignore

302
poetry.lock generated
View File

@ -16,13 +16,13 @@ pycares = ">=4.0.0"
[[package]]
name = "aiofiles"
version = "23.1.0"
version = "23.2.1"
description = "File support for asyncio."
optional = true
python-versions = ">=3.7,<4.0"
python-versions = ">=3.7"
files = [
{file = "aiofiles-23.1.0-py3-none-any.whl", hash = "sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2"},
{file = "aiofiles-23.1.0.tar.gz", hash = "sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635"},
{file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"},
{file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"},
]
[[package]]
@ -204,13 +204,13 @@ requests = ">=2.21,<3.0"
[[package]]
name = "async-timeout"
version = "4.0.2"
version = "4.0.3"
description = "Timeout context manager for asyncio programs"
optional = true
python-versions = ">=3.6"
python-versions = ">=3.7"
files = [
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
]
[[package]]
@ -506,13 +506,13 @@ pycparser = "*"
[[package]]
name = "cfgv"
version = "3.3.1"
version = "3.4.0"
description = "Validate configuration and produce human readable error messages."
optional = false
python-versions = ">=3.6.1"
python-versions = ">=3.8"
files = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
]
[[package]]
@ -601,13 +601,13 @@ files = [
[[package]]
name = "click"
version = "8.1.6"
version = "8.1.7"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"},
{file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"},
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
@ -626,71 +626,63 @@ files = [
[[package]]
name = "coverage"
version = "7.2.7"
version = "7.3.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"},
{file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"},
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"},
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"},
{file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"},
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"},
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"},
{file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"},
{file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"},
{file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"},
{file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"},
{file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"},
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"},
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"},
{file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"},
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"},
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"},
{file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"},
{file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"},
{file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"},
{file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"},
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"},
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"},
{file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"},
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"},
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"},
{file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"},
{file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"},
{file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"},
{file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"},
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"},
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"},
{file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"},
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"},
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"},
{file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"},
{file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"},
{file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"},
{file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"},
{file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"},
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"},
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"},
{file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"},
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"},
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"},
{file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"},
{file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"},
{file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"},
{file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"},
{file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"},
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"},
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"},
{file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"},
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"},
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"},
{file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"},
{file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"},
{file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"},
{file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"},
{file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"},
{file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"},
{file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"},
{file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"},
{file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"},
{file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"},
{file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"},
{file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"},
{file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"},
{file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"},
{file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"},
{file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"},
{file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"},
{file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"},
{file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"},
{file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"},
{file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"},
{file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"},
{file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"},
{file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"},
{file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"},
{file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"},
{file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"},
{file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"},
{file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"},
{file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"},
{file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"},
{file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"},
{file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"},
{file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"},
{file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"},
{file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"},
{file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"},
{file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"},
{file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"},
{file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"},
{file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"},
{file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"},
{file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"},
{file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"},
{file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"},
{file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"},
{file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"},
{file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"},
{file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"},
{file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"},
{file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"},
{file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"},
{file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"},
{file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"},
{file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"},
{file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"},
{file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"},
]
[package.dependencies]
@ -728,13 +720,13 @@ files = [
[[package]]
name = "exceptiongroup"
version = "1.1.2"
version = "1.1.3"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"},
{file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"},
{file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"},
{file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"},
]
[package.extras]
@ -756,17 +748,17 @@ testing = ["hatch", "pre-commit", "pytest", "tox"]
[[package]]
name = "fastapi"
version = "0.100.0"
version = "0.101.1"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = true
python-versions = ">=3.7"
files = [
{file = "fastapi-0.100.0-py3-none-any.whl", hash = "sha256:271662daf986da8fa98dc2b7c7f61c4abdfdccfb4786d79ed8b2878f172c6d5f"},
{file = "fastapi-0.100.0.tar.gz", hash = "sha256:acb5f941ea8215663283c10018323ba7ea737c571b67fc7e88e9469c7eb1d12e"},
{file = "fastapi-0.101.1-py3-none-any.whl", hash = "sha256:aef5f8676eb1b8389952e1fe734abe20f04b71f6936afcc53b320ba79b686a4b"},
{file = "fastapi-0.101.1.tar.gz", hash = "sha256:7b32000d14ca9992f7461117b81e4ef9ff0c07936af641b4fe40e67d5f9d63cb"},
]
[package.dependencies]
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<3.0.0"
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
starlette = ">=0.27.0,<0.28.0"
typing-extensions = ">=4.5.0"
@ -1024,13 +1016,13 @@ files = [
[[package]]
name = "identify"
version = "2.5.26"
version = "2.5.27"
description = "File identification library for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "identify-2.5.26-py2.py3-none-any.whl", hash = "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54"},
{file = "identify-2.5.26.tar.gz", hash = "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f"},
{file = "identify-2.5.27-py2.py3-none-any.whl", hash = "sha256:fdb527b2dfe24602809b2201e033c2a113d7bdf716db3ca8e3243f735dcecaba"},
{file = "identify-2.5.27.tar.gz", hash = "sha256:287b75b04a0e22d727bc9a41f0d4f3c1bcada97490fa6eabb5b28f0e9097e733"},
]
[package.extras]
@ -1324,13 +1316,13 @@ setuptools = "*"
[[package]]
name = "nonebug"
version = "0.3.4"
version = "0.3.5"
description = "nonebot2 test framework"
optional = false
python-versions = ">=3.8,<4.0"
files = [
{file = "nonebug-0.3.4-py3-none-any.whl", hash = "sha256:d6ebbde934d463141497e3162e26371b7e266d39f8cac0aa1bccc0e4542dd48b"},
{file = "nonebug-0.3.4.tar.gz", hash = "sha256:11d106dff3fe0d5fa029b9745f701770bcc484be048e72722bb17bb00f84753d"},
{file = "nonebug-0.3.5-py3-none-any.whl", hash = "sha256:588831b08b3ea42d058874214bedae646e2ab8c1ec4ae1540ff789873107a8fa"},
{file = "nonebug-0.3.5.tar.gz", hash = "sha256:4d4bf9448cd1cbfaaabaab73dbe4ac8757e86dd92a41ef79cdece8dd61e724e2"},
]
[package.dependencies]
@ -1381,29 +1373,29 @@ files = [
[[package]]
name = "pathspec"
version = "0.11.1"
version = "0.11.2"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.7"
files = [
{file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
{file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
{file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
]
[[package]]
name = "platformdirs"
version = "3.9.1"
version = "3.10.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"},
{file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"},
{file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"},
{file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"},
]
[package.extras]
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]]
name = "pluggy"
@ -1543,47 +1535,47 @@ files = [
[[package]]
name = "pydantic"
version = "1.10.11"
version = "1.10.12"
description = "Data validation and settings management using python type hints"
optional = false
python-versions = ">=3.7"
files = [
{file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"},
{file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"},
{file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"},
{file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"},
{file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"},
{file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"},
{file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"},
{file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"},
{file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"},
{file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"},
{file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"},
{file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"},
{file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"},
{file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"},
{file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"},
{file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"},
{file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"},
{file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"},
{file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"},
{file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"},
{file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"},
{file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"},
{file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"},
{file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"},
{file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"},
{file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"},
{file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"},
{file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"},
{file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"},
{file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"},
{file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"},
{file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"},
{file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"},
{file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"},
{file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"},
{file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"},
{file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"},
{file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"},
{file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"},
{file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"},
{file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"},
{file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"},
{file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"},
{file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"},
{file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"},
{file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"},
{file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"},
{file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"},
{file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"},
{file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"},
{file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"},
{file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"},
{file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"},
{file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"},
{file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"},
{file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"},
{file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"},
{file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"},
{file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"},
{file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"},
{file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"},
{file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"},
{file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"},
{file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"},
{file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"},
{file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"},
{file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"},
{file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"},
{file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"},
{file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"},
{file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"},
{file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"},
]
[package.dependencies]
@ -1821,18 +1813,18 @@ files = [
[[package]]
name = "setuptools"
version = "68.0.0"
version = "68.1.2"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"},
{file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"},
{file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"},
{file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
@ -1905,13 +1897,13 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "uvicorn"
version = "0.23.1"
version = "0.23.2"
description = "The lightning-fast ASGI server."
optional = true
python-versions = ">=3.8"
files = [
{file = "uvicorn-0.23.1-py3-none-any.whl", hash = "sha256:1d55d46b83ee4ce82b4e82f621f2050adb3eb7b5481c13f9af1744951cae2f1f"},
{file = "uvicorn-0.23.1.tar.gz", hash = "sha256:da9b0c8443b2d7ee9db00a345f1eee6db7317432c9d4400f5049cc8d358383be"},
{file = "uvicorn-0.23.2-py3-none-any.whl", hash = "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53"},
{file = "uvicorn-0.23.2.tar.gz", hash = "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a"},
]
[package.dependencies]
@ -1975,23 +1967,23 @@ test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "my
[[package]]
name = "virtualenv"
version = "20.24.1"
version = "20.24.3"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
{file = "virtualenv-20.24.1-py3-none-any.whl", hash = "sha256:01aacf8decd346cf9a865ae85c0cdc7f64c8caa07ff0d8b1dfc1733d10677442"},
{file = "virtualenv-20.24.1.tar.gz", hash = "sha256:2ef6a237c31629da6442b0bcaa3999748108c7166318d1f55cc9f8d7294e97bd"},
{file = "virtualenv-20.24.3-py3-none-any.whl", hash = "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02"},
{file = "virtualenv-20.24.3.tar.gz", hash = "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc"},
]
[package.dependencies]
distlib = ">=0.3.6,<1"
filelock = ">=3.12,<4"
platformdirs = ">=3.5.1,<4"
distlib = ">=0.3.7,<1"
filelock = ">=3.12.2,<4"
platformdirs = ">=3.9.1,<4"
[package.extras]
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezer (>=0.4.6)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.8)", "time-machine (>=2.9)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
[[package]]
name = "watchfiles"
@ -2119,13 +2111,13 @@ files = [
[[package]]
name = "werkzeug"
version = "2.3.6"
version = "2.3.7"
description = "The comprehensive WSGI web application library."
optional = false
python-versions = ">=3.8"
files = [
{file = "Werkzeug-2.3.6-py3-none-any.whl", hash = "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890"},
{file = "Werkzeug-2.3.6.tar.gz", hash = "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"},
{file = "werkzeug-2.3.7-py3-none-any.whl", hash = "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528"},
{file = "werkzeug-2.3.7.tar.gz", hash = "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8"},
]
[package.dependencies]
@ -2275,4 +2267,4 @@ websockets = ["websockets"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "e125baa8903fb84e5955cf4e3d6f666496fa1d2c2b77b36309f81e4a6c02e242"
content-hash = "0fe5200eab7eb8fe06e86f9aa727297ba96646093dfe4940f8c0d93949956582"

View File

@ -14,11 +14,9 @@ classifiers = [
"Framework :: Robot Framework",
"Framework :: Robot Framework :: Library",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3"
]
packages = [
{ include = "nonebot" },
"Programming Language :: Python :: 3",
]
packages = [{ include = "nonebot" }]
include = ["nonebot/py.typed"]
[tool.poetry.urls]
@ -31,7 +29,7 @@ python = "^3.8"
yarl = "^1.7.2"
pygtrie = "^2.4.1"
loguru = ">=0.6.0,<1.0.0"
typing-extensions = ">=4.0.0,<5.0.0"
typing-extensions = ">=4.4.0,<5.0.0"
tomli = { version = "^2.0.1", python = "<3.11" }
pydantic = { version = "^1.10.0", extras = ["dotenv"] }
@ -40,7 +38,9 @@ Quart = { version = ">=0.18.0,<1.0.0", optional = true }
fastapi = { version = ">=0.93.0,<1.0.0", optional = true }
aiohttp = { version = "^3.7.4", extras = ["speedups"], optional = true }
httpx = { version = ">=0.20.0,<1.0.0", extras = ["http2"], optional = true }
uvicorn = { version = ">=0.20.0,<1.0.0", extras = ["standard"], optional = true }
uvicorn = { version = ">=0.20.0,<1.0.0", extras = [
"standard",
], optional = true }
[tool.poetry.group.dev.dependencies]
isort = "^5.10.1"
@ -71,10 +71,7 @@ all = ["fastapi", "quart", "aiohttp", "httpx", "websockets", "uvicorn"]
[tool.pytest.ini_options]
asyncio_mode = "strict"
addopts = "--cov=nonebot --cov-append --cov-report=term-missing"
filterwarnings = [
"error",
"ignore::DeprecationWarning",
]
filterwarnings = ["error", "ignore::DeprecationWarning"]
[tool.black]
line-length = 88
@ -107,7 +104,9 @@ mark-parentheses = false
pythonVersion = "3.8"
pythonPlatform = "All"
executionEnvironments = [
{ root = "./tests", extraPaths = ["./"] },
{ root = "./tests", extraPaths = [
"./",
] },
{ root = "./" },
]

View File

@ -8,8 +8,10 @@ from nonebug import NONEBOT_INIT_KWARGS
from werkzeug.serving import BaseWSGIServer, make_server
import nonebot
from nonebot.drivers import URL
from nonebot.config import Env
from fake_server import request_handler
from nonebot.drivers import URL, Driver
from nonebot import _resolve_combine_expr
os.environ["CONFIG_FROM_ENV"] = '{"test": "test"}'
os.environ["CONFIG_OVERRIDE"] = "new"
@ -22,6 +24,17 @@ def pytest_configure(config: pytest.Config) -> None:
config.stash[NONEBOT_INIT_KWARGS] = {"config_from_init": "init"}
@pytest.fixture(name="driver")
def load_driver(request: pytest.FixtureRequest) -> Driver:
driver_name = getattr(request, "param", None)
global_driver = nonebot.get_driver()
if driver_name is None:
return global_driver
DriverClass = _resolve_combine_expr(driver_name)
return DriverClass(Env(environment=global_driver.env), global_driver.config)
@pytest.fixture(scope="session", autouse=True)
def load_plugin(nonebug_init: None) -> Set["Plugin"]:
# preload global plugins

View File

@ -0,0 +1,211 @@
from typing import Optional
from contextlib import asynccontextmanager
import pytest
from nonebug import App
from utils import FakeAdapter
from nonebot.adapters import Bot
from nonebot.drivers import (
URL,
Driver,
Request,
Response,
WebSocket,
HTTPServerSetup,
WebSocketServerSetup,
)
@pytest.mark.asyncio
async def test_adapter_connect(app: App, driver: Driver):
last_connect_bot: Optional[Bot] = None
last_disconnect_bot: Optional[Bot] = None
def _fake_bot_connect(bot: Bot):
nonlocal last_connect_bot
last_connect_bot = bot
def _fake_bot_disconnect(bot: Bot):
nonlocal last_disconnect_bot
last_disconnect_bot = bot
with pytest.MonkeyPatch.context() as m:
m.setattr(driver, "_bot_connect", _fake_bot_connect)
m.setattr(driver, "_bot_disconnect", _fake_bot_disconnect)
adapter = FakeAdapter(driver)
async with app.test_api() as ctx:
bot = ctx.create_bot(adapter=adapter)
assert last_connect_bot is bot
assert adapter.bots[bot.self_id] is bot
assert last_disconnect_bot is bot
assert bot.self_id not in adapter.bots
@pytest.mark.asyncio
@pytest.mark.parametrize(
"driver",
[
pytest.param("nonebot.drivers.fastapi:Driver", id="fastapi"),
pytest.param("nonebot.drivers.quart:Driver", id="quart"),
pytest.param(
"nonebot.drivers.httpx:Driver",
id="httpx",
marks=pytest.mark.xfail(
reason="not a server", raises=TypeError, strict=True
),
),
pytest.param(
"nonebot.drivers.websockets:Driver",
id="websockets",
marks=pytest.mark.xfail(
reason="not a server", raises=TypeError, strict=True
),
),
pytest.param(
"nonebot.drivers.aiohttp:Driver",
id="aiohttp",
marks=pytest.mark.xfail(
reason="not a server", raises=TypeError, strict=True
),
),
],
indirect=True,
)
async def test_adapter_server(driver: Driver):
last_http_setup: Optional[HTTPServerSetup] = None
last_ws_setup: Optional[WebSocketServerSetup] = None
def _fake_setup_http_server(setup: HTTPServerSetup):
nonlocal last_http_setup
last_http_setup = setup
def _fake_setup_websocket_server(setup: WebSocketServerSetup):
nonlocal last_ws_setup
last_ws_setup = setup
with pytest.MonkeyPatch.context() as m:
m.setattr(driver, "setup_http_server", _fake_setup_http_server, raising=False)
m.setattr(
driver,
"setup_websocket_server",
_fake_setup_websocket_server,
raising=False,
)
async def handle_http(request: Request):
return Response(200, content="test")
async def handle_ws(ws: WebSocket):
...
adapter = FakeAdapter(driver)
setup = HTTPServerSetup(URL("/test"), "GET", "test", handle_http)
adapter.setup_http_server(setup)
assert last_http_setup is setup
setup = WebSocketServerSetup(URL("/test"), "test", handle_ws)
adapter.setup_websocket_server(setup)
assert last_ws_setup is setup
@pytest.mark.asyncio
@pytest.mark.parametrize(
"driver",
[
pytest.param(
"nonebot.drivers.fastapi:Driver",
id="fastapi",
marks=pytest.mark.xfail(
reason="not a http client", raises=TypeError, strict=True
),
),
pytest.param(
"nonebot.drivers.quart:Driver",
id="quart",
marks=pytest.mark.xfail(
reason="not a http client", raises=TypeError, strict=True
),
),
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
pytest.param(
"nonebot.drivers.websockets:Driver",
id="websockets",
marks=pytest.mark.xfail(
reason="not a http client", raises=TypeError, strict=True
),
),
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
],
indirect=True,
)
async def test_adapter_http_client(driver: Driver):
last_request: Optional[Request] = None
async def _fake_request(request: Request):
nonlocal last_request
last_request = request
with pytest.MonkeyPatch.context() as m:
m.setattr(driver, "request", _fake_request, raising=False)
adapter = FakeAdapter(driver)
request = Request("GET", URL("/test"))
await adapter.request(request)
assert last_request is request
@pytest.mark.asyncio
@pytest.mark.parametrize(
"driver",
[
pytest.param(
"nonebot.drivers.fastapi:Driver",
id="fastapi",
marks=pytest.mark.xfail(
reason="not a websocket client", raises=TypeError, strict=True
),
),
pytest.param(
"nonebot.drivers.quart:Driver",
id="quart",
marks=pytest.mark.xfail(
reason="not a websocket client", raises=TypeError, strict=True
),
),
pytest.param(
"nonebot.drivers.httpx:Driver",
id="httpx",
marks=pytest.mark.xfail(
reason="not a websocket client", raises=TypeError, strict=True
),
),
pytest.param("nonebot.drivers.websockets:Driver", id="websockets"),
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
],
indirect=True,
)
async def test_adapter_websocket_client(driver: Driver):
_fake_ws = object()
_last_request: Optional[Request] = None
@asynccontextmanager
async def _fake_websocket(setup: Request):
nonlocal _last_request
_last_request = setup
yield _fake_ws
with pytest.MonkeyPatch.context() as m:
m.setattr(driver, "websocket", _fake_websocket, raising=False)
adapter = FakeAdapter(driver)
request = Request("GET", URL("/test"))
async with adapter.websocket(request) as ws:
assert _last_request is request
assert ws is _fake_ws

View File

@ -1,15 +1,12 @@
import json
import asyncio
from typing import Any, Set, Optional, cast
from typing import Any, Set, Optional
import pytest
from nonebug import App
import nonebot
from nonebot.config import Env
from nonebot.adapters import Bot
from nonebot.params import Depends
from nonebot import _resolve_combine_expr
from nonebot.dependencies import Dependent
from nonebot.exception import WebSocketClosed
from nonebot.drivers._lifespan import Lifespan
@ -18,25 +15,15 @@ from nonebot.drivers import (
Driver,
Request,
Response,
ASGIMixin,
WebSocket,
ForwardDriver,
ReverseDriver,
HTTPClientMixin,
HTTPServerSetup,
WebSocketClientMixin,
WebSocketServerSetup,
)
@pytest.fixture(name="driver")
def load_driver(request: pytest.FixtureRequest) -> Driver:
driver_name = getattr(request, "param", None)
global_driver = nonebot.get_driver()
if driver_name is None:
return global_driver
DriverClass = _resolve_combine_expr(driver_name)
return DriverClass(Env(environment=global_driver.env), global_driver.config)
@pytest.mark.asyncio
async def test_lifespan():
lifespan = Lifespan()
@ -80,7 +67,7 @@ async def test_lifespan():
indirect=True,
)
async def test_http_server(app: App, driver: Driver):
driver = cast(ReverseDriver, driver)
assert isinstance(driver, ASGIMixin)
async def _handle_http(request: Request) -> Response:
assert request.content in (b"test", "test")
@ -108,7 +95,7 @@ async def test_http_server(app: App, driver: Driver):
indirect=True,
)
async def test_websocket_server(app: App, driver: Driver):
driver = cast(ReverseDriver, driver)
assert isinstance(driver, ASGIMixin)
async def _handle_ws(ws: WebSocket) -> None:
await ws.accept()
@ -164,7 +151,7 @@ async def test_websocket_server(app: App, driver: Driver):
indirect=True,
)
async def test_cross_context(app: App, driver: Driver):
driver = cast(ReverseDriver, driver)
assert isinstance(driver, ASGIMixin)
ws: Optional[WebSocket] = None
ws_ready = asyncio.Event()
@ -221,7 +208,7 @@ async def test_cross_context(app: App, driver: Driver):
indirect=True,
)
async def test_http_client(driver: Driver, server_url: URL):
driver = cast(ForwardDriver, driver)
assert isinstance(driver, HTTPClientMixin)
# simple post with query, headers, cookies and content
request = Request(
@ -303,6 +290,19 @@ async def test_http_client(driver: Driver, server_url: URL):
await asyncio.sleep(1)
@pytest.mark.asyncio
@pytest.mark.parametrize(
"driver",
[
pytest.param("nonebot.drivers.websockets:Driver", id="websockets"),
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
],
indirect=True,
)
async def test_websocket_client(driver: Driver):
assert isinstance(driver, WebSocketClientMixin)
@pytest.mark.asyncio
@pytest.mark.parametrize(
("driver", "driver_type"),

View File

@ -2,7 +2,7 @@ import pytest
from nonebug import App
import nonebot
from nonebot.drivers import Driver, ReverseDriver
from nonebot.drivers import Driver, ASGIMixin, ReverseDriver
from nonebot import (
get_app,
get_bot,
@ -47,6 +47,7 @@ async def test_get_driver(app: App, monkeypatch: pytest.MonkeyPatch):
async def test_get_asgi(app: App, monkeypatch: pytest.MonkeyPatch):
driver = get_driver()
assert isinstance(driver, ReverseDriver)
assert isinstance(driver, ASGIMixin)
assert get_asgi() == driver.asgi
@ -54,6 +55,7 @@ async def test_get_asgi(app: App, monkeypatch: pytest.MonkeyPatch):
async def test_get_app(app: App, monkeypatch: pytest.MonkeyPatch):
driver = get_driver()
assert isinstance(driver, ReverseDriver)
assert isinstance(driver, ASGIMixin)
assert get_app() == driver.server_app

View File

@ -1,8 +1,9 @@
from typing_extensions import override
from typing import Type, Union, Mapping, Iterable, Optional
from pydantic import Extra, create_model
from nonebot.adapters import Event, Message, MessageSegment
from nonebot.adapters import Bot, Event, Adapter, Message, MessageSegment
def escape_text(s: str, *, escape_comma: bool = True) -> str:
@ -12,11 +13,24 @@ def escape_text(s: str, *, escape_comma: bool = True) -> str:
return s
class FakeAdapter(Adapter):
@classmethod
@override
def get_name(cls) -> str:
return "fake"
@override
async def _call_api(self, bot: Bot, api: str, **data):
raise NotImplementedError
class FakeMessageSegment(MessageSegment["FakeMessage"]):
@classmethod
@override
def get_message_class(cls):
return FakeMessage
@override
def __str__(self) -> str:
return self.data["text"] if self.type == "text" else f"[fake:{self.type}]"
@ -32,16 +46,19 @@ class FakeMessageSegment(MessageSegment["FakeMessage"]):
def nested(content: "FakeMessage"):
return FakeMessageSegment("node", {"content": content})
@override
def is_text(self) -> bool:
return self.type == "text"
class FakeMessage(Message[FakeMessageSegment]):
@classmethod
@override
def get_segment_class(cls):
return FakeMessageSegment
@staticmethod
@override
def _construct(msg: Union[str, Iterable[Mapping]]):
if isinstance(msg, str):
yield FakeMessageSegment.text(msg)
@ -50,6 +67,7 @@ class FakeMessage(Message[FakeMessageSegment]):
yield FakeMessageSegment(**seg)
return
@override
def __add__(
self, other: Union[str, FakeMessageSegment, Iterable[FakeMessageSegment]]
):
@ -71,30 +89,37 @@ def make_fake_event(
Base = _base or Event
class FakeEvent(Base, extra=Extra.forbid):
@override
def get_type(self) -> str:
return _type
@override
def get_event_name(self) -> str:
return _name
@override
def get_event_description(self) -> str:
return _description
@override
def get_user_id(self) -> str:
if _user_id is not None:
return _user_id
raise NotImplementedError
@override
def get_session_id(self) -> str:
if _session_id is not None:
return _session_id
raise NotImplementedError
@override
def get_message(self) -> "Message":
if _message is not None:
return _message
raise NotImplementedError
@override
def is_tome(self) -> bool:
return _to_me

View File

@ -22,21 +22,22 @@ options:
## 驱动器类型
驱动器的类型有两种:
驱动器类型大体上可以分为两种:
- `ForwardDriver`:即客户端型驱动器,多用于使用 HTTP 轮询,连接 WebSocket 服务器等情形。
- `ReverseDriver`:即服务端型驱动器,多用于使用 WebHook接收 WebSocket 客户端连接等情形。
- `Forward`:即客户端型驱动器,多用于使用 HTTP 轮询,连接 WebSocket 服务器等情形。
- `Reverse`:即服务端型驱动器,多用于使用 WebHook接收 WebSocket 客户端连接等情形。
客户端型驱动器具有以下两种功能
客户端型驱动器可以分为以下两种
1. 异步发送 HTTP 请求,自定义 `HTTP Method`、`URL`、`Header`、`Body`、`Cookie`、`Proxy`、`Timeout` 等。
2. 异步建立 WebSocket 连接上下文,自定义 `WebSocket URL`、`Header`、`Cookie`、`Proxy`、`Timeout` 等。
服务端型驱动器通常为 ASGI 应用框架,具有以下功能
服务端型驱动器目前有
1. 协议适配器自定义 HTTP 上报地址以及对上报数据处理的回调函数。
2. 协议适配器自定义 WebSocket 连接请求地址以及对 WebSocket 请求处理的回调函数。
3. 用户可以向 ASGI 应用添加任何服务端相关功能,如:[添加自定义路由](./routing.md)。
1. ASGI 应用框架,具有以下功能:
- 协议适配器自定义 HTTP 上报地址以及对上报数据处理的回调函数。
- 协议适配器自定义 WebSocket 连接请求地址以及对 WebSocket 请求处理的回调函数。
- 用户可以向 ASGI 应用添加任何服务端相关功能,如:[添加自定义路由](./routing.md)。
## 配置驱动器
@ -79,7 +80,7 @@ DRIVER=~none
### FastAPI默认
**类型:**服务端驱动器
**类型:**ASGI 服务端驱动器
> FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints.
@ -185,7 +186,7 @@ nonebot.run(app="bot:app")
### Quart
**类型:**`ReverseDriver`
**类型:**ASGI 服务端驱动器
> Quart is an asyncio reimplementation of the popular Flask microframework API.
@ -249,7 +250,7 @@ nonebot.run(app="bot:app")
### HTTPX
**类型:**`ForwardDriver`
**类型:**HTTP 客户端驱动器
:::warning 注意
本驱动器仅支持 HTTP 请求,不支持 WebSocket 连接请求。
@ -263,7 +264,7 @@ DRIVER=~httpx
### websockets
**类型:**`ForwardDriver`
**类型:**WebSocket 客户端驱动器
:::warning 注意
本驱动器仅支持 WebSocket 连接请求,不支持 HTTP 请求。
@ -277,7 +278,7 @@ DRIVER=~websockets
### AIOHTTP
**类型:**`ForwardDriver`
**类型:**HTTP/WebSocket 客户端驱动器
> [AIOHTTP](https://docs.aiohttp.org/): Asynchronous HTTP Client/Server for asyncio and Python.

View File

@ -12,7 +12,7 @@ options:
在[驱动器](./driver.md)一节中,我们了解了驱动器的两种类型。既然驱动器可以作为服务端运行,那么我们就可以向驱动器添加路由规则,从而实现自定义的 API 接口等功能。在添加路由规则时,我们需要注意驱动器的类型,详情可以参考[选择驱动器](./driver.md#配置驱动器)。
NoneBot 中,我们可以通过两种途径向驱动器添加路由规则:
NoneBot 中,我们可以通过两种途径向 ASGI 驱动器添加路由规则:
1. 通过 NoneBot 的兼容层建立路由规则。
2. 直接向 ASGI 应用添加路由规则。
@ -23,9 +23,9 @@ NoneBot 中,我们可以通过两种途径向驱动器添加路由规则:
```python {3}
from nonebot import get_driver
from nonebot.drivers import ReverseDriver
from nonebot.drivers import ASGIMixin
can_use = isinstance(get_driver(), ReverseDriver)
can_use = isinstance(get_driver(), ASGIMixin)
```
## 通过兼容层添加路由
@ -45,12 +45,12 @@ NoneBot 兼容层定义了两个数据类 `HTTPServerSetup` 和 `WebSocketServer
```python
from nonebot import get_driver
from nonebot.drivers import URL, Request, Response, HTTPServerSetup
from nonebot.drivers import URL, Request, Response, ASGIMixin, HTTPServerSetup
async def hello(request: Request) -> Response:
return Response(200, content="Hello, world!")
if isinstance((driver := get_driver()), ReverseDriver):
if isinstance((driver := get_driver()), ASGIMixin):
driver.setup_http_server(
HTTPServerSetup(
path=URL("/hello"),
@ -75,7 +75,7 @@ if isinstance((driver := get_driver()), ReverseDriver):
```python
from nonebot import get_driver
from nonebot.drivers import URL, WebSocket, WebSocketServerSetup
from nonebot.drivers import URL, ASGIMixin, WebSocket, WebSocketServerSetup
async def ws_handler(ws: WebSocket):
await ws.accept()
@ -91,7 +91,7 @@ async def ws_handler(ws: WebSocket):
await websocket.close()
# do some cleanup
if isinstance((driver := get_driver()), ReverseDriver):
if isinstance((driver := get_driver()), ASGIMixin):
driver.setup_websocket_server(
WebSocketServerSetup(
path=URL("/ws"),

View File

@ -125,8 +125,8 @@ NoneBot 提供了多种 [Driver](../advanced/driver) 来帮助适配器进行网
import asyncio
from typing_extensions import override
from nonebot.drivers import Request, ForwardDriver
from nonebot.exception import WebSocketClosed
from nonebot.drivers import Request, WebSocketClientMixin
class Adapter(BaseAdapter):
@override
@ -137,11 +137,11 @@ class Adapter(BaseAdapter):
self.setup()
def setup(self) -> None:
if not isinstance(self.driver, ForwardDriver):
if not isinstance(self.driver, WebSocketClientMixin):
# 判断用户配置的Driver类型是否符合适配器要求不符合时应抛出异常
raise RuntimeError(
f"Current driver {self.config.driver} doesn't support forward connections!"
f"{self.get_name()} Adapter need a ForwardDriver to work."
f"Current driver {self.config.driver} doesn't support websocket client connections!"
f"{self.get_name()} Adapter need a WebSocket Client Driver to work."
)
# 在 NoneBot 启动和关闭时进行相关操作
self.driver.on_startup(self.startup)
@ -202,8 +202,8 @@ class Adapter(BaseAdapter):
```python {30,38} title=adapter.py
from nonebot.drivers import (
Request,
ASGIMixin,
WebSocket,
ReverseDriver,
HTTPServerSetup,
WebSocketServerSetup
)
@ -216,10 +216,10 @@ class Adapter(BaseAdapter):
self.setup()
def setup(self) -> None:
if not isinstance(self.driver, ReverseDriver):
if not isinstance(self.driver, ASGIMixin):
raise RuntimeError(
f"Current driver {self.config.driver} doesn't support forward connections!"
f"{self.get_name()} Adapter need a ReverseDriver to work."
f"Current driver {self.config.driver} doesn't support asgi server!"
f"{self.get_name()} Adapter need a asgi server driver to work."
)
# 建立服务端路由
# HTTP Webhook 路由