Skip to content

Commit 43ae5d6

Browse files
committed
Fixes after merge
1 parent c9a5166 commit 43ae5d6

8 files changed

Lines changed: 75 additions & 12 deletions

File tree

.github/workflows/linter.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ jobs:
6363
"${{ steps.ruff-format.outcome }}" == "failure" || \
6464
"${{ steps.mypy.outcome }}" == "failure" || \
6565
"${{ steps.pyright.outcome }}" == "failure"]]; then
66-
# Disable due to not to block on "PR too large error"
67-
# "${{ steps.jscpd.outcome }}" == "failure" ]]; then
6866
echo "One or more linting/checking steps failed."
6967
exit 1
7068
fi

.github/workflows/unit-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
3737
strategy:
3838
matrix:
39-
python-version: ["3.10", "3.13"]
39+
python-version: ['3.10', '3.13']
4040
steps:
4141
- name: Checkout code
4242
uses: actions/checkout@v6
@@ -57,6 +57,6 @@ jobs:
5757
- name: Install dependencies
5858
run: uv sync --locked
5959
- name: Run tests and check coverage
60-
run: uv run pytest --cov=a2a --cov-report term --cov-fail-under=85
60+
run: uv run pytest --cov=a2a --cov-report term --cov-fail-under=88
6161
- name: Show coverage summary in log
6262
run: uv run coverage report

.github/workflows/update-a2a-types.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Set up Python
1818
uses: actions/setup-python@v6
1919
with:
20-
python-version: "3.10"
20+
python-version: '3.10'
2121
- name: Install uv
2222
uses: astral-sh/setup-uv@v7
2323
- name: Configure uv shell
@@ -41,8 +41,8 @@ jobs:
4141
token: ${{ secrets.A2A_BOT_PAT }}
4242
committer: a2a-bot <a2a-bot@google.com>
4343
author: a2a-bot <a2a-bot@google.com>
44-
commit-message: "${{ github.event.client_payload.message }}"
45-
title: "${{ github.event.client_payload.message }}"
44+
commit-message: '${{ github.event.client_payload.message }}'
45+
title: '${{ github.event.client_payload.message }}'
4646
body: |
4747
Commit: https://github.com/a2aproject/A2A/commit/${{ github.event.client_payload.sha }}
4848
branch: auto-update-a2a-types-${{ github.event.client_payload.sha }}

