From 2f86537195c3e26ce405910be9cfa9499c2840bc Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Sun, 10 Aug 2025 16:15:50 +0000 Subject: [PATCH 1/5] Correctly install `uv` with the requested Python version, otherwise all tests run with the stock Python. --- .github/workflows/unit-tests.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 5e3ffefd..99e092bc 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -40,17 +40,15 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - name: Set up test environment variables run: | echo "POSTGRES_TEST_DSN=postgresql+asyncpg://a2a:a2a_password@localhost:5432/a2a_test" >> $GITHUB_ENV echo "MYSQL_TEST_DSN=mysql+aiomysql://a2a:a2a_password@localhost:3306/a2a_test" >> $GITHUB_ENV - - name: Install uv + - name: Install uv for Python ${{ matrix.python-version }} uses: astral-sh/setup-uv@v6 + with: + python-version: ${{ matrix.python-version }} - name: Add uv to PATH run: | echo "$HOME/.cargo/bin" >> $GITHUB_PATH From 05f796b21facafcb1a774bf19ef64b87924c9ebc Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Sun, 10 Aug 2025 16:22:05 +0000 Subject: [PATCH 2/5] Since Python 3.11 and [bpo-40066](https://github.com/python/cpython/issues/84247), Enum.__str__ shows the enum name. Adjust the error message to only show the string value. --- src/a2a/server/request_handlers/default_request_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/a2a/server/request_handlers/default_request_handler.py b/src/a2a/server/request_handlers/default_request_handler.py index 2549d087..1dfce760 100644 --- a/src/a2a/server/request_handlers/default_request_handler.py +++ b/src/a2a/server/request_handlers/default_request_handler.py @@ -195,7 +195,7 @@ async def _setup_message_execution( if task.status.state in TERMINAL_TASK_STATES: raise ServerError( error=InvalidParamsError( - message=f'Task {task.id} is in terminal state: {task.status.state}' + message=f'Task {task.id} is in terminal state: {task.status.state.value}' ) ) @@ -437,7 +437,7 @@ async def on_resubscribe_to_task( if task.status.state in TERMINAL_TASK_STATES: raise ServerError( error=InvalidParamsError( - message=f'Task {task.id} is in terminal state: {task.status.state}' + message=f'Task {task.id} is in terminal state: {task.status.state.value}' ) ) From 6a364f29754712ad37ef7b905787134de3bad63e Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Sun, 10 Aug 2025 16:31:19 +0000 Subject: [PATCH 3/5] Adjust error message that changed in Python 3.12. https://github.com/python/cpython/issues/98284 https://github.com/python/cpython/issues/91578 --- tests/server/apps/jsonrpc/test_jsonrpc_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/server/apps/jsonrpc/test_jsonrpc_app.py b/tests/server/apps/jsonrpc/test_jsonrpc_app.py index e0cadab6..6b86e7e7 100644 --- a/tests/server/apps/jsonrpc/test_jsonrpc_app.py +++ b/tests/server/apps/jsonrpc/test_jsonrpc_app.py @@ -92,7 +92,7 @@ def test_jsonrpc_app_build_method_abstract_raises_typeerror( # This will fail at definition time if an abstract method is not implemented with pytest.raises( TypeError, - match="Can't instantiate abstract class IncompleteJSONRPCApp with abstract method build", + match=".*abstract class IncompleteJSONRPCApp .* abstract method '?build'?", ): class IncompleteJSONRPCApp(JSONRPCApplication): From 519b905bae8ed3fc7024affa73f1ea0e8514a19e Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Sun, 10 Aug 2025 16:33:23 +0000 Subject: [PATCH 4/5] Since Python 3.13, shutdown queues raise a `QueueShutDown` exception. https://github.com/python/cpython/issues/96471 --- tests/server/events/test_event_queue.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/server/events/test_event_queue.py b/tests/server/events/test_event_queue.py index ffe74877..3d265601 100644 --- a/tests/server/events/test_event_queue.py +++ b/tests/server/events/test_event_queue.py @@ -1,4 +1,5 @@ import asyncio +import sys from typing import Any from unittest.mock import ( @@ -203,7 +204,11 @@ async def test_dequeue_event_closed_and_empty_no_wait( await event_queue.close() assert event_queue.is_closed() # Ensure queue is actually empty (e.g. by trying a non-blocking get on internal queue) - with pytest.raises(asyncio.QueueEmpty): + if sys.version_info < (3, 13): + expected = asyncio.QueueEmpty + else: + expected = asyncio.QueueShutDown + with pytest.raises(expected): event_queue.queue.get_nowait() with pytest.raises(asyncio.QueueEmpty, match='Queue is closed.'): @@ -217,9 +222,11 @@ async def test_dequeue_event_closed_and_empty_waits_then_raises( """Test dequeue_event raises QueueEmpty eventually when closed, empty, and no_wait=False.""" await event_queue.close() assert event_queue.is_closed() - with pytest.raises( - asyncio.QueueEmpty - ): # Should still raise QueueEmpty as per current implementation + if sys.version_info < (3, 13): + expected = asyncio.QueueEmpty + else: + expected = asyncio.QueueShutDown + with pytest.raises(expected): event_queue.queue.get_nowait() # verify internal queue is empty # This test is tricky because await event_queue.dequeue_event() would hang if not for the close check. From 51a6847894f18108f3ff736bf91ea371316779fc Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Sun, 10 Aug 2025 20:59:38 +0000 Subject: [PATCH 5/5] define fixture expected_queue_closed_exception as suggested by Gemini --- tests/server/events/test_event_queue.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/server/events/test_event_queue.py b/tests/server/events/test_event_queue.py index 3d265601..ecb7d814 100644 --- a/tests/server/events/test_event_queue.py +++ b/tests/server/events/test_event_queue.py @@ -196,19 +196,22 @@ async def test_enqueue_event_when_closed(event_queue: EventQueue) -> None: await child_queue.dequeue_event(no_wait=True) +@pytest.fixture +def expected_queue_closed_exception(): + if sys.version_info < (3, 13): + return asyncio.QueueEmpty + return asyncio.QueueShutDown + + @pytest.mark.asyncio async def test_dequeue_event_closed_and_empty_no_wait( - event_queue: EventQueue, + event_queue: EventQueue, expected_queue_closed_exception ) -> None: """Test dequeue_event raises QueueEmpty when closed, empty, and no_wait=True.""" await event_queue.close() assert event_queue.is_closed() # Ensure queue is actually empty (e.g. by trying a non-blocking get on internal queue) - if sys.version_info < (3, 13): - expected = asyncio.QueueEmpty - else: - expected = asyncio.QueueShutDown - with pytest.raises(expected): + with pytest.raises(expected_queue_closed_exception): event_queue.queue.get_nowait() with pytest.raises(asyncio.QueueEmpty, match='Queue is closed.'): @@ -217,16 +220,12 @@ async def test_dequeue_event_closed_and_empty_no_wait( @pytest.mark.asyncio async def test_dequeue_event_closed_and_empty_waits_then_raises( - event_queue: EventQueue, + event_queue: EventQueue, expected_queue_closed_exception ) -> None: """Test dequeue_event raises QueueEmpty eventually when closed, empty, and no_wait=False.""" await event_queue.close() assert event_queue.is_closed() - if sys.version_info < (3, 13): - expected = asyncio.QueueEmpty - else: - expected = asyncio.QueueShutDown - with pytest.raises(expected): + with pytest.raises(expected_queue_closed_exception): event_queue.queue.get_nowait() # verify internal queue is empty # This test is tricky because await event_queue.dequeue_event() would hang if not for the close check.