Skip to content

Commit 9baefef

Browse files
committed
Merge branch 'snake-case-field' of https://github.com/google-a2a/a2a-python into snake-case-field
2 parents 02db1a5 + 4944c4f commit 9baefef

File tree

6 files changed

+57
-4
lines changed

6 files changed

+57
-4
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## [0.2.15](https://github.com/a2aproject/a2a-python/compare/v0.2.14...v0.2.15) (2025-07-21)
4+
5+
6+
### Bug Fixes
7+
8+
* Add Input Validation for Empty Message Content ([#327](https://github.com/a2aproject/a2a-python/issues/327)) ([5061834](https://github.com/a2aproject/a2a-python/commit/5061834e112a4eb523ac505f9176fc42d86d8178))
9+
* Prevent import grpc issues for Client after making dependencies optional ([#330](https://github.com/a2aproject/a2a-python/issues/330)) ([53ad485](https://github.com/a2aproject/a2a-python/commit/53ad48530b47ef1cbd3f40d0432f9170b663839d)), closes [#326](https://github.com/a2aproject/a2a-python/issues/326)
10+
311
## [0.2.14](https://github.com/a2aproject/a2a-python/compare/v0.2.13...v0.2.14) (2025-07-18)
412

513

src/a2a/client/__init__.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Client-side components for interacting with an A2A agent."""
22

3+
import logging
4+
35
from a2a.client.auth import (
46
AuthInterceptor,
57
CredentialService,
@@ -12,11 +14,31 @@
1214
A2AClientJSONError,
1315
A2AClientTimeoutError,
1416
)
15-
from a2a.client.grpc_client import A2AGrpcClient
1617
from a2a.client.helpers import create_text_message_object
1718
from a2a.client.middleware import ClientCallContext, ClientCallInterceptor
1819

1920

21+
logger = logging.getLogger(__name__)
22+
23+
try:
24+
from a2a.client.grpc_client import A2AGrpcClient # type: ignore
25+
except ImportError as e:
26+
_original_error = e
27+
logger.debug(
28+
'A2AGrpcClient not loaded. This is expected if gRPC dependencies are not installed. Error: %s',
29+
_original_error,
30+
)
31+
32+
class A2AGrpcClient: # type: ignore
33+
"""Placeholder for A2AGrpcClient when dependencies are not installed."""
34+
35+
def __init__(self, *args, **kwargs):
36+
raise ImportError(
37+
'To use A2AGrpcClient, its dependencies must be installed. '
38+
'You can install them with \'pip install "a2a-sdk[grpc]"\''
39+
) from _original_error
40+
41+
2042
__all__ = [
2143
'A2ACardResolver',
2244
'A2AClient',

src/a2a/client/grpc_client.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22

33
from collections.abc import AsyncGenerator
44

5-
import grpc
5+
6+
try:
7+
import grpc
8+
except ImportError as e:
9+
raise ImportError(
10+
'A2AGrpcClient requires grpcio and grpcio-tools to be installed. '
11+
'Install with: '
12+
"'pip install a2a-sdk[grpc]'"
13+
) from e
14+
615

716
from a2a.grpc import a2a_pb2, a2a_pb2_grpc
817
from a2a.types import (

src/a2a/server/request_handlers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
_original_error = e
2424
logger.debug(
2525
'GrpcHandler not loaded. This is expected if gRPC dependencies are not installed. Error: %s',
26+
_original_error,
2627
)
2728

2829
class GrpcHandler: # type: ignore

src/a2a/utils/task.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import uuid
44

5-
from a2a.types import Artifact, Message, Task, TaskState, TaskStatus
5+
from a2a.types import Artifact, Message, Task, TaskState, TaskStatus, TextPart
66

77

88
def new_task(request: Message) -> Task:
@@ -18,12 +18,15 @@ def new_task(request: Message) -> Task:
1818
1919
Raises:
2020
TypeError: If the message role is None.
21-
ValueError: If the message parts are empty.
21+
ValueError: If the message parts are empty or if any part has empty content.
2222
"""
2323
if not request.role:
2424
raise TypeError('Message role cannot be None')
2525
if not request.parts:
2626
raise ValueError('Message parts cannot be empty')
27+
for part in request.parts:
28+
if isinstance(part.root, TextPart) and not part.root.text:
29+
raise ValueError('TextPart content cannot be empty')
2730

2831
return Task(
2932
status=TaskStatus(state=TaskState.submitted),

tests/utils/test_task.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ def test_new_task_invalid_message_empty_parts(self):
145145
)
146146
)
147147

148+
def test_new_task_invalid_message_empty_content(self):
149+
with self.assertRaises(ValueError):
150+
new_task(
151+
Message(
152+
role=Role.user,
153+
parts=[Part(root=TextPart(text=''))],
154+
messageId=str(uuid.uuid4()),
155+
)
156+
)
157+
148158
def test_new_task_invalid_message_none_role(self):
149159
with self.assertRaises(TypeError):
150160
msg = Message.model_construct(

0 commit comments

Comments
 (0)