mirror of
https://github.com/nonebot/nonebot2.git
synced 2026-09-16 22:33:49 +00:00
✨ Feature: 添加 httpx2 驱动器支持 (#4194)
This commit is contained in:
@@ -151,6 +151,7 @@ NoneBot2 是一个现代、跨平台、可扩展的 Python 聊天机器人框架
|
|||||||
| [Quart](https://quart.palletsprojects.com/en/latest/)(异步 Flask) | 服务端 |
|
| [Quart](https://quart.palletsprojects.com/en/latest/)(异步 Flask) | 服务端 |
|
||||||
| [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
|
| [aiohttp](https://docs.aiohttp.org/en/stable/) | 客户端 |
|
||||||
| [httpx](https://www.python-httpx.org/) | 客户端 |
|
| [httpx](https://www.python-httpx.org/) | 客户端 |
|
||||||
|
| [httpx2](https://github.com/pydantic/httpx2) | 客户端 |
|
||||||
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
|
| [websockets](https://websockets.readthedocs.io/en/stable/) | 客户端 |
|
||||||
|
|
||||||
更多:[概览](https://nonebot.dev/docs/)
|
更多:[概览](https://nonebot.dev/docs/)
|
||||||
|
|||||||
@@ -39,6 +39,16 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"is_official": true
|
"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",
|
"module_name": "~websockets",
|
||||||
"project_link": "nonebot2[websockets]",
|
"project_link": "nonebot2[websockets]",
|
||||||
|
|||||||
@@ -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"""
|
||||||
@@ -29,6 +29,7 @@ dependencies = [
|
|||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
websockets = ["websockets >=15.0"]
|
websockets = ["websockets >=15.0"]
|
||||||
httpx = ["httpx[http2] >=0.26.0, <1.0.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"]
|
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"]
|
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"]
|
fastapi = ["fastapi >=0.93.0, <1.0.0", "uvicorn[standard] >=0.20.0, <1.0.0"]
|
||||||
@@ -36,6 +37,7 @@ all = [
|
|||||||
"websockets >=15.0",
|
"websockets >=15.0",
|
||||||
"fastapi >=0.93.0, <1.0.0",
|
"fastapi >=0.93.0, <1.0.0",
|
||||||
"httpx[http2] >=0.26.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",
|
"aiohttp[speedups] >=3.11.0, <4.0.0",
|
||||||
"uvicorn[standard] >=0.20.0, <1.0.0",
|
"uvicorn[standard] >=0.20.0, <1.0.0",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ async def test_adapter_connect(app: App, driver: Driver):
|
|||||||
reason="not a server", raises=TypeError, strict=True
|
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(
|
pytest.param(
|
||||||
"nonebot.drivers.websockets:Driver",
|
"nonebot.drivers.websockets:Driver",
|
||||||
id="websockets",
|
id="websockets",
|
||||||
@@ -129,6 +136,7 @@ def test_adapter_server(driver: Driver):
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
|
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
|
||||||
|
pytest.param("nonebot.drivers.httpx2:Driver", id="httpx2"),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"nonebot.drivers.websockets:Driver",
|
"nonebot.drivers.websockets:Driver",
|
||||||
id="websockets",
|
id="websockets",
|
||||||
@@ -182,6 +190,13 @@ async def test_adapter_http_client(driver: Driver):
|
|||||||
reason="not a websocket client", raises=TypeError, strict=True
|
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.websockets:Driver", id="websockets"),
|
||||||
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ async def test_cross_context(app: App, driver: Driver):
|
|||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
|
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
|
||||||
|
pytest.param("nonebot.drivers.httpx2:Driver", id="httpx2"),
|
||||||
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
||||||
],
|
],
|
||||||
indirect=True,
|
indirect=True,
|
||||||
@@ -392,6 +393,7 @@ async def test_http_client(driver: Driver, server_url: URL):
|
|||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
|
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
|
||||||
|
pytest.param("nonebot.drivers.httpx2:Driver", id="httpx2"),
|
||||||
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
||||||
],
|
],
|
||||||
indirect=True,
|
indirect=True,
|
||||||
@@ -733,6 +735,7 @@ def test_timeout_unset_vs_none():
|
|||||||
"driver",
|
"driver",
|
||||||
[
|
[
|
||||||
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
|
pytest.param("nonebot.drivers.httpx:Driver", id="httpx"),
|
||||||
|
pytest.param("nonebot.drivers.httpx2:Driver", id="httpx2"),
|
||||||
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
pytest.param("nonebot.drivers.aiohttp:Driver", id="aiohttp"),
|
||||||
],
|
],
|
||||||
indirect=True,
|
indirect=True,
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ requires-python = ">=3.10, <4.0"
|
|||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.14'",
|
"python_full_version >= '3.14'",
|
||||||
"python_full_version == '3.13.*'",
|
"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 = [[
|
conflicts = [[
|
||||||
{ package = "nonebot2", group = "pydantic-v1" },
|
{ package = "nonebot2", group = "pydantic-v1" },
|
||||||
@@ -845,7 +846,8 @@ source = { registry = "https://pypi.org/simple" }
|
|||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.14'",
|
"python_full_version >= '3.14'",
|
||||||
"python_full_version == '3.13.*'",
|
"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 = [
|
dependencies = [
|
||||||
{ name = "annotated-doc", marker = "extra == 'group-8-nonebot2-pydantic-v1'" },
|
{ name = "annotated-doc", marker = "extra == 'group-8-nonebot2-pydantic-v1'" },
|
||||||
@@ -865,7 +867,8 @@ source = { registry = "https://pypi.org/simple" }
|
|||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.14'",
|
"python_full_version >= '3.14'",
|
||||||
"python_full_version == '3.13.*'",
|
"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 = [
|
dependencies = [
|
||||||
{ name = "annotated-doc", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
|
{ 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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "httptools"
|
name = "httptools"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -1140,6 +1156,37 @@ http2 = [
|
|||||||
{ name = "h2" },
|
{ 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]]
|
[[package]]
|
||||||
name = "hypercorn"
|
name = "hypercorn"
|
||||||
version = "0.18.0"
|
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.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 = "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 = "httpx", extra = ["http2"] },
|
||||||
|
{ name = "httpx2", extra = ["http2"] },
|
||||||
{ name = "uvicorn", extra = ["standard"] },
|
{ name = "uvicorn", extra = ["standard"] },
|
||||||
{ name = "websockets" },
|
{ name = "websockets" },
|
||||||
]
|
]
|
||||||
@@ -1511,6 +1559,9 @@ fastapi = [
|
|||||||
httpx = [
|
httpx = [
|
||||||
{ name = "httpx", extra = ["http2"] },
|
{ name = "httpx", extra = ["http2"] },
|
||||||
]
|
]
|
||||||
|
httpx2 = [
|
||||||
|
{ name = "httpx2", extra = ["http2"] },
|
||||||
|
]
|
||||||
quart = [
|
quart = [
|
||||||
{ name = "quart" },
|
{ name = "quart" },
|
||||||
{ name = "uvicorn", extra = ["standard"] },
|
{ name = "uvicorn", extra = ["standard"] },
|
||||||
@@ -1562,6 +1613,8 @@ requires-dist = [
|
|||||||
{ name = "fastapi", marker = "extra == 'fastapi'", specifier = ">=0.93.0,<1.0.0" },
|
{ 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 == 'all'", specifier = ">=0.26.0,<1.0.0" },
|
||||||
{ name = "httpx", extras = ["http2"], marker = "extra == 'httpx'", 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 = "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 = "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" },
|
{ name = "pygtrie", specifier = ">=2.4.1,<3.0.0" },
|
||||||
@@ -1576,7 +1629,7 @@ requires-dist = [
|
|||||||
{ name = "websockets", marker = "extra == 'websockets'", specifier = ">=15.0" },
|
{ name = "websockets", marker = "extra == 'websockets'", specifier = ">=15.0" },
|
||||||
{ name = "yarl", specifier = ">=1.7.2,<2.0.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]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
@@ -1947,7 +2000,8 @@ source = { registry = "https://pypi.org/simple" }
|
|||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.14'",
|
"python_full_version >= '3.14'",
|
||||||
"python_full_version == '3.13.*'",
|
"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 = [
|
dependencies = [
|
||||||
{ name = "typing-extensions", marker = "extra == 'group-8-nonebot2-pydantic-v1'" },
|
{ name = "typing-extensions", marker = "extra == 'group-8-nonebot2-pydantic-v1'" },
|
||||||
@@ -1989,13 +2043,14 @@ source = { registry = "https://pypi.org/simple" }
|
|||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.14'",
|
"python_full_version >= '3.14'",
|
||||||
"python_full_version == '3.13.*'",
|
"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 = [
|
dependencies = [
|
||||||
{ name = "annotated-types" },
|
{ name = "annotated-types", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
|
||||||
{ name = "pydantic-core" },
|
{ name = "pydantic-core", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
|
||||||
{ name = "typing-inspection" },
|
{ 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" }
|
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 = [
|
wheels = [
|
||||||
@@ -2007,7 +2062,7 @@ name = "pydantic-core"
|
|||||||
version = "2.46.4"
|
version = "2.46.4"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
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" }
|
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 = [
|
wheels = [
|
||||||
@@ -2352,7 +2407,8 @@ source = { registry = "https://pypi.org/simple" }
|
|||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.14'",
|
"python_full_version >= '3.14'",
|
||||||
"python_full_version == '3.13.*'",
|
"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 = [
|
dependencies = [
|
||||||
{ name = "anyio", marker = "extra == 'group-8-nonebot2-pydantic-v1'" },
|
{ name = "anyio", marker = "extra == 'group-8-nonebot2-pydantic-v1'" },
|
||||||
@@ -2370,7 +2426,8 @@ source = { registry = "https://pypi.org/simple" }
|
|||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.14'",
|
"python_full_version >= '3.14'",
|
||||||
"python_full_version == '3.13.*'",
|
"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 = [
|
dependencies = [
|
||||||
{ name = "anyio", marker = "extra == 'group-8-nonebot2-pydantic-v2' or extra != 'group-8-nonebot2-pydantic-v1'" },
|
{ 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"
|
version = "0.2.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "exceptiongroup", 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' or (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" }
|
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 = [
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.16.0"
|
version = "4.16.0"
|
||||||
@@ -2480,7 +2546,7 @@ name = "typing-inspection"
|
|||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
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" }
|
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 = [
|
wheels = [
|
||||||
|
|||||||
@@ -262,6 +262,20 @@ nonebot.run(app="bot:app")
|
|||||||
DRIVER=~httpx
|
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
|
### websockets
|
||||||
|
|
||||||
**类型:**WebSocket 客户端驱动器
|
**类型:**WebSocket 客户端驱动器
|
||||||
|
|||||||
Reference in New Issue
Block a user