|
1 | 1 | import json |
| 2 | + |
2 | 3 | from collections.abc import AsyncGenerator, Callable, Iterator |
3 | 4 | from contextlib import contextmanager |
4 | 5 | from typing import Any, NoReturn |
5 | 6 |
|
6 | 7 | import httpx |
| 8 | + |
7 | 9 | from httpx_sse import EventSource, SSEError |
8 | 10 |
|
9 | 11 | from a2a.client.client import ClientCallContext |
@@ -99,20 +101,14 @@ async def send_http_stream_request( |
99 | 101 |
|
100 | 102 |
|
101 | 103 | class _SSEEventSource: |
102 | | - """Class-based async context manager for SSE connections. |
103 | | -
|
104 | | - A drop-in replacement for ``httpx_sse.aconnect_sse`` that is safe to use |
105 | | - inside async generators. ``aconnect_sse`` uses ``@asynccontextmanager`` |
106 | | - which internally creates an async generator registered with the event |
107 | | - loop. When the enclosing async generator is abandoned, |
108 | | - ``shutdown_asyncgens`` tries to finalize both generators independently |
109 | | - and the nested ``athrow()`` calls collide with |
110 | | - ``RuntimeError: athrow(): asynchronous generator is already running``. |
111 | | -
|
112 | | - This class avoids the problem because ``__aenter__``/``__aexit__`` are |
113 | | - plain coroutines - no async generators are created, nothing is registered |
114 | | - with ``loop._asyncgens``, and ``shutdown_asyncgens`` has nothing to |
115 | | - collide with. |
| 104 | + """Class-based replacement for ``httpx_sse.aconnect_sse``. |
| 105 | +
|
| 106 | + ``aconnect_sse`` is an ``@asynccontextmanager`` whose internal async |
| 107 | + generator leaks into ``loop._asyncgens``. When the enclosing async |
| 108 | + generator is abandoned, ``shutdown_asyncgens`` collides with the |
| 109 | + cascading ``athrow()`` cleanup — see https://bugs.python.org/issue38559. |
| 110 | +
|
| 111 | + Plain ``__aenter__``/``__aexit__`` coroutines avoid this entirely. |
116 | 112 | """ |
117 | 113 |
|
118 | 114 | def __init__( |
|
0 commit comments