Skip to content

Commit 9fe1e26

Browse files
authored
Merge branch 'main' into feat_add_method_to_context_state
2 parents 82a3cc0 + ceeb4ef commit 9fe1e26

File tree

2 files changed

+125
-1
lines changed

2 files changed

+125
-1
lines changed

CHANGELOG.md

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

3+
## [0.3.5](https://github.com/a2aproject/a2a-python/compare/v0.3.4...v0.3.5) (2025-09-08)
4+
5+
6+
### Bug Fixes
7+
8+
* Prevent client disconnect from stopping task execution ([#440](https://github.com/a2aproject/a2a-python/issues/440)) ([58b4c81](https://github.com/a2aproject/a2a-python/commit/58b4c81746fc83e65f23f46308c47099697554ea)), closes [#296](https://github.com/a2aproject/a2a-python/issues/296)
9+
* **proto:** Adds metadata field to A2A DataPart proto ([#455](https://github.com/a2aproject/a2a-python/issues/455)) ([6d0ef59](https://github.com/a2aproject/a2a-python/commit/6d0ef593adaa22b2af0a5dd1a186646c180e3f8c))
10+
11+
12+
### Documentation
13+
14+
* add example docs for `[@validate](https://github.com/validate)` and `[@validate](https://github.com/validate)_async_generator` ([#422](https://github.com/a2aproject/a2a-python/issues/422)) ([18289eb](https://github.com/a2aproject/a2a-python/commit/18289eb19bbdaebe5e36e26be686e698f223160b))
15+
* Restructure README ([9758f78](https://github.com/a2aproject/a2a-python/commit/9758f7896c5497d6ca49f798296a7380b2134b29))
16+
317
## [0.3.4](https://github.com/a2aproject/a2a-python/compare/v0.3.3...v0.3.4) (2025-09-02)
418

519

src/a2a/utils/helpers.py

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,62 @@ def validate(
139139
and returns a boolean.
140140
error_message: An optional custom error message for the `UnsupportedOperationError`.
141141
If None, the string representation of the expression will be used.
142+
143+
Examples:
144+
Demonstrating with an async method:
145+
>>> import asyncio
146+
>>> from a2a.utils.errors import ServerError
147+
>>>
148+
>>> class MyAgent:
149+
... def __init__(self, streaming_enabled: bool):
150+
... self.streaming_enabled = streaming_enabled
151+
...
152+
... @validate(
153+
... lambda self: self.streaming_enabled,
154+
... 'Streaming is not enabled for this agent',
155+
... )
156+
... async def stream_response(self, message: str):
157+
... return f'Streaming: {message}'
158+
>>>
159+
>>> async def run_async_test():
160+
... # Successful call
161+
... agent_ok = MyAgent(streaming_enabled=True)
162+
... result = await agent_ok.stream_response('hello')
163+
... print(result)
164+
...
165+
... # Call that fails validation
166+
... agent_fail = MyAgent(streaming_enabled=False)
167+
... try:
168+
... await agent_fail.stream_response('world')
169+
... except ServerError as e:
170+
... print(e.error.message)
171+
>>>
172+
>>> asyncio.run(run_async_test())
173+
Streaming: hello
174+
Streaming is not enabled for this agent
175+
176+
Demonstrating with a sync method:
177+
>>> class SecureAgent:
178+
... def __init__(self):
179+
... self.auth_enabled = False
180+
...
181+
... @validate(
182+
... lambda self: self.auth_enabled,
183+
... 'Authentication must be enabled for this operation',
184+
... )
185+
... def secure_operation(self, data: str):
186+
... return f'Processing secure data: {data}'
187+
>>>
188+
>>> # Error case example
189+
>>> agent = SecureAgent()
190+
>>> try:
191+
... agent.secure_operation('secret')
192+
... except ServerError as e:
193+
... print(e.error.message)
194+
Authentication must be enabled for this operation
195+
196+
Note:
197+
This decorator works with both sync and async methods automatically.
142198
"""
143199

144200
def decorator(function: Callable) -> Callable:
@@ -174,7 +230,7 @@ def sync_wrapper(self: Any, *args, **kwargs) -> Any:
174230
def validate_async_generator(
175231
expression: Callable[[Any], bool], error_message: str | None = None
176232
):
177-
"""Decorator that validates if a given expression evaluates to True.
233+
"""Decorator that validates if a given expression evaluates to True for async generators.
178234
179235
Typically used on class methods to check capabilities or configuration
180236
before executing the method's logic. If the expression is False,
@@ -185,6 +241,60 @@ def validate_async_generator(
185241
and returns a boolean.
186242
error_message: An optional custom error message for the `UnsupportedOperationError`.
187243
If None, the string representation of the expression will be used.
244+
245+
Examples:
246+
Streaming capability validation with success case:
247+
>>> import asyncio
248+
>>> from a2a.utils.errors import ServerError
249+
>>>
250+
>>> class StreamingAgent:
251+
... def __init__(self, streaming_enabled: bool):
252+
... self.streaming_enabled = streaming_enabled
253+
...
254+
... @validate_async_generator(
255+
... lambda self: self.streaming_enabled,
256+
... 'Streaming is not supported by this agent',
257+
... )
258+
... async def stream_messages(self, count: int):
259+
... for i in range(count):
260+
... yield f'Message {i}'
261+
>>>
262+
>>> async def run_streaming_test():
263+
... # Successful streaming
264+
... agent = StreamingAgent(streaming_enabled=True)
265+
... async for msg in agent.stream_messages(2):
266+
... print(msg)
267+
>>>
268+
>>> asyncio.run(run_streaming_test())
269+
Message 0
270+
Message 1
271+
272+
Error case - validation fails:
273+
>>> class FeatureAgent:
274+
... def __init__(self):
275+
... self.features = {'real_time': False}
276+
...
277+
... @validate_async_generator(
278+
... lambda self: self.features.get('real_time', False),
279+
... 'Real-time feature must be enabled to stream updates',
280+
... )
281+
... async def real_time_updates(self):
282+
... yield 'This should not be yielded'
283+
>>>
284+
>>> async def run_error_test():
285+
... agent = FeatureAgent()
286+
... try:
287+
... async for _ in agent.real_time_updates():
288+
... pass
289+
... except ServerError as e:
290+
... print(e.error.message)
291+
>>>
292+
>>> asyncio.run(run_error_test())
293+
Real-time feature must be enabled to stream updates
294+
295+
Note:
296+
This decorator is specifically for async generator methods (async def with yield).
297+
The validation happens before the generator starts yielding values.
188298
"""
189299

190300
def decorator(function):

0 commit comments

Comments
 (0)