From 2e635370bb479911801da0591a43e3072192e0b5 Mon Sep 17 00:00:00 2001 From: Ju4tCode <42488585+yanyongyu@users.noreply.github.com> Date: Sat, 26 Aug 2023 11:03:24 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20Feature:=20=E7=BB=86=E5=8C=96=20dr?= =?UTF-8?q?iver=20=E8=81=8C=E8=B4=A3=E7=B1=BB=E5=9E=8B=20(#2296)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nonebot/__init__.py | 16 +- nonebot/drivers/__init__.py | 12 +- nonebot/drivers/aiohttp.py | 21 +- nonebot/drivers/fastapi.py | 8 +- nonebot/drivers/httpx.py | 23 +- nonebot/drivers/quart.py | 6 +- nonebot/drivers/websockets.py | 22 +- nonebot/internal/adapter/adapter.py | 13 +- nonebot/internal/driver/__init__.py | 5 + nonebot/internal/driver/driver.py | 111 ++++++-- poetry.lock | 302 +++++++++++----------- pyproject.toml | 21 +- tests/conftest.py | 15 +- tests/test_adapters/test_adapter.py | 211 +++++++++++++++ tests/test_driver.py | 42 +-- tests/test_init.py | 4 +- tests/utils.py | 27 +- website/docs/advanced/driver.md | 27 +- website/docs/advanced/routing.md | 14 +- website/docs/developer/adapter-writing.md | 16 +- 20 files changed, 632 insertions(+), 284 deletions(-) create mode 100644 tests/test_adapters/test_adapter.py diff --git a/nonebot/__init__.py b/nonebot/__init__.py index 9946e953..c9e4020e 100644 --- a/nonebot/__init__.py +++ b/nonebot/__init__.py @@ -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 ` 尚未调用) @@ -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 ` 尚未调用) @@ -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 diff --git a/nonebot/drivers/__init__.py b/nonebot/drivers/__init__.py index 5c6b3ab1..de0f9897 100644 --- a/nonebot/drivers/__init__.py +++ b/nonebot/drivers/__init__.py @@ -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, diff --git a/nonebot/drivers/aiohttp.py b/nonebot/drivers/aiohttp.py index 5ffc4f90..5408c1ce 100644 --- a/nonebot/drivers/aiohttp.py +++ b/nonebot/drivers/aiohttp.py @@ -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 -"""AIOHTTP Driver""" +if TYPE_CHECKING: + + class Driver(Mixin, NoneDriver): + ... + +else: + Driver = combine_driver(NoneDriver, Mixin) + """AIOHTTP Driver""" diff --git a/nonebot/drivers/fastapi.py b/nonebot/drivers/fastapi.py index a8fb4f69..f1d96f2f 100644 --- a/nonebot/drivers/fastapi.py +++ b/nonebot/drivers/fastapi.py @@ -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, diff --git a/nonebot/drivers/httpx.py b/nonebot/drivers/httpx.py index dd952d46..a6e55146 100644 --- a/nonebot/drivers/httpx.py +++ b/nonebot/drivers/httpx.py @@ -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 -"""HTTPX Driver""" + class Driver(Mixin, NoneDriver): + ... + +else: + Driver = combine_driver(NoneDriver, Mixin) + """HTTPX Driver""" diff --git a/nonebot/drivers/quart.py b/nonebot/drivers/quart.py index a30524e9..8c90db71 100644 --- a/nonebot/drivers/quart.py +++ b/nonebot/drivers/quart.py @@ -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): diff --git a/nonebot/drivers/websockets.py b/nonebot/drivers/websockets.py index 1e711e2b..5c4e167e 100644 --- a/nonebot/drivers/websockets.py +++ b/nonebot/drivers/websockets.py @@ -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 -"""Websockets Driver""" +if TYPE_CHECKING: + + class Driver(Mixin, NoneDriver): + ... + +else: + Driver = combine_driver(NoneDriver, Mixin) + """Websockets Driver""" diff --git a/nonebot/internal/adapter/adapter.py b/nonebot/internal/adapter/adapter.py index 4748a0ad..6bcfb1a8 100644 --- a/nonebot/internal/adapter/adapter.py +++ b/nonebot/internal/adapter/adapter.py @@ -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 diff --git a/nonebot/internal/driver/__init__.py b/nonebot/internal/driver/__init__.py index 533ecfa7..aad18257 100644 --- a/nonebot/internal/driver/__init__.py +++ b/nonebot/internal/driver/__init__.py @@ -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 diff --git a/nonebot/internal/driver/driver.py b/nonebot/internal/driver/driver.py index a3a4818b..3d620635 100644 --- a/nonebot/internal/driver/driver.py +++ b/nonebot/internal/driver/driver.py @@ -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"Loaded adapters: {escape_tag(', '.join(self._adapters))}" ) + self.on_shutdown(self._cleanup) + @abc.abstractmethod def on_startup(self, func: Callable) -> Callable: """注册一个在驱动器启动时执行的函数""" @@ -156,7 +175,9 @@ class Driver(abc.ABC): "" ) - 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): "" ) - 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( + "Waiting for running bot connection hooks..." + ) + 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 diff --git a/poetry.lock b/poetry.lock index 94825329..abe12530 100644 --- a/poetry.lock +++ b/poetry.lock @@ -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" diff --git a/pyproject.toml b/pyproject.toml index 3e74ad34..5ebd0249 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 = "./" }, ] diff --git a/tests/conftest.py b/tests/conftest.py index 56e2b5f0..ec58600f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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 diff --git a/tests/test_adapters/test_adapter.py b/tests/test_adapters/test_adapter.py new file mode 100644 index 00000000..41e74973 --- /dev/null +++ b/tests/test_adapters/test_adapter.py @@ -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 diff --git a/tests/test_driver.py b/tests/test_driver.py index 75c9bc2a..70fbea6b 100644 --- a/tests/test_driver.py +++ b/tests/test_driver.py @@ -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"), diff --git a/tests/test_init.py b/tests/test_init.py index 016082fb..9f9e1218 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -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 diff --git a/tests/utils.py b/tests/utils.py index be084ac4..e18bc51d 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -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 diff --git a/website/docs/advanced/driver.md b/website/docs/advanced/driver.md index f921f6b1..d9e3460e 100644 --- a/website/docs/advanced/driver.md +++ b/website/docs/advanced/driver.md @@ -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. diff --git a/website/docs/advanced/routing.md b/website/docs/advanced/routing.md index eb2c8226..d16ad518 100644 --- a/website/docs/advanced/routing.md +++ b/website/docs/advanced/routing.md @@ -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"), diff --git a/website/docs/developer/adapter-writing.md b/website/docs/developer/adapter-writing.md index 904bf4b8..a9fdcfbc 100644 --- a/website/docs/developer/adapter-writing.md +++ b/website/docs/developer/adapter-writing.md @@ -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 路由