Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/a2a/client/transports/http_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def get_http_args(context: ClientCallContext | None) -> dict[str, Any]:
if context and context.service_parameters:
http_kwargs['headers'] = context.service_parameters.copy()
if context and context.timeout is not None:
http_kwargs['timeout'] = httpx.Timeout(context.timeout)
http_kwargs['timeout'] = context.timeout
return http_kwargs


Expand Down Expand Up @@ -179,9 +179,12 @@ def __init__(
url: str,
**kwargs: Any,
) -> None:
headers = httpx.Headers(kwargs.pop('headers', None))
headers.setdefault('Accept', 'text/event-stream')
headers.setdefault('Cache-Control', 'no-store')
headers: dict[str, str] = dict(kwargs.pop('headers', None) or {})
present = {key.lower() for key in headers}
if 'accept' not in present:
headers['Accept'] = 'text/event-stream'
if 'cache-control' not in present:
headers['Cache-Control'] = 'no-store'
self._request = client.build_request(
method, url, headers=headers, **kwargs
)
Expand Down
49 changes: 49 additions & 0 deletions tests/client/transports/test_http_helpers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import httpx
import pytest

from a2a.client.client import ClientCallContext
from a2a.client.errors import A2AClientError
from a2a.client.transports.http_helpers import (
_SSEEventSource,
_default_sse_error_handler,
get_http_args,
parse_sse_stream,
send_http_stream_request,
)
Expand Down Expand Up @@ -62,3 +65,49 @@ async def test_send_http_stream_request_non_sse(mocker):
c async for c in send_http_stream_request(client, 'GET', 'http://test')
]
assert chunks == ['plain error response']


def test_get_http_args_passes_timeout_through_unwrapped():
"""The timeout stays a bare float, for the client itself to wrap."""
http_kwargs = get_http_args(ClientCallContext(timeout=5.0))

assert http_kwargs['timeout'] == 5.0
assert not isinstance(http_kwargs['timeout'], httpx.Timeout)

request = httpx.AsyncClient().build_request(
'GET', 'http://test', **http_kwargs
)
assert request.extensions['timeout'] == {
'connect': 5.0,
'read': 5.0,
'write': 5.0,
'pool': 5.0,
}


def test_sse_event_source_sets_default_stream_headers():
source = _SSEEventSource(httpx.AsyncClient(), 'GET', 'http://test')

assert source._request.headers['Accept'] == 'text/event-stream'
assert source._request.headers['Cache-Control'] == 'no-store'


@pytest.mark.parametrize(
'caller_spelling', ['accept', 'Accept', 'ACCEPT', 'AcCePt']
)
def test_sse_event_source_keeps_caller_accept_whatever_its_case(
caller_spelling: str,
):
"""A caller's own Accept wins, and is never joined by a second one.

HTTP header names are case-insensitive, so matching the caller's
spelling exactly is not enough to detect that they already set one.
"""
source = _SSEEventSource(
httpx.AsyncClient(),
'GET',
'http://test',
headers={caller_spelling: 'application/json'},
)

assert source._request.headers.get_list('accept') == ['application/json']
2 changes: 1 addition & 1 deletion tests/client/transports/test_jsonrpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ async def test_send_message_with_timeout_context(
mock_httpx_client.build_request.assert_called_once()
_, kwargs = mock_httpx_client.build_request.call_args
assert 'timeout' in kwargs
assert kwargs['timeout'] == httpx.Timeout(15.0)
assert kwargs['timeout'] == 15.0


class TestGetTask:
Expand Down
2 changes: 1 addition & 1 deletion tests/client/transports/test_rest_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ async def test_send_message_with_timeout_context(
mock_build_request.assert_called_once()
_, kwargs = mock_build_request.call_args
assert 'timeout' in kwargs
assert kwargs['timeout'] == httpx.Timeout(10.0)
assert kwargs['timeout'] == 10.0

@pytest.mark.asyncio
async def test_url_serialization(
Expand Down
Loading