diff --git a/examples/realtime/push_to_talk_app.py.lock b/examples/realtime/push_to_talk_app.py.lock index b4e5403afb..30c345313e 100644 --- a/examples/realtime/push_to_talk_app.py.lock +++ b/examples/realtime/push_to_talk_app.py.lock @@ -177,20 +177,20 @@ wheels = [ [[package]] name = "httpcore2" -version = "2.10.0" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "h11" }, { name = "truststore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/83/a896fc59940fc5a6e2aff3a4be1d92fa890112936803b331cae75a993c34/httpcore2-2.10.0.tar.gz", hash = "sha256:13c0cc3d1919d4f28457f60cd2c2abe04113a8af184ccf1142811beba936f9dc", size = 67427, upload-time = "2026-08-09T09:11:32.123Z" } +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/e5/4f/d149104195a35e2853a2fc203a8e3477747e58c80e17dda686dace174383/httpcore2-2.10.0-py3-none-any.whl", hash = "sha256:7df06cfb34070cae4f7c89be69dc1095eca138e9704ceffb98d25c1912ab6f01", size = 83000, upload-time = "2026-08-09T09:11:29.555Z" }, + { 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 = "httpx2" -version = "2.10.0" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio", marker = "sys_platform != 'emscripten'" }, @@ -200,9 +200,9 @@ dependencies = [ { name = "truststore", marker = "sys_platform != 'emscripten'" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/3d/f9a8c07a3884f3e5b26205e8436a18b3af61c5d53192c3bea235574dbbec/httpx2-2.10.0.tar.gz", hash = "sha256:8741d7329fe2c7885fc9ceb61c8217acfb87a85f75723714b89ebf7ad7196338", size = 98749, upload-time = "2026-08-09T09:11:33.24Z" } +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/b9/6d/a637d52449d98a6892d9a4dc0262587afdb6a66f201871842dce5a97b1c1/httpx2-2.10.0-py3-none-any.whl", hash = "sha256:5e3194a432701e1cc6f69a8b1b2fa199ef907013fede8d9a09a2c5b7b8141a18", size = 94355, upload-time = "2026-08-09T09:11:30.882Z" }, + { 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]] @@ -597,7 +597,7 @@ wheels = [ [[package]] name = "openai" -version = "3.3.0" +version = "3.16.2" source = { editable = "../../" } dependencies = [ { name = "anyio" }, @@ -618,12 +618,12 @@ requires-dist = [ { name = "aiohttp", marker = "extra == 'aiohttp'", specifier = ">=3.14.3" }, { name = "anyio", specifier = ">=4.10.0,<5" }, { name = "botocore", marker = "extra == 'bedrock'", specifier = ">=1.40.0,<2" }, - { name = "httpx2", specifier = ">=2.7.0,<3" }, - { name = "jiter", specifier = ">=0.10.0,<1" }, + { name = "httpx2", specifier = ">=2.12.0,<3" }, + { name = "jiter", specifier = ">=0.16.0,<1" }, { name = "numpy", marker = "extra == 'datalib'", specifier = ">=1" }, { name = "numpy", marker = "extra == 'voice-helpers'", specifier = ">=2.0.2" }, { name = "pandas", marker = "extra == 'datalib'", specifier = ">=1.2.3" }, - { name = "pydantic", specifier = ">=1.9.0,<3" }, + { name = "pydantic", specifier = ">=1.10.13,!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,<3" }, { name = "sniffio" }, { name = "sounddevice", marker = "extra == 'voice-helpers'", specifier = ">=0.5.1" }, { name = "typing-extensions", specifier = ">=4.14,<5" }, @@ -634,7 +634,7 @@ provides-extras = ["aiohttp", "realtime", "datalib", "voice-helpers", "bedrock"] [package.metadata.requires-dev] build = [ - { name = "hatchling", specifier = "==1.26.3" }, + { name = "hatchling", specifier = "==1.27.0" }, { name = "packaging", specifier = "==26.3" }, { name = "pathspec", specifier = "==1.1.1" }, { name = "pluggy", specifier = "==1.6.0" }, @@ -646,11 +646,10 @@ dev = [ { name = "botocore", specifier = "==1.42.97" }, { name = "griffe", specifier = ">=1" }, { name = "inline-snapshot", specifier = ">=0.28.0" }, - { name = "mypy", specifier = "==1.17" }, + { name = "mypy", specifier = "==2.3.1" }, { name = "pandas-stubs", specifier = ">=1.1.0.11" }, - { name = "pyright", specifier = "==1.1.399" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, + { name = "pytest", specifier = ">=9.0.3" }, + { name = "pytest-asyncio", specifier = ">=1.4.0" }, { name = "pytest-xdist", specifier = ">=3.6.1" }, { name = "rich", specifier = ">=13.7.1" }, { name = "ruff" }, diff --git a/pyproject.toml b/pyproject.toml index 231a977e3a..2104968afa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [ ] dependencies = [ - "httpx2>=2.7.0, <3", + "httpx2>=2.12.0, <3", "pydantic>=1.10.13, <3, !=2.0.*, !=2.1.*, !=2.2.*, !=2.3.*", "typing-extensions>=4.14, <5", "anyio>=4.10.0, <5", @@ -84,9 +84,11 @@ required-version = ">=0.12.1" exclude-newer = "8 days" default-groups = ["dev", "pydantic-v2"] conflicts = [[{ group = "pydantic-v1" }, { group = "pydantic-v2" }]] -# Security floors for transitive development dependencies. Constraints do not +# Security floors for transitive dependencies. Constraints do not # install these packages into the SDK's runtime environment. constraint-dependencies = [ + # HTTPX2 also publishes an exact pin to its matching httpcore2 release. + "httpcore2>=2.12.0", "azure-core>=1.38.0", "cryptography>=50.0.0", "msal>=1.37.0", diff --git a/scripts/check-python-version-policy.py b/scripts/check-python-version-policy.py index 9571360ceb..ae57734836 100644 --- a/scripts/check-python-version-policy.py +++ b/scripts/check-python-version-policy.py @@ -10,7 +10,7 @@ PRERELEASE = "3.15" UNMARKED_DEPENDENCIES = ( "aiohttp>=3.14.3", - "httpx2>=2.7.0, <3", + "httpx2>=2.12.0, <3", "anyio>=4.10.0, <5", "botocore>=1.40.0,<2", ) diff --git a/scripts/utils/validate-httpx2-wheel.py b/scripts/utils/validate-httpx2-wheel.py index 2d3e0bba5d..e1ea16140b 100644 --- a/scripts/utils/validate-httpx2-wheel.py +++ b/scripts/utils/validate-httpx2-wheel.py @@ -45,7 +45,7 @@ def validate_metadata(wheel: Path) -> None: if metadata["Requires-Python"] != ">=3.10": raise RuntimeError(f"Expected Python >=3.10, found: {metadata['Requires-Python']}") - for expected in ("httpx2<3,>=2.7.0", "anyio<5,>=4.10.0"): + for expected in ("httpx2<3,>=2.12.0", "anyio<5,>=4.10.0"): if not any(value.startswith(expected) for value in base): raise RuntimeError(f"Expected the base wheel to require {expected}: {base}") if any(requirement_name(value) == "httpx" for value in requirements): diff --git a/tests/test_client.py b/tests/test_client.py index b77b78cb9d..3fd6b8fa41 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1279,7 +1279,7 @@ def retry_handler(_request: httpx2.Request) -> httpx2.Response: ) assert response.retries_taken == failures_before_success - assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + assert int(response.http_request.headers["x-stainless-retry-count"]) == failures_before_success @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @@ -1374,7 +1374,7 @@ def retry_handler(_request: httpx2.Request) -> httpx2.Response: model="gpt-5.4", ) as response: assert response.retries_taken == failures_before_success - assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + assert int(response.http_request.headers["x-stainless-retry-count"]) == failures_before_success def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly @@ -2629,7 +2629,7 @@ def retry_handler(_request: httpx2.Request) -> httpx2.Response: ) assert response.retries_taken == failures_before_success - assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + assert int(response.http_request.headers["x-stainless-retry-count"]) == failures_before_success @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @@ -2724,7 +2724,7 @@ def retry_handler(_request: httpx2.Request) -> httpx2.Response: model="gpt-5.4", ) as response: assert response.retries_taken == failures_before_success - assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + assert int(response.http_request.headers["x-stainless-retry-count"]) == failures_before_success async def test_get_platform(self) -> None: platform = await asyncify(get_platform)() diff --git a/tests/test_httpx2_decompression.py b/tests/test_httpx2_decompression.py new file mode 100644 index 0000000000..e2348d637d --- /dev/null +++ b/tests/test_httpx2_decompression.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import httpx2 +import pytest + +from openai import OpenAI, AsyncOpenAI + +from .test_large_payload_contract import CompressedStream, AsyncCompressedStream, compressed_response + + +@pytest.mark.parametrize("encoding", ["gzip", "deflate"]) +async def test_decoding_failure_closes_response_stream(encoding: str) -> None: + stream = CompressedStream(b"not a compressed stream") + with OpenAI( + api_key="test-key", + max_retries=0, + http_client=httpx2.Client(transport=httpx2.MockTransport(lambda _: compressed_response(stream, encoding))), + ) as client: + with client.responses.with_streaming_response.create(model="gpt-4o-mini", input="Hello") as response: + with pytest.raises(httpx2.DecodingError): + list(response.iter_bytes()) + # Check before context-manager cleanup can hide an upstream leak. + assert stream.closed + assert response.http_response.is_closed + + async_stream = AsyncCompressedStream(b"not a compressed stream") + async with AsyncOpenAI( + api_key="test-key", + max_retries=0, + http_client=httpx2.AsyncClient( + transport=httpx2.MockTransport(lambda _: compressed_response(async_stream, encoding)) + ), + ) as async_client: + async with async_client.responses.with_streaming_response.create( + model="gpt-4o-mini", input="Hello" + ) as async_response: + with pytest.raises(httpx2.DecodingError): + _ = [chunk async for chunk in async_response.iter_bytes()] + assert async_stream.closed + assert async_response.http_response.is_closed diff --git a/tests/test_large_payload_contract.py b/tests/test_large_payload_contract.py index 4e45cd75ce..00b85a8e96 100644 --- a/tests/test_large_payload_contract.py +++ b/tests/test_large_payload_contract.py @@ -1,5 +1,6 @@ from __future__ import annotations +import gzip import json from typing import Any, Callable, Iterator, AsyncIterator from typing_extensions import override @@ -224,9 +225,87 @@ def events() -> Iterator[dict[str, Any]]: assert_intact(message.parsed.value, value) +class CompressedStream(httpx2.SyncByteStream): + def __init__(self, data: bytes) -> None: + self.data = data + self.closed = False + + @override + def __iter__(self) -> Iterator[bytes]: + # One small network chunk must not inflate into one enormous chunk. + yield self.data + + @override + def close(self) -> None: + self.closed = True + + +class AsyncCompressedStream(httpx2.AsyncByteStream): + def __init__(self, data: bytes) -> None: + self.data = data + self.closed = False + + @override + async def __aiter__(self) -> AsyncIterator[bytes]: + yield self.data + + @override + async def aclose(self) -> None: + self.closed = True + + +def compressed_response(stream: CompressedStream | AsyncCompressedStream, encoding: str) -> httpx2.Response: + # Passing a stream avoids eager decoding in Response's constructor. + return httpx2.Response( + 200, headers={"content-encoding": encoding, "content-type": "application/json"}, stream=stream + ) + + +async def check_large_compressed_responses() -> None: + text = "x" * PAYLOAD_SIZE + body = json.dumps(response_body(text)).encode() + data = gzip.compress(body) + stream = CompressedStream(data) + with OpenAI( + api_key="test-key", + max_retries=0, + http_client=httpx2.Client(transport=httpx2.MockTransport(lambda _: compressed_response(stream, "gzip"))), + ) as client: + with client.responses.with_streaming_response.create(model="gpt-4o-mini", input="Hello") as response: + size = 0 + for chunk in response.iter_bytes(): + # The upstream decoder's chunk bound is not a response-size cap. + assert len(chunk) <= 1024 * 1024 + assert chunk == body[size : size + len(chunk)] + size += len(chunk) + assert size == len(body) + assert stream.closed + + async_stream = AsyncCompressedStream(data) + async with AsyncOpenAI( + api_key="test-key", + max_retries=0, + http_client=httpx2.AsyncClient( + transport=httpx2.MockTransport(lambda _: compressed_response(async_stream, "gzip")) + ), + ) as async_client: + async with async_client.responses.with_streaming_response.create( + model="gpt-4o-mini", input="Hello" + ) as async_response: + size = 0 + async for chunk in async_response.iter_bytes(): + assert len(chunk) <= 1024 * 1024 + assert chunk == body[size : size + len(chunk)] + size += len(chunk) + assert size == len(body) + assert async_stream.closed + + async def test_large_payload_contract() -> None: # One test prevents pytest-xdist from running high-memory cases together. check_blocking_json() await check_structured_json() await check_responses_stream() check_chat_stream() + + await check_large_compressed_responses() diff --git a/uv.lock b/uv.lock index 45a0a6f081..1325814bc6 100644 --- a/uv.lock +++ b/uv.lock @@ -3,10 +3,10 @@ revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -29,6 +29,7 @@ exclude-newer-span = "P8D" constraints = [ { name = "azure-core", specifier = ">=1.38.0" }, { name = "cryptography", specifier = ">=50.0.0" }, + { name = "httpcore2", specifier = ">=2.12.0" }, { name = "msal", specifier = ">=1.37.0" }, { name = "pygments", specifier = ">=2.20.0" }, { name = "pyjwt", specifier = ">=2.13.0" }, @@ -804,31 +805,41 @@ wheels = [ [[package]] name = "httpcore2" -version = "2.7.0" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "h11" }, - { name = "truststore" }, + { name = "h11", marker = "python_full_version < '3.11' or sys_platform != 'emscripten' or (extra == 'group-6-openai-pydantic-v1' and extra == 'group-6-openai-pydantic-v2')" }, + { name = "truststore", marker = "python_full_version < '3.11' or sys_platform != 'emscripten' or (extra == 'group-6-openai-pydantic-v1' and extra == 'group-6-openai-pydantic-v2')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/fe/6a3f9f1a8bb8733326140737446aaf72fddb8b54b8f202302f5c84960613/httpcore2-2.7.0.tar.gz", hash = "sha256:6dc0fedf329a52a990930a5579edfebaea81118ea700ea0dd7de2b5e5be49efc", size = 65593, upload-time = "2026-07-14T20:40:01.111Z" } +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/6f/6c/62e2e279e63fc4f7a5ee841ef13175a8bbc613f258e9dcc186e9de803a42/httpcore2-2.7.0-py3-none-any.whl", hash = "sha256:1452f589fe23f55b44546cd884294c41a29330af902bc0b71a761fd52d18f92b", size = 81506, upload-time = "2026-07-14T20:39:58.053Z" }, + { 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 = "httpx2" -version = "2.7.0" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "httpcore2" }, + { name = "anyio", marker = "sys_platform != 'emscripten' or (extra == 'group-6-openai-pydantic-v1' and extra == 'group-6-openai-pydantic-v2')" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten' or (extra == 'group-6-openai-pydantic-v1' and extra == 'group-6-openai-pydantic-v2')" }, + { name = "httpx2-jsfetch", marker = "(python_full_version >= '3.12' and sys_platform == 'emscripten') or (python_full_version < '3.12' and extra == 'group-6-openai-pydantic-v1' and extra == 'group-6-openai-pydantic-v2') or (sys_platform != 'emscripten' and extra == 'group-6-openai-pydantic-v1' and extra == 'group-6-openai-pydantic-v2')" }, { name = "idna" }, - { name = "truststore" }, + { name = "truststore", marker = "sys_platform != 'emscripten' or (extra == 'group-6-openai-pydantic-v1' and extra == 'group-6-openai-pydantic-v2')" }, { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-6-openai-pydantic-v1' and extra == 'group-6-openai-pydantic-v2')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/4a/129b2e21b90ac2985d3928d96792bccc39bc6dfe796c5eee2d8ec06d4105/httpx2-2.7.0.tar.gz", hash = "sha256:8b30709aed5c8465b0dd3b95c09ce301c8f79e7e7a2d00ab0af551e0d0375b07", size = 94487, upload-time = "2026-07-14T20:40:02.318Z" } +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]] +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/1d/b8/c341bba6411bdfda786020343c47a75ef472f6085caf82391b142b1a3ad9/httpx2-2.7.0-py3-none-any.whl", hash = "sha256:ed2a2719c696789e09493bd8e2bec3d8bd925cc6e26b68389ec25ade132f7bf4", size = 90234, upload-time = "2026-07-14T20:39:59.531Z" }, + { 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]] @@ -1435,10 +1446,10 @@ version = "2.4.6" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -1593,7 +1604,7 @@ requires-dist = [ { name = "aiohttp", marker = "extra == 'aiohttp'", specifier = ">=3.14.3" }, { name = "anyio", specifier = ">=4.10.0,<5" }, { name = "botocore", marker = "extra == 'bedrock'", specifier = ">=1.40.0,<2" }, - { name = "httpx2", specifier = ">=2.7.0,<3" }, + { name = "httpx2", specifier = ">=2.12.0,<3" }, { name = "jiter", specifier = ">=0.16.0,<1" }, { name = "numpy", marker = "extra == 'datalib'", specifier = ">=1" }, { name = "numpy", marker = "extra == 'voice-helpers'", specifier = ">=2.0.2" }, @@ -1891,10 +1902,10 @@ version = "1.10.26" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", @@ -1943,10 +1954,10 @@ version = "2.12.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.15' and sys_platform == 'win32'", - "python_full_version == '3.14.*' and sys_platform == 'win32'", "python_full_version >= '3.15' and sys_platform == 'emscripten'", - "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'",