Feature: 添加 httpx2 驱动器支持 (#4194)

This commit is contained in:
StarHeart
2026-09-13 23:43:34 +08:00
committed by GitHub
parent 206d31a61d
commit 92c840ef11
8 changed files with 389 additions and 16 deletions
+1
View File
@@ -151,6 +151,7 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
| [Quart](https://quart.palletsprojects.com/en/latest/)(异步 Flask | 服务端 |
| [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
| [httpx](https://www.python-httpx.org/) | 客户端 |
| [httpx2](https://github.com/pydantic/httpx2) | 客户端 |
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
更多:[概览](https://nonebot.dev/docs/)
+10
View File
@@ -39,6 +39,16 @@
"tags": [],
"is_official": true
},
{
"module_name": "~httpx2",
"project_link": "nonebot2[httpx2]",
"name": "HTTPX2",
"desc": "HTTPX2 驱动器",
"author_id": 42488585,
"homepage": "/docs/advanced/driver",
"tags": [],
"is_official": true
},
{
"module_name": "~websockets",
"project_link": "nonebot2[websockets]",
+262
View File
@@ -0,0 +1,262 @@
"""[HTTPX2](https://github.com/pydantic/httpx2) 驱动适配
```bash
nb driver install httpx2
# 或者
pip install nonebot2[httpx2]
```
:::tip[提示]
本驱动仅支持客户端 HTTP 连接
:::
FrontMatter:
mdx:
format: md
sidebar_position: 7
description: nonebot.drivers.httpx2 模块
"""
from collections.abc import AsyncGenerator
from typing import TYPE_CHECKING
from typing_extensions import override
from multidict import CIMultiDict
from nonebot.drivers import (
URL,
HTTPClientMixin,
HTTPClientSession,
HTTPVersion,
Request,
Response,
combine_driver,
)
from nonebot.drivers.none import Driver as NoneDriver
from nonebot.internal.driver import (
DEFAULT_TIMEOUT,
Cookies,
CookieTypes,
HeaderTypes,
QueryTypes,
Timeout,
TimeoutTypes,
)
from nonebot.utils import UNSET, UnsetType, exclude_unset
try:
import httpx2
except ModuleNotFoundError as e: # pragma: no cover
raise ImportError(
"Please install httpx2 first to use this driver. "
"Install with pip: `pip install nonebot2[httpx2]`"
) from e
class Session(HTTPClientSession):
@override
def __init__(
self,
params: QueryTypes = None,
headers: HeaderTypes = None,
cookies: CookieTypes = None,
version: str | HTTPVersion = HTTPVersion.H11,
timeout: TimeoutTypes | UnsetType = UNSET,
proxy: str | None = None,
):
self._client: httpx2.AsyncClient | None = None
self._params = (
tuple(URL.build(query=params).query.items()) if params is not None else None
)
self._headers = (
tuple(CIMultiDict(headers).items()) if headers is not None else None
)
self._cookies = Cookies(cookies)
self._version = HTTPVersion(version)
_timeout = None
if isinstance(timeout, Timeout):
avg_timeout = timeout.total and timeout.total / 4
timeout_kwargs: dict[str, float | None] = exclude_unset(
{
"timeout": avg_timeout,
"connect": timeout.connect,
"read": timeout.read,
}
)
if timeout_kwargs:
_timeout = httpx2.Timeout(**timeout_kwargs)
elif timeout is not UNSET:
_timeout = httpx2.Timeout(timeout)
if _timeout is None:
avg_timeout = DEFAULT_TIMEOUT.total and DEFAULT_TIMEOUT.total / 4
_timeout = httpx2.Timeout(
**exclude_unset(
{
"timeout": avg_timeout,
"connect": DEFAULT_TIMEOUT.connect,
"read": DEFAULT_TIMEOUT.read,
}
)
)
self._timeout = _timeout
self._proxy = proxy
@property
def client(self) -> httpx2.AsyncClient:
if self._client is None:
raise RuntimeError("Session is not initialized")
return self._client
def _get_timeout(self, timeout: TimeoutTypes | UnsetType) -> httpx2.Timeout:
_timeout = None
if isinstance(timeout, Timeout):
avg_timeout = timeout.total and timeout.total / 4
timeout_kwargs: dict[str, float | None] = exclude_unset(
{
"timeout": avg_timeout,
"connect": timeout.connect,
"read": timeout.read,
}
)
if timeout_kwargs:
_timeout = httpx2.Timeout(**timeout_kwargs)
elif timeout is not UNSET:
_timeout = httpx2.Timeout(timeout)
if _timeout is None:
return self._timeout
return _timeout
@override
async def request(self, setup: Request) -> Response:
response = await self.client.request(
setup.method,
str(setup.url),
content=setup.content,
data=setup.data,
files=setup.files,
json=setup.json,
# ensure the params priority
params=setup.url.raw_query_string,
headers=tuple(setup.headers.items()),
cookies=setup.cookies.jar,
timeout=self._get_timeout(setup.timeout),
)
return Response(
response.status_code,
headers=response.headers.multi_items(),
content=response.content,
request=setup,
)
@override
async def stream_request(
self,
setup: Request,
*,
chunk_size: int = 1024,
) -> AsyncGenerator[Response, None]:
async with self.client.stream(
setup.method,
str(setup.url),
content=setup.content,
data=setup.data,
files=setup.files,
json=setup.json,
# ensure the params priority
params=setup.url.raw_query_string,
headers=tuple(setup.headers.items()),
cookies=setup.cookies.jar,
timeout=self._get_timeout(setup.timeout),
) as response:
response_headers = response.headers.multi_items()
async for chunk in response.aiter_bytes(chunk_size=chunk_size):
yield Response(
response.status_code,
headers=response_headers,
content=chunk,
request=setup,
)
@override
async def setup(self) -> None:
if self._client is not None:
raise RuntimeError("Session has already been initialized")
self._client = httpx2.AsyncClient(
params=self._params,
headers=self._headers,
cookies=self._cookies.jar,
http2=self._version == HTTPVersion.H2,
proxy=self._proxy,
follow_redirects=True,
)
await self._client.__aenter__()
@override
async def close(self) -> None:
try:
if self._client is not None:
await self._client.aclose()
finally:
self._client = None
class Mixin(HTTPClientMixin):
"""HTTPX2 Mixin"""
@property
@override
def type(self) -> str:
return "httpx2"
@override
async def request(self, setup: Request) -> Response:
async with self.get_session(
version=setup.version, proxy=setup.proxy
) as session:
return await session.request(setup)
@override
async def stream_request(
self,
setup: Request,
*,
chunk_size: int = 1024,
) -> AsyncGenerator[Response, None]:
async with self.get_session(
version=setup.version, proxy=setup.proxy
) as session:
async for response in session.stream_request(setup, chunk_size=chunk_size):
yield response
@override
def get_session(
self,
params: QueryTypes = None,
headers: HeaderTypes = None,
cookies: CookieTypes = None,
version: str | HTTPVersion = HTTPVersion.H11,
timeout: TimeoutTypes = None,
proxy: str | None = None,
) -> Session:
return Session(
params=params,
headers=headers,
cookies=cookies,
version=version,
timeout=timeout,
proxy=proxy,
)
if TYPE_CHECKING:
class Driver(Mixin, NoneDriver): ...
else:
Driver = combine_driver(NoneDriver, Mixin)
"""HTTPX2 Driver"""
+2
View File
@@ -29,6 +29,7 @@ dependencies = [
[project.optional-dependencies]
websockets = ["websockets >=15.0"]
httpx = ["httpx[http2] >=0.26.0, <1.0.0"]
httpx2 = ["httpx2[http2] >=2.0.0, <3.0.0"]
aiohttp = ["aiohttp[speedups] >=3.11.0, <4.0.0"]
quart = ["Quart >=0.18.0, <1.0.0", "uvicorn[standard] >=0.20.0, <1.0.0"]
fastapi = ["fastapi >=0.93.0, <1.0.0", "uvicorn[standard] >=0.20.0, <1.0.0"]
@@ -36,6 +37,7 @@ all = [
"websockets >=15.0",
"fastapi >=0.93.0, <1.0.0",
"httpx[http2] >=0.26.0, <1.0.0",
"httpx2[http2] >=2.0.0, <3.0.0",
"aiohttp[speedups] >=3.11.0, <4.0.0",
"uvicorn[standard] >=0.20.0, <1.0.0",
]
+15
View File
@@ -56,6 +56,13 @@ async def test_adapter_connect(app: App, driver: Driver):
reason="not a server", raises=TypeError, strict=True
),
),
pytest.param(
"nonebot.drivers.httpx2:Driver",
id="httpx2",
marks=pytest.mark.xfail(
reason="not a server", raises=TypeError, strict=True
),
),
pytest.param(
"nonebot.drivers.websockets:Driver",
id="websockets",
@@ -129,6 +136,7 @@ def test_adapter_server(driver: Driver):
),
),
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
pytest.param("nonebot.drivers.httpx2:Driver", id="httpx2"),
pytest.param(
"nonebot.drivers.websockets:Driver",
id="websockets",
@@ -182,6 +190,13 @@ async def test_adapter_http_client(driver: Driver):
reason="not a websocket client", raises=TypeError, strict=True
),
),
pytest.param(
"nonebot.drivers.httpx2:Driver",
id="httpx2",
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"),
],
+3
View File
@@ -225,6 +225,7 @@ async def test_cross_context(app: App, driver: Driver):
"driver",
[
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
pytest.param("nonebot.drivers.httpx2:Driver", id="httpx2"),
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
],
indirect=True,
@@ -392,6 +393,7 @@ async def test_http_client(driver: Driver, server_url: URL):
"driver",
[
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
pytest.param("nonebot.drivers.httpx2:Driver", id="httpx2"),
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
],
indirect=True,
@@ -733,6 +735,7 @@ def test_timeout_unset_vs_none():
"driver",
[
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
pytest.param("nonebot.drivers.httpx2:Driver", id="httpx2"),
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
],
indirect=True,
Generated
+82 -16
View File
@@ -4,7 +4,8 @@ requires-python = ">=3.10, <4.0"
resolution-markers = [
"python_full_version >= '3.14'",
"python_full_version == '3.13.*'",
"python_full_version < '3.13'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"(python_full_version < '3.13' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten')",
]
conflicts = [[
{ package = "nonebot2", group = "pydantic-v1" },
@@ -845,7 +846,8 @@ source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14'",
"python_full_version == '3.13.*'",
"python_full_version < '3.13'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"(python_full_version < '3.13' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten')",
]
dependencies = [
{ name = "annotated-doc", marker = "extra == 'group-8-nonebot2-pydantic-v1'" },
@@ -865,7 +867,8 @@ source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14'",
"python_full_version == '3.13.*'",
"python_full_version < '3.13'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"(python_full_version < '3.13' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten')",
]
dependencies = [
{ name = "annotated-doc", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
@@ -1070,6 +1073,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpcore2"
version = "2.12.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "h11", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten' or (extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2')" },
{ name = "truststore", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten' or (extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/74/d370e55600d9bcfa0d9794b0166126d49291a3d2b20c268fc98c453a4948/httpcore2-2.12.0-py3-none-any.whl", hash = "sha256:7e04258ce01013d7d615e5b910a3b27fac937d7a95038227e79652b4ba3b4ceb", size = 83074, upload-time = "2026-08-18T13:22:05.854Z" },
]
[[package]]
name = "httptools"
version = "0.8.0"
@@ -1140,6 +1156,37 @@ http2 = [
{ name = "h2" },
]
[[package]]
name = "httpx2"
version = "2.12.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio", marker = "sys_platform != 'emscripten' or (extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2')" },
{ name = "httpcore2", marker = "sys_platform != 'emscripten' or (extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2')" },
{ name = "httpx2-jsfetch", marker = "(python_full_version >= '3.12' and sys_platform == 'emscripten') or (python_full_version < '3.12' and extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2') or (sys_platform != 'emscripten' and extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2')" },
{ name = "idna" },
{ name = "truststore", marker = "sys_platform != 'emscripten' or (extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2')" },
{ name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7f/f8/579a8b51e42e38ee32647df9f08aa25643ae788e275cc625b199829c4671/httpx2-2.12.0.tar.gz", hash = "sha256:7631fe9887a8a2275f4a2540e053aa670fcc50742864a9ae7c66e609fdcf12cf", size = 100040, upload-time = "2026-08-18T13:22:09.086Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/95/411ba65569158e862368917aaf56597f3e5fa3b91b0502919638465a08f3/httpx2-2.12.0-py3-none-any.whl", hash = "sha256:cc8b6eecb8661c146b8f89a60e97456ee086e91a784ed31ac450c3a9e613dd36", size = 95427, upload-time = "2026-08-18T13:22:06.834Z" },
]
[package.optional-dependencies]
http2 = [
{ name = "h2" },
]
[[package]]
name = "httpx2-jsfetch"
version = "1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" },
]
[[package]]
name = "hypercorn"
version = "0.18.0"
@@ -1500,6 +1547,7 @@ all = [
{ name = "fastapi", version = "0.125.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-8-nonebot2-pydantic-v1'" },
{ name = "fastapi", version = "0.139.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
{ name = "httpx", extra = ["http2"] },
{ name = "httpx2", extra = ["http2"] },
{ name = "uvicorn", extra = ["standard"] },
{ name = "websockets" },
]
@@ -1511,6 +1559,9 @@ fastapi = [
httpx = [
{ name = "httpx", extra = ["http2"] },
]
httpx2 = [
{ name = "httpx2", extra = ["http2"] },
]
quart = [
{ name = "quart" },
{ name = "uvicorn", extra = ["standard"] },
@@ -1562,6 +1613,8 @@ requires-dist = [
{ name = "fastapi", marker = "extra == 'fastapi'", specifier = ">=0.93.0,<1.0.0" },
{ name = "httpx", extras = ["http2"], marker = "extra == 'all'", specifier = ">=0.26.0,<1.0.0" },
{ name = "httpx", extras = ["http2"], marker = "extra == 'httpx'", specifier = ">=0.26.0,<1.0.0" },
{ name = "httpx2", extras = ["http2"], marker = "extra == 'all'", specifier = ">=2.0.0,<3.0.0" },
{ name = "httpx2", extras = ["http2"], marker = "extra == 'httpx2'", specifier = ">=2.0.0,<3.0.0" },
{ name = "loguru", specifier = ">=0.6.0,<1.0.0" },
{ name = "pydantic", specifier = ">=1.10.0,!=2.5.0,!=2.5.1,!=2.10.0,!=2.10.1,<3.0.0" },
{ name = "pygtrie", specifier = ">=2.4.1,<3.0.0" },
@@ -1576,7 +1629,7 @@ requires-dist = [
{ name = "websockets", marker = "extra == 'websockets'", specifier = ">=15.0" },
{ name = "yarl", specifier = ">=1.7.2,<2.0.0" },
]
provides-extras = ["websockets", "httpx", "aiohttp", "quart", "fastapi", "all"]
provides-extras = ["websockets", "httpx", "httpx2", "aiohttp", "quart", "fastapi", "all"]
[package.metadata.requires-dev]
dev = [
@@ -1947,7 +2000,8 @@ source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14'",
"python_full_version == '3.13.*'",
"python_full_version < '3.13'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"(python_full_version < '3.13' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten')",
]
dependencies = [
{ name = "typing-extensions", marker = "extra == 'group-8-nonebot2-pydantic-v1'" },
@@ -1989,13 +2043,14 @@ source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14'",
"python_full_version == '3.13.*'",
"python_full_version < '3.13'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"(python_full_version < '3.13' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten')",
]
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
{ name = "annotated-types", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
{ name = "pydantic-core", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
{ name = "typing-extensions", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
{ name = "typing-inspection", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
wheels = [
@@ -2007,7 +2062,7 @@ name = "pydantic-core"
version = "2.46.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
{ name = "typing-extensions", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
wheels = [
@@ -2352,7 +2407,8 @@ source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14'",
"python_full_version == '3.13.*'",
"python_full_version < '3.13'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"(python_full_version < '3.13' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten')",
]
dependencies = [
{ name = "anyio", marker = "extra == 'group-8-nonebot2-pydantic-v1'" },
@@ -2370,7 +2426,8 @@ source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.14'",
"python_full_version == '3.13.*'",
"python_full_version < '3.13'",
"python_full_version == '3.12.*' and sys_platform == 'emscripten'",
"(python_full_version < '3.13' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten')",
]
dependencies = [
{ name = "anyio", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
@@ -2386,8 +2443,8 @@ name = "taskgroup"
version = "0.2.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "exceptiongroup", marker = "python_full_version < '3.13' or (extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2')" },
{ name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2')" },
{ name = "exceptiongroup", marker = "(python_full_version < '3.13' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten') or (python_full_version >= '3.12' and extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2') or (sys_platform != 'emscripten' and extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2')" },
{ name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten') or (python_full_version >= '3.12' and extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2') or (sys_platform != 'emscripten' and extra == 'group-8-nonebot2-pydantic-v1' and extra == 'group-8-nonebot2-pydantic-v2')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/8d/e218e0160cc1b692e6e0e5ba34e8865dbb171efeb5fc9a704544b3020605/taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d", size = 11504, upload-time = "2025-01-03T09:24:13.761Z" }
wheels = [
@@ -2466,6 +2523,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1c/93/dab25dc87ac48da0fe0f6419e07d0bfd98799bed4e05e7b9e0f85a1a4b4b/trio-0.33.0-py3-none-any.whl", hash = "sha256:3bd5d87f781d9b0192d592aef28691f8951d6c2e41b7e1da4c25cde6c180ae9b", size = 510294, upload-time = "2026-02-14T18:40:53.313Z" },
]
[[package]]
name = "truststore"
version = "0.10.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
]
[[package]]
name = "typing-extensions"
version = "4.16.0"
@@ -2480,7 +2546,7 @@ name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
{ name = "typing-extensions", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
+14
View File
@@ -262,6 +262,20 @@ nonebot.run(app="bot:app")
DRIVER=~httpx
```
### HTTPX2
**类型:**HTTP 客户端驱动器
:::warning[注意]
本驱动器仅支持 HTTP 请求,不支持 WebSocket 连接请求。
:::
> [HTTPX2](https://github.com/pydantic/httpx2) is a fully featured HTTP client library for Python. It includes an integrated command line client, has support for both HTTP/1.1 and HTTP/2, and provides both sync and async APIs.
```env
DRIVER=~httpx2
```
### websockets
**类型:**WebSocket 客户端驱动器