From c23204c9c3f5de6562d3702467f4ceda2f5c5108 Mon Sep 17 00:00:00 2001 From: Carlos Chinchilla Corbacho <188046461+cchinchilla-dev@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:28:50 +0200 Subject: [PATCH] fix(core): restore structured __repr__ on A2A exceptions --- src/a2a/client/errors.py | 8 +++++++ src/a2a/utils/errors.py | 8 +++++++ tests/client/test_errors.py | 37 +++++++++++++++++++++++++++- tests/utils/test_a2a_errors.py | 44 ++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 tests/utils/test_a2a_errors.py diff --git a/src/a2a/client/errors.py b/src/a2a/client/errors.py index 4d3802d11..853ff597f 100644 --- a/src/a2a/client/errors.py +++ b/src/a2a/client/errors.py @@ -14,6 +14,14 @@ def __init__(self, message: str, status_code: int | None = None) -> None: super().__init__(message) self.status_code = status_code + def __repr__(self) -> str: + """Return a developer-friendly representation exposing the status code.""" + return ( + f'{self.__class__.__name__}(' + f'status_code={self.status_code!r}, ' + f'message={self.message!r})' + ) + class A2AClientTimeoutError(A2AClientError): """Exception for timeout errors during a request.""" diff --git a/src/a2a/utils/errors.py b/src/a2a/utils/errors.py index b55f47070..18aa0f58b 100644 --- a/src/a2a/utils/errors.py +++ b/src/a2a/utils/errors.py @@ -27,6 +27,14 @@ def __init__(self, message: str | None = None, data: dict | None = None): self.data = data super().__init__(self.message) + def __repr__(self) -> str: + """Return a developer-friendly representation exposing message and data.""" + return ( + f'{self.__class__.__name__}(' + f'message={self.message!r}, ' + f'data={self.data!r})' + ) + class TaskNotFoundError(A2AError): """Exception raised when a task is not found.""" diff --git a/tests/client/test_errors.py b/tests/client/test_errors.py index 1ee7ab10a..280f1c293 100644 --- a/tests/client/test_errors.py +++ b/tests/client/test_errors.py @@ -1,6 +1,10 @@ import pytest -from a2a.client import A2AClientError +from a2a.client import ( + A2AClientError, + A2AClientTimeoutError, + AgentCardResolutionError, +) class TestA2AClientError: @@ -23,3 +27,34 @@ def test_raising_base_error(self) -> None: raise A2AClientError('Generic client error') assert str(excinfo.value) == 'Generic client error' + + +class TestClientErrorRepr: + """Test cases for the __repr__ of client exception classes.""" + + @pytest.mark.parametrize( + ('error', 'expected'), + [ + ( + A2AClientError('Network communication error'), + "A2AClientError(message='Network communication error', data=None)", + ), + ( + A2AClientTimeoutError('Client Request timed out'), + "A2AClientTimeoutError(message='Client Request timed out', data=None)", + ), + ( + AgentCardResolutionError('Failed to fetch agent card', 404), + 'AgentCardResolutionError(status_code=404, ' + "message='Failed to fetch agent card')", + ), + ( + AgentCardResolutionError('Invalid agent card JSON'), + 'AgentCardResolutionError(status_code=None, ' + "message='Invalid agent card JSON')", + ), + ], + ) + def test_repr(self, error: A2AClientError, expected: str) -> None: + """Test that repr exposes structured attributes as keyword arguments.""" + assert repr(error) == expected diff --git a/tests/utils/test_a2a_errors.py b/tests/utils/test_a2a_errors.py new file mode 100644 index 000000000..ea7133add --- /dev/null +++ b/tests/utils/test_a2a_errors.py @@ -0,0 +1,44 @@ +import pytest + +from a2a.utils.errors import ( + A2AError, + InvalidParamsError, + TaskNotFoundError, + VersionNotSupportedError, +) + + +class TestA2AErrorRepr: + """Test cases for the __repr__ of A2AError and its subclasses.""" + + @pytest.mark.parametrize( + ('error', 'expected'), + [ + (A2AError(), "A2AError(message='A2A Error', data=None)"), + ( + TaskNotFoundError(), + "TaskNotFoundError(message='Task not found', data=None)", + ), + ( + TaskNotFoundError('Task abc not found'), + "TaskNotFoundError(message='Task abc not found', data=None)", + ), + ( + InvalidParamsError('Invalid field', data={'field': 'id'}), + "InvalidParamsError(message='Invalid field', data={'field': 'id'})", + ), + ( + VersionNotSupportedError(data={'supported': ['1.0']}), + "VersionNotSupportedError(message='Version not supported', " + "data={'supported': ['1.0']})", + ), + ], + ) + def test_repr(self, error: A2AError, expected: str) -> None: + """Test that repr exposes message and data as keyword arguments.""" + assert repr(error) == expected + + def test_str_unchanged(self) -> None: + """Test that __str__ still returns only the message.""" + error = InvalidParamsError('Invalid field', data={'field': 'id'}) + assert str(error) == 'Invalid field'