diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 7b828563..533ee43b 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -76,7 +76,7 @@ jobs: - name: Install uv run: | - python -m pip install --upgrade pip + python -m pip install --upgrade "pip>=26.2" # PYSEC-2026-3721 fix floor pip install uv - name: Install project dependencies (hash-verified from uv.lock) @@ -297,7 +297,7 @@ jobs: - name: Install uv run: | - python -m pip install --upgrade pip + python -m pip install --upgrade "pip>=26.2" # PYSEC-2026-3721 fix floor pip install uv - name: Install project without [uipath] extra @@ -381,7 +381,7 @@ jobs: - name: Install uv run: | - python -m pip install --upgrade pip + python -m pip install --upgrade "pip>=26.2" # PYSEC-2026-3721 fix floor pip install uv - name: Install project dependencies (hash-verified from uv.lock) @@ -527,7 +527,7 @@ jobs: - name: Install uv run: | - python -m pip install --upgrade pip + python -m pip install --upgrade "pip>=26.2" # PYSEC-2026-3721 fix floor pip install uv - name: Install project dependencies (hash-verified from uv.lock) @@ -697,7 +697,7 @@ jobs: - name: Install uv run: | - python -m pip install --upgrade pip + python -m pip install --upgrade "pip>=26.2" # PYSEC-2026-3721 fix floor pip install uv - name: Install project dependencies (hash-verified from uv.lock) @@ -840,7 +840,7 @@ jobs: - name: Install uv run: | - python -m pip install --upgrade pip + python -m pip install --upgrade "pip>=26.2" # PYSEC-2026-3721 fix floor pip install uv - name: Install project dependencies (with codex extra) @@ -919,7 +919,7 @@ jobs: - name: Install uv run: | - python -m pip install --upgrade pip + python -m pip install --upgrade "pip>=26.2" # PYSEC-2026-3721 fix floor pip install uv - name: Install project dependencies (hash-verified from uv.lock) diff --git a/pyproject.toml b/pyproject.toml index ae1bcdb9..3650a0c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,14 @@ dependencies = [ "click>=8.3.3", "rich>=14.3.3", "python-dotenv>=1.2.2", - "anthropic>=0.86.0", + # Cap the major: 1.0.0 already proved a major can drop call kwargs + # (temperature/top_p/top_k off messages.create — see judge_anthropic.py). + "anthropic>=1.0.0,<2.0.0", + # anthropic>=1.0.0 migrated its HTTP layer from httpx to httpx2 (its own + # exceptions, e.g. APIConnectionError, now carry an httpx2.Request). Declared + # explicitly since our code/tests construct httpx2 types directly. Capped to + # mirror anthropic's own bound on it (`httpx2<3,>=2.0.0`). + "httpx2>=2.12.0,<3.0.0", "claude-agent-sdk>=0.2.124", "anyio>=4.13.0", "radon>=6.0.1", diff --git a/src/coder_eval/evaluation/judge_anthropic.py b/src/coder_eval/evaluation/judge_anthropic.py index c8dfde82..4bddcc0f 100644 --- a/src/coder_eval/evaluation/judge_anthropic.py +++ b/src/coder_eval/evaluation/judge_anthropic.py @@ -40,7 +40,14 @@ async def invoke_anthropic_judge_async( Returns the SDK response converted to a dict via ``model_dump`` so the caller can reuse ``extract_verdict_from_anthropic_response`` — Anthropic's native shape (content blocks with ``type: tool_use``) is identical - between this SDK call and the Bedrock httpx-direct call. + between this SDK call and the Bedrock httpx2-direct call. + + ``temperature`` is forwarded via ``extra_body`` rather than as a top-level + kwarg: anthropic 1.0.0 dropped ``temperature``/``top_p``/``top_k`` from + ``AsyncMessages.create``'s typed signature, but the Messages API itself + still accepts ``temperature`` in the raw JSON body — the same body shape + the Bedrock path already sends it in — so ``extra_body`` keeps both judge + backends honoring ``LLMJudgeCriterion.temperature`` identically. """ alias = to_anthropic_alias(model) client = AsyncAnthropic(timeout=timeout_seconds) @@ -51,12 +58,17 @@ async def invoke_anthropic_judge_async( system=system, messages=[{"role": "user", "content": user}], max_tokens=max_tokens, - temperature=temperature, - tools=[tool_spec], # type: ignore[arg-type] + tools=[tool_spec], # pyright: ignore[reportArgumentType] tool_choice={"type": "tool", "name": tool_spec["name"]}, + extra_body={"temperature": temperature}, ) except APIError as e: # The SDK already retries transient failures internally (2 attempts # by default) — do not add another retry loop here. raise JudgeInfrastructureError(f"Anthropic judge API error: {e}") from e + except Exception as e: + # A signature/contract break (e.g. a removed or renamed kwarg after an + # SDK bump) must not be scored as an agent failure — see CLAUDE.md's + # CE039 rationale: an eval-infra fault is not the agent's fault. + raise JudgeInfrastructureError(f"Anthropic judge call failed: {e}") from e return response.model_dump() diff --git a/src/coder_eval/evaluation/judge_bedrock.py b/src/coder_eval/evaluation/judge_bedrock.py index 4e364a0c..ca201645 100644 --- a/src/coder_eval/evaluation/judge_bedrock.py +++ b/src/coder_eval/evaluation/judge_bedrock.py @@ -14,7 +14,7 @@ intentionally do not share an HTTP client. Async on purpose: this is llm_judge's only implementation of the network -call (there is no sync twin) — ``httpx.AsyncClient`` lets the call yield the +call (there is no sync twin) — ``httpx2.AsyncClient`` lets the call yield the event loop instead of blocking a thread-pool thread for the wait, so ``SuccessChecker.check_all_async`` awaits it directly without pinning a thread. (``check_all_async`` currently runs criteria sequentially; running @@ -27,7 +27,7 @@ import logging from typing import Any -import httpx +import httpx2 from coder_eval.errors import JudgeInfrastructureError from coder_eval.errors.categories import RetryConfig @@ -90,13 +90,13 @@ async def invoke_bedrock_judge_async( attempts = _JUDGE_RETRY.max_retries + 1 last_failure = "" last_exc: Exception | None = None - async with httpx.AsyncClient() as client: + async with httpx2.AsyncClient() as client: for attempt in range(attempts): if attempt: await asyncio.sleep(compute_backoff(_JUDGE_RETRY, attempt - 1)) try: response = await client.post(url, headers=headers, json=body, timeout=timeout_seconds) - except httpx.HTTPError as e: + except httpx2.HTTPError as e: last_failure = f"Bedrock invoke transport error: {e}" last_exc = e logger.warning("Bedrock judge attempt %d/%d failed: %s", attempt + 1, attempts, last_failure) diff --git a/src/coder_eval/evaluation/verdict_tool.py b/src/coder_eval/evaluation/verdict_tool.py index 9b887851..815cc4c6 100644 --- a/src/coder_eval/evaluation/verdict_tool.py +++ b/src/coder_eval/evaluation/verdict_tool.py @@ -40,7 +40,7 @@ "description": _SUBMIT_VERDICT_DESCRIPTION, "input_schema": JudgeVerdict.model_json_schema(), } -"""Anthropic-native tool spec for the Bedrock httpx-direct path and Anthropic SDK calls. +"""Anthropic-native tool spec for the Bedrock httpx2-direct path and Anthropic SDK calls. Note: Anthropic uses ``input_schema``, not OpenAI's ``parameters``. """ @@ -132,7 +132,7 @@ def extract_verdict_from_anthropic_response( ``{"type": "tool_use", "name": "submit_verdict"}`` and validates the last one's ``input`` against ``JudgeVerdict``. Used by: - * The Bedrock httpx path (``invoke_bedrock_judge_async``) — raw JSON dict. + * The Bedrock httpx2 path (``invoke_bedrock_judge_async``) — raw JSON dict. * The Anthropic SDK Direct path (``invoke_anthropic_judge_async``) — response converted via ``Message.model_dump()``. diff --git a/tests/test_judge_anthropic.py b/tests/test_judge_anthropic.py index a5b9dfc2..4747867e 100644 --- a/tests/test_judge_anthropic.py +++ b/tests/test_judge_anthropic.py @@ -10,6 +10,7 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest +from anthropic.resources.messages import AsyncMessages from coder_eval.evaluation.judge_anthropic import invoke_anthropic_judge_async from coder_eval.evaluation.verdict_tool import SUBMIT_VERDICT_ANTHROPIC_TOOL @@ -29,12 +30,25 @@ def _make_response(*, score: float = 0.5, rationale: str = "ok") -> MagicMock: def _make_client(response: MagicMock | None = None) -> MagicMock: client = MagicMock() - client.messages.create = AsyncMock(return_value=response if response is not None else _make_response()) + # spec-bound to the real ``AsyncMessages.create`` signature so a kwarg the + # installed SDK no longer accepts (e.g. a removed ``temperature``) fails + # here instead of silently passing against an unconstrained MagicMock. + client.messages = MagicMock(spec=AsyncMessages) + client.messages.create = AsyncMock( + wraps=lambda **kwargs: _bind_and_return(response if response is not None else _make_response(), **kwargs) + ) client.__aenter__ = AsyncMock(return_value=client) client.__aexit__ = AsyncMock(return_value=None) return client +def _bind_and_return(response: MagicMock, **kwargs: Any) -> MagicMock: + import inspect + + inspect.signature(AsyncMessages.create).bind(MagicMock(), **kwargs) + return response + + async def _invoke(**overrides): defaults = { "model": "anthropic.claude-sonnet-4-6", @@ -79,24 +93,43 @@ async def test_invoke_anthropic_judge_raises_on_empty_model() -> None: async def test_invoke_anthropic_judge_passes_temperature_and_max_tokens() -> None: + """``temperature`` travels via ``extra_body`` — anthropic 1.0.0 dropped it as a + top-level ``messages.create`` kwarg, but the Messages API still accepts it in + the raw JSON body (the same body shape the Bedrock path sends it in).""" client = _make_client() with patch("coder_eval.evaluation.judge_anthropic.AsyncAnthropic", return_value=client): await _invoke(temperature=0.7, max_tokens=321, system="sys", user="usr") kwargs: dict[str, Any] = client.messages.create.call_args.kwargs - assert kwargs["temperature"] == 0.7 + assert "temperature" not in kwargs + assert kwargs["extra_body"] == {"temperature": 0.7} assert kwargs["max_tokens"] == 321 assert kwargs["system"] == "sys" assert kwargs["messages"] == [{"role": "user", "content": "usr"}] +async def test_invoke_anthropic_judge_escalates_on_signature_break() -> None: + """A kwarg the installed SDK no longer accepts must escalate as infra, not + silently score the row 0.0 (see judge_bedrock.py's parallel retry/escalation + contract and CLAUDE.md's CE039 rationale).""" + from coder_eval.errors import JudgeInfrastructureError + + client = _make_client() + client.messages.create.side_effect = TypeError("create() got an unexpected keyword argument 'temperature'") + with ( + patch("coder_eval.evaluation.judge_anthropic.AsyncAnthropic", return_value=client), + pytest.raises(JudgeInfrastructureError, match="Anthropic judge call failed"), + ): + await _invoke() + + async def test_invoke_anthropic_judge_wraps_api_error() -> None: - import httpx + import httpx2 from anthropic import APIConnectionError from coder_eval.errors import JudgeInfrastructureError client = _make_client() - sdk_error = APIConnectionError(request=httpx.Request("POST", "https://api.anthropic.com")) + sdk_error = APIConnectionError(request=httpx2.Request("POST", "https://api.anthropic.com")) client.messages.create.side_effect = sdk_error with ( patch("coder_eval.evaluation.judge_anthropic.AsyncAnthropic", return_value=client), diff --git a/tests/test_judge_bedrock.py b/tests/test_judge_bedrock.py index da2bc3b6..a3ea103a 100644 --- a/tests/test_judge_bedrock.py +++ b/tests/test_judge_bedrock.py @@ -9,6 +9,7 @@ from typing import Any from unittest.mock import AsyncMock, MagicMock +import httpx2 import pytest from coder_eval.errors import JudgeInfrastructureError @@ -51,7 +52,7 @@ def _tool_use_response(score: float = 0.5, rationale: str = "ok") -> dict[str, A def _make_async_client(post_side_effect) -> MagicMock: - """Mock ``httpx.AsyncClient`` — supports the ``async with`` + repeated ``.post(...)`` shape.""" + """Mock ``httpx2.AsyncClient`` — supports the ``async with`` + repeated ``.post(...)`` shape.""" client = MagicMock() client.__aenter__ = AsyncMock(return_value=client) client.__aexit__ = AsyncMock(return_value=None) @@ -83,7 +84,7 @@ def fake_post(url: str, *, headers: dict[str, str], json: dict[str, Any], timeou captured["timeout"] = timeout return _make_response(status_code=200, json_data=_tool_use_response(score=0.5)) - monkeypatch.setattr(judge_bedrock.httpx, "AsyncClient", lambda: _make_async_client(fake_post)) + monkeypatch.setattr(judge_bedrock.httpx2, "AsyncClient", lambda: _make_async_client(fake_post)) result = await _invoke(max_tokens=42) assert result["content"][0]["type"] == "tool_use" @@ -99,7 +100,7 @@ def fake_post(url: str, *, headers: dict[str, str], json: dict[str, Any], timeou async def test_invoke_bedrock_judge_raises_on_4xx(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( - judge_bedrock.httpx, + judge_bedrock.httpx2, "AsyncClient", lambda: _make_async_client(lambda *a, **kw: _make_response(status_code=400, text='{"message":"bad model"}')), ) @@ -115,7 +116,7 @@ def counting_post(*a: Any, **kw: Any) -> MagicMock: calls.append(1) return _make_response(status_code=500, text="upstream error") - monkeypatch.setattr(judge_bedrock.httpx, "AsyncClient", lambda: _make_async_client(counting_post)) + monkeypatch.setattr(judge_bedrock.httpx2, "AsyncClient", lambda: _make_async_client(counting_post)) with pytest.raises(JudgeInfrastructureError, match="Bedrock invoke failed: 500"): await _invoke() # Exactly 1 initial call + max_retries retries — read from the constant, don't hardcode. @@ -125,7 +126,7 @@ def counting_post(*a: Any, **kw: Any) -> MagicMock: async def test_invoke_bedrock_judge_raises_on_non_dict_response(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( - judge_bedrock.httpx, + judge_bedrock.httpx2, "AsyncClient", lambda: _make_async_client(lambda *a, **kw: _make_response(json_data=["not a dict"])), ) @@ -149,16 +150,14 @@ async def test_invoke_bedrock_judge_raises_on_empty_model() -> None: async def test_invoke_bedrock_judge_wraps_transport_error( monkeypatch: pytest.MonkeyPatch, no_sleep: list[float] ) -> None: - import httpx as _httpx - def raising_post(*a: Any, **kw: Any) -> MagicMock: - raise _httpx.ConnectTimeout("connection timed out") + raise httpx2.ConnectTimeout("connection timed out") - monkeypatch.setattr(judge_bedrock.httpx, "AsyncClient", lambda: _make_async_client(raising_post)) + monkeypatch.setattr(judge_bedrock.httpx2, "AsyncClient", lambda: _make_async_client(raising_post)) with pytest.raises(JudgeInfrastructureError, match="Bedrock invoke transport error") as excinfo: await _invoke() assert "connection timed out" in str(excinfo.value) - assert isinstance(excinfo.value.__cause__, _httpx.ConnectTimeout) + assert isinstance(excinfo.value.__cause__, httpx2.ConnectTimeout) async def test_invoke_bedrock_judge_strips_v1_suffix_in_url(monkeypatch: pytest.MonkeyPatch) -> None: @@ -168,7 +167,7 @@ def fake_post(url: str, **kw: Any) -> MagicMock: captured["url"] = url return _make_response(json_data=_tool_use_response()) - monkeypatch.setattr(judge_bedrock.httpx, "AsyncClient", lambda: _make_async_client(fake_post)) + monkeypatch.setattr(judge_bedrock.httpx2, "AsyncClient", lambda: _make_async_client(fake_post)) await _invoke(model="anthropic.claude-opus-4-6-v1") assert "/model/eu.anthropic.claude-opus-4-6/invoke" in captured["url"] @@ -189,7 +188,7 @@ def sequenced_post(*a: Any, **kw: Any) -> MagicMock: calls.append(1) return next(responses) - monkeypatch.setattr(judge_bedrock.httpx, "AsyncClient", lambda: _make_async_client(sequenced_post)) + monkeypatch.setattr(judge_bedrock.httpx2, "AsyncClient", lambda: _make_async_client(sequenced_post)) result = await _invoke() assert result["content"][0]["input"]["score"] == 0.9 assert len(calls) == 3 @@ -205,7 +204,7 @@ def counting_post(*a: Any, **kw: Any) -> MagicMock: calls.append(1) return _make_response(status_code=403, text="forbidden") - monkeypatch.setattr(judge_bedrock.httpx, "AsyncClient", lambda: _make_async_client(counting_post)) + monkeypatch.setattr(judge_bedrock.httpx2, "AsyncClient", lambda: _make_async_client(counting_post)) with pytest.raises(JudgeInfrastructureError, match="Bedrock invoke failed: 403"): await _invoke() assert len(calls) == 1 @@ -215,17 +214,15 @@ def counting_post(*a: Any, **kw: Any) -> MagicMock: async def test_invoke_bedrock_judge_retries_connect_error_then_succeeds( monkeypatch: pytest.MonkeyPatch, no_sleep: list[float] ) -> None: - import httpx as _httpx - calls: list[int] = [] def flaky_post(*a: Any, **kw: Any) -> MagicMock: calls.append(1) if len(calls) == 1: - raise _httpx.ConnectError("connection refused") + raise httpx2.ConnectError("connection refused") return _make_response(status_code=200, json_data=_tool_use_response()) - monkeypatch.setattr(judge_bedrock.httpx, "AsyncClient", lambda: _make_async_client(flaky_post)) + monkeypatch.setattr(judge_bedrock.httpx2, "AsyncClient", lambda: _make_async_client(flaky_post)) result = await _invoke() assert result["content"][0]["type"] == "tool_use" assert len(calls) == 2 @@ -237,6 +234,6 @@ async def test_invoke_bedrock_judge_malformed_json_body_escalates(monkeypatch: p response = _make_response(status_code=200) response.json.side_effect = _json.JSONDecodeError("Expecting value", doc="", pos=0) - monkeypatch.setattr(judge_bedrock.httpx, "AsyncClient", lambda: _make_async_client(lambda *a, **kw: response)) + monkeypatch.setattr(judge_bedrock.httpx2, "AsyncClient", lambda: _make_async_client(lambda *a, **kw: response)) with pytest.raises(JudgeInfrastructureError, match="not valid JSON"): await _invoke() diff --git a/tests/test_judge_burn_in_live.py b/tests/test_judge_burn_in_live.py index ed9b112b..67cfc3f1 100644 --- a/tests/test_judge_burn_in_live.py +++ b/tests/test_judge_burn_in_live.py @@ -1,7 +1,7 @@ """Live burn-in tests for the typed verdict tool channel. Exercises the ``submit_verdict`` channel against the three real backends: -Anthropic-direct (Anthropic SDK + native ``tools``), Bedrock (httpx + +Anthropic-direct (Anthropic SDK + native ``tools``), Bedrock (httpx2 + Anthropic-native tools), and the Claude Code SDK (in-process MCP server). Each test ``pytest.skip``s when the required credentials are not present, so the file is safe to run in CI without a credential set. @@ -93,7 +93,7 @@ def test_llm_judge_anthropic_direct_tool_channel(hello_sandbox: Sandbox) -> None def test_llm_judge_bedrock_tool_channel(hello_sandbox: Sandbox) -> None: - """Bedrock route: httpx POST with Anthropic-native ``tools`` + ``tool_choice``.""" + """Bedrock route: httpx2 POST with Anthropic-native ``tools`` + ``tool_choice``.""" bearer = os.environ.get("AWS_BEARER_TOKEN_BEDROCK") region = os.environ.get("AWS_REGION") if not bearer or not region: diff --git a/uv.lock b/uv.lock index bd9fd214..d376af77 100644 --- a/uv.lock +++ b/uv.lock @@ -153,21 +153,20 @@ wheels = [ [[package]] name = "anthropic" -version = "0.102.0" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "distro" }, { name = "docstring-parser" }, - { name = "httpx" }, + { name = "httpx2" }, { name = "jiter" }, { name = "pydantic" }, { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/47/cb2a71f70431fb09af4db83e3ea89eb2dd8e0e348d27af53ed32e6c599dd/anthropic-0.102.0.tar.gz", hash = "sha256:96f747cad11886c4ae12d4080131b94eebd68b202bd2190fe27959031bb1fa9c", size = 763697, upload-time = "2026-05-13T18:12:41.624Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/aa/4978e58035bd6c638c7b483450a68b7ef2d732ab78885e27bb9db0cff1a2/anthropic-1.0.0.tar.gz", hash = "sha256:42be3c97604af7252c5898413aee076ace6c46e9bca0d0d90ceb77c7d3719027", size = 1077769, upload-time = "2026-08-20T19:59:00.565Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/75/0f6c603594876413bc858a00e7cc0d80a0cc14edf5c7b959a3ea6ec45e44/anthropic-0.102.0-py3-none-any.whl", hash = "sha256:ab96540bbd4b0f36564252d955a86f8abbe4f00944a24bc9931acc9b139bab6f", size = 763070, upload-time = "2026-05-13T18:12:43.474Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5b/db4a854aebf5d33a5ab714c46af6eb85ee44f390ed29b7b325c00b9f11ed/anthropic-1.0.0-py3-none-any.whl", hash = "sha256:32dd52e9e1d774393b27182f451398ba4262287a4d0eab30887f89f1481b3ae4", size = 1171725, upload-time = "2026-08-20T19:58:58.725Z" }, ] [[package]] @@ -457,6 +456,7 @@ dependencies = [ { name = "azure-monitor-opentelemetry-exporter" }, { name = "claude-agent-sdk" }, { name = "click" }, + { name = "httpx2" }, { name = "jmespath" }, { name = "jsonschema" }, { name = "opentelemetry-sdk" }, @@ -498,7 +498,7 @@ uipath = [ [package.metadata] requires-dist = [ - { name = "anthropic", specifier = ">=0.86.0" }, + { name = "anthropic", specifier = ">=1.0.0,<2.0.0" }, { name = "anyio", specifier = ">=4.13.0" }, { name = "azure-monitor-opentelemetry-exporter", specifier = ">=1.0.0b30,<1.1.0" }, { name = "bandit", extras = ["toml"], marker = "extra == 'dev'", specifier = ">=1.9.4" }, @@ -506,6 +506,7 @@ requires-dist = [ { name = "click", specifier = ">=8.3.3" }, { name = "defusedxml", marker = "extra == 'dev'", specifier = ">=0.7.1" }, { name = "google-antigravity", marker = "extra == 'antigravity'", specifier = "==0.1.7" }, + { name = "httpx2", specifier = ">=2.12.0,<3.0.0" }, { name = "jmespath", specifier = ">=1.1.0" }, { name = "jsonschema", specifier = ">=4.26.0" }, { name = "mcp", marker = "extra == 'dev'", specifier = ">=1.28.1" }, @@ -894,6 +895,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" }, + { name = "truststore" }, +] +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 = "httpx" version = "0.28.1" @@ -918,6 +932,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, ] +[[package]] +name = "httpx2" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "sys_platform == 'emscripten'" }, + { name = "idna" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, +] +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/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 = "identify" version = "2.6.18" @@ -929,11 +968,11 @@ wheels = [ [[package]] name = "idna" -version = "3.15" +version = "3.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, ] [[package]] @@ -1473,11 +1512,11 @@ wheels = [ [[package]] name = "pip" -version = "26.1.2" +version = "26.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/91/47e7d486260f618783899587af63ccf7980fb60245c3e63dd4571c6b57ad/pip-26.1.2.tar.gz", hash = "sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605", size = 1840799, upload-time = "2026-05-31T17:33:58.56Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/15/4500e320e6b101ec3b719ae85b697d9940b6cda672bc555bd6016fc60c6f/pip-26.2.1.tar.gz", hash = "sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f", size = 1848877, upload-time = "2026-08-04T22:51:14.148Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/95/6b5cb3461ea5673ba0995989746db58eb18b91b54dbf331e72f569540946/pip-26.1.2-py3-none-any.whl", hash = "sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab", size = 1813144, upload-time = "2026-05-31T17:33:56.772Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6e/1736e5b4ae2b778ef2f81c47d797de9f891d4d8acb047a24ca37a60294dd/pip-26.2.1-py3-none-any.whl", hash = "sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e", size = 1816632, upload-time = "2026-08-04T22:51:12.472Z" }, ] [[package]]