diff --git a/examples/sandbox_05_fastapi.py b/examples/sandbox_05_fastapi.py index 79e56c3b..e010d4c7 100644 --- a/examples/sandbox_05_fastapi.py +++ b/examples/sandbox_05_fastapi.py @@ -3,7 +3,7 @@ import os import webbrowser -import httpx +import httpx2 as httpx from dotenv import load_dotenv from vercel.sandbox import AsyncSandbox as Sandbox diff --git a/pyproject.toml b/pyproject.toml index 3ee53102..450a3a74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ dev = [ "pytest-asyncio<2", "pytest-xdist<4", "hypothesis>=6.0.0,<7", - "respx>=0.21.0,<1", + "respx==0.23.1", "uvloop<1", "mypy>=1.20.2,<2", "build<2", @@ -38,7 +38,7 @@ markers = [ [tool.mypy] ignore_missing_imports = true explicit_package_bases = true -mypy_path = ["src", "src/vercel-oidc", "src/vercel-headers"] +mypy_path = ["src", "src/vercel-oidc", "src/vercel-headers", "tests/stubs"] [tool.ruff] line-length = 100 diff --git a/src/vercel-oidc/pyproject.toml b/src/vercel-oidc/pyproject.toml index 674e2c30..af375525 100644 --- a/src/vercel-oidc/pyproject.toml +++ b/src/vercel-oidc/pyproject.toml @@ -11,7 +11,7 @@ requires-python = ">=3.10" license = "MIT" license-files = ["LICENSE", "LICENSE.*"] dependencies = [ - "httpx>=0.27.0,<1", + "httpx2[http2]>=2", "vercel-headers>=0.6.0", ] diff --git a/src/vercel-oidc/vercel/oidc/token.py b/src/vercel-oidc/vercel/oidc/token.py index 2eb69c39..856268a0 100644 --- a/src/vercel-oidc/vercel/oidc/token.py +++ b/src/vercel-oidc/vercel/oidc/token.py @@ -3,7 +3,7 @@ import os from collections.abc import Mapping -import httpx +import httpx2 as httpx from vercel.headers import get_headers diff --git a/src/vercel/_internal/blob/core.py b/src/vercel/_internal/blob/core.py index 9d7d8ae3..1727e286 100644 --- a/src/vercel/_internal/blob/core.py +++ b/src/vercel/_internal/blob/core.py @@ -8,7 +8,7 @@ from typing import Any, Literal, cast from urllib.parse import parse_qs, urlencode, urlparse, urlunparse -import httpx +import httpx2 as httpx from vercel._internal.blob import ( PutHeaders, diff --git a/src/vercel/_internal/http/clients.py b/src/vercel/_internal/http/clients.py index 0fc1e92c..689aad92 100644 --- a/src/vercel/_internal/http/clients.py +++ b/src/vercel/_internal/http/clients.py @@ -6,7 +6,7 @@ from collections.abc import Sequence from typing import Any -import httpx +import httpx2 as httpx from vercel._internal.http.config import DEFAULT_TIMEOUT from vercel._internal.http.request_client import ( diff --git a/src/vercel/_internal/http/request_client.py b/src/vercel/_internal/http/request_client.py index 82102c9c..85196755 100644 --- a/src/vercel/_internal/http/request_client.py +++ b/src/vercel/_internal/http/request_client.py @@ -9,7 +9,7 @@ from dataclasses import dataclass from typing import Any, cast -import httpx +import httpx2 as httpx from vercel._internal.http.transport import BaseTransport, RequestBody diff --git a/src/vercel/_internal/http/transport.py b/src/vercel/_internal/http/transport.py index d394b74a..2d209626 100644 --- a/src/vercel/_internal/http/transport.py +++ b/src/vercel/_internal/http/transport.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from typing import Any -import httpx +import httpx2 as httpx def _normalize_path(path: str) -> str: diff --git a/src/vercel/_internal/sandbox/core.py b/src/vercel/_internal/sandbox/core.py index 190e1307..a59865a1 100644 --- a/src/vercel/_internal/sandbox/core.py +++ b/src/vercel/_internal/sandbox/core.py @@ -21,7 +21,7 @@ from importlib.metadata import version as _pkg_version from typing import Any, TypeAlias, cast -import httpx +import httpx2 as httpx from vercel._internal.fs import ( FileHandle, diff --git a/src/vercel/_internal/sandbox/errors.py b/src/vercel/_internal/sandbox/errors.py index 416d4e4b..dc090146 100644 --- a/src/vercel/_internal/sandbox/errors.py +++ b/src/vercel/_internal/sandbox/errors.py @@ -2,7 +2,7 @@ from __future__ import annotations -import httpx +import httpx2 as httpx def _normalize_retry_after(value: str | int | None) -> int | str | None: diff --git a/src/vercel/_internal/sandbox/pty_binary.py b/src/vercel/_internal/sandbox/pty_binary.py index 70242840..0f65fff5 100644 --- a/src/vercel/_internal/sandbox/pty_binary.py +++ b/src/vercel/_internal/sandbox/pty_binary.py @@ -4,7 +4,7 @@ from pathlib import Path -import httpx +import httpx2 as httpx from .constants import PTY_BINARY_DOWNLOAD_TIMEOUT from .time import to_seconds_float diff --git a/src/vercel/_internal/telemetry/client.py b/src/vercel/_internal/telemetry/client.py index 397eb244..1a1a1b25 100644 --- a/src/vercel/_internal/telemetry/client.py +++ b/src/vercel/_internal/telemetry/client.py @@ -6,7 +6,7 @@ import uuid from typing import Any -import httpx +import httpx2 as httpx from vercel._internal.telemetry.credentials import extract_credentials diff --git a/src/vercel/_internal/workflow/worlds/vercel.py b/src/vercel/_internal/workflow/worlds/vercel.py index 4c08959c..a2d45ff2 100644 --- a/src/vercel/_internal/workflow/worlds/vercel.py +++ b/src/vercel/_internal/workflow/worlds/vercel.py @@ -10,7 +10,7 @@ from typing import Any, TypeVar import cbor2 -import httpx +import httpx2 as httpx import pydantic from vercel._internal.polyfills import UTC diff --git a/src/vercel/cache/cache_build.py b/src/vercel/cache/cache_build.py index 6510dd0d..52cd45a1 100644 --- a/src/vercel/cache/cache_build.py +++ b/src/vercel/cache/cache_build.py @@ -3,7 +3,7 @@ import json from collections.abc import Callable, Mapping, Sequence -import httpx +import httpx2 as httpx from vercel._internal.telemetry.tracker import track diff --git a/src/vercel/deployments/deployments.py b/src/vercel/deployments/deployments.py index 3acdd0fd..e90a33bb 100644 --- a/src/vercel/deployments/deployments.py +++ b/src/vercel/deployments/deployments.py @@ -3,7 +3,7 @@ import os from typing import Any -import httpx +import httpx2 as httpx from vercel._internal.telemetry.tracker import track diff --git a/src/vercel/projects/projects.py b/src/vercel/projects/projects.py index ba80f589..214139e6 100644 --- a/src/vercel/projects/projects.py +++ b/src/vercel/projects/projects.py @@ -4,7 +4,7 @@ import urllib.parse from typing import Any -import httpx +import httpx2 as httpx from vercel._internal.telemetry.tracker import track diff --git a/src/vercel/pyproject.toml b/src/vercel/pyproject.toml index e1a351ec..49feb89a 100644 --- a/src/vercel/pyproject.toml +++ b/src/vercel/pyproject.toml @@ -11,7 +11,7 @@ requires-python = ">=3.10" license = "MIT" license-files = ["LICENSE", "LICENSE.*"] dependencies = [ - "httpx>=0.27.0,<1", + "httpx2[http2]>=2", "pydantic>=2.7.0,<3", "anyio>=4.0.0,<5", "typing-extensions>=4.0.0,<5 ; python_version < '3.11'", diff --git a/tests/conftest.py b/tests/conftest.py index cca76bd7..bcf8b65d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,8 +6,76 @@ import uuid from collections.abc import Generator from pathlib import Path +from typing import Any +import httpcore2 +import httpx2 import pytest +import respx +import respx.mocks +import respx.models +import respx.router +from respx.mocks import HTTPCoreMocker + + +class HTTPCore2Mocker(HTTPCoreMocker): + name = "httpcore2" + targets = [ + "httpcore2._sync.connection.HTTPConnection", + "httpcore2._sync.connection_pool.ConnectionPool", + "httpcore2._sync.http_proxy.HTTPProxy", + "httpcore2._async.connection.AsyncHTTPConnection", + "httpcore2._async.connection_pool.AsyncConnectionPool", + "httpcore2._async.http_proxy.AsyncHTTPProxy", + ] + + @classmethod + def to_httpx_request(cls, **kwargs: Any) -> httpx2.Request: + request = kwargs["request"] + method = ( + request.method.decode("ascii") + if isinstance(request.method, bytes) + else request.method + ) + scheme = ( + request.url.scheme.decode("ascii") + if isinstance(request.url.scheme, bytes) + else request.url.scheme + ) + host = ( + request.url.host.decode("ascii") + if isinstance(request.url.host, bytes) + else request.url.host + ) + return httpx2.Request( + method, + httpx2.URL( + scheme=scheme, + host=host, + port=request.url.port, + raw_path=request.url.target, + ), + headers=request.headers, + stream=request.stream, + extensions=request.extensions, + ) + + @classmethod + def from_sync_httpx_response( + cls, httpx_response: httpx2.Response, target: object, **kwargs: Any + ) -> httpcore2.Response: + return httpcore2.Response( + status=httpx_response.status_code, + headers=httpx_response.headers.raw, + content=httpx_response.stream, + extensions=httpx_response.extensions, + ) + + +respx.models.httpx = httpx2 +respx.mocks.httpx = httpx2 +respx.router.httpx = httpx2 +respx.mocks.DEFAULT_MOCKER = HTTPCore2Mocker.name @pytest.fixture diff --git a/tests/integration/test_blob_multipart_auto_upload.py b/tests/integration/test_blob_multipart_auto_upload.py index dc851ef7..3fd2ea57 100644 --- a/tests/integration/test_blob_multipart_auto_upload.py +++ b/tests/integration/test_blob_multipart_auto_upload.py @@ -6,7 +6,7 @@ from collections.abc import Callable from typing import Any -import httpx +import httpx2 as httpx import pytest import respx diff --git a/tests/integration/test_blob_sync_async.py b/tests/integration/test_blob_sync_async.py index e9989042..84ccc20d 100644 --- a/tests/integration/test_blob_sync_async.py +++ b/tests/integration/test_blob_sync_async.py @@ -5,7 +5,7 @@ import io -import httpx +import httpx2 as httpx import pytest import respx diff --git a/tests/integration/test_http_transport_raw_body.py b/tests/integration/test_http_transport_raw_body.py index aef18f10..a957dbbe 100644 --- a/tests/integration/test_http_transport_raw_body.py +++ b/tests/integration/test_http_transport_raw_body.py @@ -2,7 +2,7 @@ import pytest import respx -from httpx import Response +from httpx2 import Response from vercel._internal.http import ( AsyncTransport, diff --git a/tests/integration/test_http_url_construction.py b/tests/integration/test_http_url_construction.py index 1797d246..b1925608 100644 --- a/tests/integration/test_http_url_construction.py +++ b/tests/integration/test_http_url_construction.py @@ -11,7 +11,7 @@ import pytest import respx -from httpx import Response +from httpx2 import Response from vercel._internal.http import ( AsyncTransport, @@ -59,7 +59,9 @@ class TestUrlNormalization: @respx.mock def test_sync_normalization_consistency(self, base_url: str, path: str, expected_url: str): """Test that SyncTransport normalizes URLs consistently.""" - route = respx.get(expected_url).mock(return_value=Response(200, json={"ok": True})) + route = respx.get(expected_url).mock( + return_value=Response(200, json={"ok": True}) + ) client = create_base_client(timeout=30.0, base_url=base_url) transport = SyncTransport(client) @@ -93,7 +95,9 @@ async def test_async_normalization_consistency( self, base_url: str, path: str, expected_url: str ): """Test that AsyncTransport normalizes URLs consistently.""" - route = respx.get(expected_url).mock(return_value=Response(200, json={"ok": True})) + route = respx.get(expected_url).mock( + return_value=Response(200, json={"ok": True}) + ) client = create_base_async_client(timeout=30.0, base_url=base_url) transport = AsyncTransport(client) @@ -136,7 +140,9 @@ class TestEdgeCases: @respx.mock def test_edge_cases(self, base_url: str, path: str, expected_url: str): """Test edge cases for URL construction.""" - route = respx.get(expected_url).mock(return_value=Response(200, json={"ok": True})) + route = respx.get(expected_url).mock( + return_value=Response(200, json={"ok": True}) + ) client = create_base_client(timeout=30.0, base_url=base_url) transport = SyncTransport(client) diff --git a/tests/integration/test_sandbox_streaming_errors.py b/tests/integration/test_sandbox_streaming_errors.py index 1eb881db..5e85bb05 100644 --- a/tests/integration/test_sandbox_streaming_errors.py +++ b/tests/integration/test_sandbox_streaming_errors.py @@ -1,6 +1,6 @@ """Test that non-2xx streaming responses raise sandbox-specific errors.""" -import httpx +import httpx2 as httpx import pytest import respx diff --git a/tests/integration/test_sandbox_sync_async.py b/tests/integration/test_sandbox_sync_async.py index d225000c..7d30d799 100644 --- a/tests/integration/test_sandbox_sync_async.py +++ b/tests/integration/test_sandbox_sync_async.py @@ -11,7 +11,7 @@ from pathlib import Path from unittest.mock import AsyncMock -import httpx +import httpx2 as httpx import pytest import respx diff --git a/tests/stubs/respx/__init__.pyi b/tests/stubs/respx/__init__.pyi new file mode 100644 index 00000000..e5f59c50 --- /dev/null +++ b/tests/stubs/respx/__init__.pyi @@ -0,0 +1,116 @@ +from collections.abc import Awaitable, Callable, Iterator, Sequence +from types import TracebackType +from typing import Any, overload + +import httpx2 + +_RouteResult = httpx2.Request | httpx2.Response | None +_SideEffect = ( + Callable[..., _RouteResult | Awaitable[_RouteResult]] + | Exception + | type[Exception] + | Iterator[httpx2.Response | Exception | type[Exception]] + | Sequence[httpx2.Response | Exception | type[Exception]] +) + + +class Call: + request: httpx2.Request + optional_response: httpx2.Response | None + @property + def response(self) -> httpx2.Response: ... + @property + def has_response(self) -> bool: ... + + +class CallList(list[Call]): + @property + def called(self) -> bool: ... + @property + def call_count(self) -> int: ... + @property + def last(self) -> Call: ... + + +class Route: + calls: CallList + def mock( + self, + return_value: httpx2.Response | None = None, + *, + side_effect: _SideEffect | None = None, + ) -> Route: ... + def respond(self, status_code: int = 200, **kwargs: Any) -> Route: ... + def pass_through(self, value: bool = True) -> Route: ... + @property + def called(self) -> bool: ... + @property + def call_count(self) -> int: ... + + +class Router: + routes: Any + calls: CallList + def request( + self, method: str, url: Any = None, *, name: str | None = None, **lookups: Any + ) -> Route: ... + def get(self, url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... + def post(self, url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... + def put(self, url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... + def patch(self, url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... + def delete(self, url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... + def head(self, url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... + def options(self, url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... + + +class MockRouter(Router): + @overload + def __call__( + self, + func: None = None, + *, + assert_all_called: bool | None = None, + assert_all_mocked: bool | None = None, + base_url: str | None = None, + using: str | None = ..., + ) -> MockRouter: ... + @overload + def __call__( + self, + func: Callable[..., Any], + *, + assert_all_called: bool | None = None, + assert_all_mocked: bool | None = None, + base_url: str | None = None, + using: str | None = ..., + ) -> Callable[..., Any]: ... + def __enter__(self) -> MockRouter: ... + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: ... + async def __aenter__(self) -> MockRouter: ... + async def __aexit__(self, *args: Any) -> None: ... + + +mock: MockRouter +routes: Any +calls: CallList + +def start() -> None: ... +def stop(*, clear: bool = True, reset: bool = True) -> None: ... +def clear() -> None: ... +def reset() -> None: ... +def pop(name: str, default: Any = ...) -> Route | Any: ... +def route(*patterns: Any, name: str | None = None, **lookups: Any) -> Route: ... +def add(route: Route, *, name: str | None = None) -> Route: ... +def request(method: str, url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... +def get(url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... +def post(url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... +def put(url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... +def patch(url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... +def delete(url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... +def head(url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... +def options(url: Any = None, *, name: str | None = None, **lookups: Any) -> Route: ... diff --git a/tests/stubs/respx/py.typed b/tests/stubs/respx/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/test_request_client.py b/tests/unit/test_request_client.py index debee6d6..eda2bb6b 100644 --- a/tests/unit/test_request_client.py +++ b/tests/unit/test_request_client.py @@ -4,7 +4,7 @@ from typing import Any from unittest.mock import patch -import httpx +import httpx2 as httpx import pytest from vercel._internal.http.request_client import ( @@ -401,7 +401,7 @@ def test_factory_method_retries_network_errors(self) -> None: class TestSyncTransportEndToEnd: def test_with_real_sync_transport(self) -> None: """Test that RequestClient works with SyncTransport + iter_coroutine.""" - import httpx + import httpx2 as httpx # Create a real httpx.Client with a mock transport mock_transport = httpx.MockTransport(lambda req: httpx.Response(200, json={"ok": True})) diff --git a/tests/unit/test_sandbox_errors.py b/tests/unit/test_sandbox_errors.py index 8ad0a16b..7dbbafe8 100644 --- a/tests/unit/test_sandbox_errors.py +++ b/tests/unit/test_sandbox_errors.py @@ -4,7 +4,7 @@ from typing import Any -import httpx +import httpx2 as httpx import pytest from vercel._internal.http import BaseTransport, RequestClient diff --git a/uv.lock b/uv.lock index f40268d7..ae5332bf 100644 --- a/uv.lock +++ b/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-06-24T20:48:23.897147Z" +exclude-newer = "2026-06-24T23:16:16.629239Z" exclude-newer-span = "P2D" [manifest] @@ -27,7 +27,7 @@ dev = [ { name = "pytest", specifier = ">=7.0.0,<10" }, { name = "pytest-asyncio", specifier = "<2" }, { name = "pytest-xdist", specifier = "<4" }, - { name = "respx", specifier = ">=0.21.0,<1" }, + { name = "respx", specifier = "==0.23.1" }, { name = "ruff", specifier = ">=0.5.0,<1" }, { name = "twine", specifier = "<7" }, { name = "uvloop", specifier = "<1" }, @@ -393,6 +393,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "h2" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, +] + [[package]] name = "hatchling" version = "1.30.1" @@ -409,6 +422,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/56/49/2797ec0ef88008a653a8867bb8d1e5c223cd2df8e40390dd5c6a0279cbc5/hatchling-1.30.1-py3-none-any.whl", hash = "sha256:161eacafb3c6f91526e92116d21426369f2c36e98c36a864f11a96345ad4ee31", size = 77489, upload-time = "2026-06-02T00:09:40.139Z" }, ] +[[package]] +name = "hpack" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/5b/fcabf6028144a8723726318b07a32c2f3314acdff6265743cf08a344b18e/hpack-4.2.0.tar.gz", hash = "sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0", size = 51300, upload-time = "2026-06-23T18:34:46.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/b4/4a9fcfb2aef6ba44d9073ecd301443aa00b3dac95de5619f2a7de7ec8a91/hpack-4.2.0-py3-none-any.whl", hash = "sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986", size = 34246, upload-time = "2026-06-23T18:34:45.472Z" }, +] + [[package]] name = "httpcore" version = "1.0.9" @@ -422,6 +444,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.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/9b/2b1d1833a58236d1f6ee755e027a3917da0db59cc9708554cefc440ee8b6/httpcore2-2.4.0.tar.gz", hash = "sha256:3093a8ab8980d9f910b9cb4351df9186a0ad2350a6284a9107ac9a362a584422", size = 64618, upload-time = "2026-06-11T06:35:53.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/72/4fdf2306143a92a471fad9f3655aa542d43aa9188a7c9534e82c9aecf837/httpcore2-2.4.0-py3-none-any.whl", hash = "sha256:5218779da5d6e3c2013ac706121abfb3815d450e0613495c0de50264dce58242", size = 80151, upload-time = "2026-06-11T06:35:50.89Z" }, +] + [[package]] name = "httpx" version = "0.28.1" @@ -437,6 +472,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] +[[package]] +name = "httpx2" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpcore2" }, + { name = "idna" }, + { name = "truststore" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/60/b43ced4ccf26e95b396dbf67051d3e5042b645917d4da0469dd82a3bdd4f/httpx2-2.4.0.tar.gz", hash = "sha256:32e0734b61eb0824b3f56a9e98d6d92d381a3ef12c0045aa917ee63df6c411ef", size = 81691, upload-time = "2026-06-11T06:35:54.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/45/82bc57c3d9c3314f663b67cc057f1c017a6450685dde513f4f8db5cf431f/httpx2-2.4.0-py3-none-any.whl", hash = "sha256:425acd99297829599decf6701386dd84db3542597d36d3e2e4def930ecd57fd9", size = 74941, upload-time = "2026-06-11T06:35:52.235Z" }, +] + +[package.optional-dependencies] +http2 = [ + { name = "h2" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, +] + [[package]] name = "hypothesis" version = "6.152.4" @@ -464,11 +529,11 @@ wheels = [ [[package]] name = "idna" -version = "3.13" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -1204,6 +1269,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/a4/81502f486f01db95bc8320646a8a12511f5e556cb63d5e224d91816605c4/trove_classifiers-2026.6.1.19-py3-none-any.whl", hash = "sha256:ab4c4ec93cc4a4e7815fa759906e05e6bb3f2fbd92ea0f897288c6a43efd15b3", size = 14211, upload-time = "2026-06-01T19:41:33.434Z" }, ] +[[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 = "twine" version = "6.2.0" @@ -1304,7 +1378,7 @@ source = { editable = "src/vercel" } dependencies = [ { name = "anyio" }, { name = "cbor2" }, - { name = "httpx" }, + { name = "httpx2", extra = ["http2"] }, { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, @@ -1318,7 +1392,7 @@ dependencies = [ requires-dist = [ { name = "anyio", specifier = ">=4.0.0,<5" }, { name = "cbor2", specifier = ">=6.0,<7" }, - { name = "httpx", specifier = ">=0.27.0,<1" }, + { name = "httpx2", extras = ["http2"], specifier = ">=2" }, { name = "pydantic", specifier = ">=2.7.0,<3" }, { name = "python-dotenv", specifier = ">=1.0.0,<2" }, { name = "typing-extensions", marker = "python_full_version < '3.11'", specifier = ">=4.0.0,<5" }, @@ -1336,13 +1410,13 @@ source = { editable = "src/vercel-headers" } name = "vercel-oidc" source = { editable = "src/vercel-oidc" } dependencies = [ - { name = "httpx" }, + { name = "httpx2", extra = ["http2"] }, { name = "vercel-headers" }, ] [package.metadata] requires-dist = [ - { name = "httpx", specifier = ">=0.27.0,<1" }, + { name = "httpx2", extras = ["http2"], specifier = ">=2" }, { name = "vercel-headers", editable = "src/vercel-headers" }, ]