src/a2a/client/transports/jsonrpc.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,17 @@ async def send_message_streaming(
176176
try:
177177
event_source.response.raise_for_status()
178178
async for sse in event_source.aiter_sse():
179+
if not sse.data:
180+
continue
179181
json_rpc_response = JSONRPC20Response.from_json(sse.data)
180182
if json_rpc_response.error:
181183
raise A2AClientJSONRPCError(json_rpc_response.error)
182184
response: StreamResponse = json_format.ParseDict(
183185
json_rpc_response.result, StreamResponse()
184186
)
185187
yield response
188+
except httpx.TimeoutException as e:
189+
raise A2AClientTimeoutError('Client Request timed out') from e
186190
except httpx.HTTPStatusError as e:
187191
raise A2AClientHTTPError(e.response.status_code, str(e)) from e
188192
except SSEError as e:
@@ -415,6 +419,8 @@ async def subscribe(
415419
json_rpc_response.result, StreamResponse()
416420
)
417421
yield response
422+
except httpx.TimeoutException as e:
423+
raise A2AClientTimeoutError('Client Request timed out') from e
418424
except SSEError as e:
419425
raise A2AClientHTTPError(
420426
400, f'Invalid SSE response or protocol error: {e}'

src/a2a/client/transports/rest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ async def send_message_streaming(
163163
async for sse in event_source.aiter_sse():
164164
event: StreamResponse = Parse(sse.data, StreamResponse())
165165
yield event
166+
except httpx.TimeoutException as e:
167+
raise A2AClientTimeoutError('Client Request timed out') from e
166168
except httpx.HTTPStatusError as e:
167169
raise A2AClientHTTPError(e.response.status_code, str(e)) from e
168170
except SSEError as e:
@@ -383,8 +385,12 @@ async def subscribe(
383385
) as event_source:
384386
try:
385387
async for sse in event_source.aiter_sse():
388+
if not sse.data:
389+
continue
386390
event: StreamResponse = Parse(sse.data, StreamResponse())
387391
yield event
392+
except httpx.TimeoutException as e:
393+
raise A2AClientTimeoutError('Client Request timed out') from e
388394
except SSEError as e:
389395
raise A2AClientHTTPError(
390396
400, f'Invalid SSE response or protocol error: {e}'

tests/client/test_errors.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,9 @@ class TestA2AClientJSONRPCErrorRepr:
170170

171171
def test_repr(self) -> None:
172172
"""Test that __repr__ shows the JSON-RPC error object."""
173-
response = JSONRPC20Response(
174-
_id='test-1',
175-
error={'code': -32601, 'message': 'Method not found', 'data': None},
173+
error = A2AClientJSONRPCError(
174+
{'code': -32601, 'message': 'Method not found', 'data': None}
176175
)
177-
error = A2AClientJSONRPCError(response.error)
178176
assert (
179177
repr(error)
180178
== "A2AClientJSONRPCError(\"JSON-RPC Error {'code': -32601, 'message': 'Method not found', 'data': None}\")"

tests/client/transports/test_jsonrpc_client.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,27 @@ async def test_send_message_streaming_request_error(
430430
async for _ in transport.send_message_streaming(request):
431431
pass
432432

433+
@pytest.mark.asyncio
434+
@patch('a2a.client.transports.jsonrpc.aconnect_sse')
435+
async def test_send_message_streaming_timeout(
436+
self,
437+
mock_aconnect_sse: AsyncMock,
438+
transport: JsonRpcTransport,
439+
):
440+
request = create_send_message_request()
441+
mock_event_source = AsyncMock()
442+
mock_event_source.response.raise_for_status = MagicMock()
443+
mock_event_source.aiter_sse = MagicMock(
444+
side_effect=httpx.TimeoutException('Timeout')
445+
)
446+
mock_aconnect_sse.return_value.__aenter__.return_value = (
447+
mock_event_source
448+
)
449+
450+
with pytest.raises(A2AClientTimeoutError, match='timed out'):
451+
async for _ in transport.send_message_streaming(request):
452+
pass
453+
433454

434455
class TestInterceptors:
435456
"""Tests for interceptor functionality."""

tests/client/transports/test_rest_client.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from httpx_sse import EventSource, ServerSentEvent
99

1010
from a2a.client import create_text_message_object
11-
from a2a.client.errors import A2AClientHTTPError
11+
from a2a.client.errors import A2AClientHTTPError, A2AClientTimeoutError
1212
from a2a.client.transports.rest import RestTransport
1313
from a2a.extensions.common import HTTP_EXTENSION_HEADER
1414
from a2a.types.a2a_pb2 import (
@@ -56,6 +56,40 @@ def _assert_extensions_header(mock_kwargs: dict, expected_extensions: set[str]):
5656
assert actual_extensions == expected_extensions
5757

5858

59+
class TestRestTransport:
60+
@pytest.mark.asyncio
61+
@patch('a2a.client.transports.rest.aconnect_sse')
62+
async def test_send_message_streaming_timeout(
63+
self,
64+
mock_aconnect_sse: AsyncMock,
65+
mock_httpx_client: AsyncMock,
66+
mock_agent_card: MagicMock,
67+
):
68+
client = RestTransport(
69+
httpx_client=mock_httpx_client, agent_card=mock_agent_card
70+
)
71+
params = SendMessageRequest(
72+
message=create_text_message_object(content='Hello stream')
73+
)
74+
mock_event_source = AsyncMock(spec=EventSource)
75+
mock_event_source.response = MagicMock(spec=httpx.Response)
76+
mock_event_source.response.raise_for_status.return_value = None
77+
mock_event_source.aiter_sse.side_effect = httpx.TimeoutException(
78+
'Read timed out'
79+
)
80+
mock_aconnect_sse.return_value.__aenter__.return_value = (
81+
mock_event_source
82+
)
83+
84+
with pytest.raises(A2AClientTimeoutError) as exc_info:
85+
_ = [
86+
item
87+
async for item in client.send_message_streaming(request=params)
88+
]
89+
90+
assert 'Client Request timed out' in str(exc_info.value)
91+
92+
5993
class TestRestTransportExtensions:
6094
@pytest.mark.asyncio
6195
async def test_send_message_with_default_extensions(

0 commit comments

Comments
 (0)