Commit 2e45c0d
feat: add async context manager support to ClientTransport (#682)
# Description
Thank you for opening a Pull Request!
Before submitting your PR, there are a few things you can do to make
sure it goes smoothly:
- [x] Follow the [`CONTRIBUTING`
Guide](https://github.com/a2aproject/a2a-python/blob/main/CONTRIBUTING.md).
- [x] Make your Pull Request title in the
<https://www.conventionalcommits.org/> specification.
- Important Prefixes for
[release-please](https://github.com/googleapis/release-please):
- `fix:` which represents bug fixes, and correlates to a
[SemVer](https://semver.org/) patch.
- `feat:` represents a new feature, and correlates to a SemVer minor.
- `feat!:`, or `fix!:`, `refactor!:`, etc., which represent a breaking
change (indicated by the `!`) and will result in a SemVer major.
- [x] Ensure the tests and linter pass (Run `bash scripts/format.sh`
from the repository root to format)
- [x] Appropriate docs were updated (if necessary)
Fixes #674 🦕
## Problem
`ClientTransport` defines an abstract `close()` method but does not
implement `__aenter__`/`__aexit__`. This means transports cannot be used
with `async with`, which is the idiomatic Python pattern for managing
async resources. If an exception occurs between creating a transport and
calling `close()`, the underlying connection (e.g., gRPC channel, httpx
client) is never cleaned up:
```python
transport = GrpcTransport(channel=channel, agent_card=agent_card)
result = await transport.send_message(params) # if this raises, close() is never called
await transport.close()
```
## Fix
Added `__aenter__` and `__aexit__` methods to `ClientTransport` in
`src/a2a/client/transports/base.py`:
`__aenter__` returns `self`.
`__aexit__` awaits `close()`.
This enables the standard async context manager pattern on all transport
implementations (`GrpcTransport`, `RestTransport`, `JsonRpcTransport`):
```python
async with GrpcTransport(channel=channel, agent_card=agent_card) as transport:
result = await transport.send_message(params)
# close() called automatically, even on exceptions
```
This is a non-breaking, additive change. Calling `close()` manually or
via `try/finally` continues to work exactly as before.
## Test
Tests were added to `tests/client/test_base_client.py` since it already
imports and mocks `ClientTransport`. Happy to move them to a dedicated
file if maintainers prefer.
## Note
As mentioned in #674, the same pattern could also be applied to
`BaseClient`, which wraps `ClientTransport` and also exposes a `close()`
method. I've kept this PR scoped to `ClientTransport` only. Happy to
extend it to `BaseClient` in this same PR or a follow-up if maintainers
prefer.
Release-As: 0.3.23
---------
Co-authored-by: Ivan Shymko <vana.shimko@gmail.com>1 parent c91d4fb commit 2e45c0d
2 files changed
Lines changed: 43 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
3 | 6 | | |
4 | 7 | | |
5 | 8 | | |
| |||
19 | 22 | | |
20 | 23 | | |
21 | 24 | | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
22 | 38 | | |
23 | 39 | | |
24 | 40 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
64 | 90 | | |
65 | 91 | | |
66 | 92 | | |
| |||
0 commit comments