RuntimeError when trying to mock httpx Response with async stream
See original GitHub issueWhen I try to mock a httpx Response with an async stream using respx, I get the following error:
RuntimeError: Attempted to call a sync iterator on an async stream.
I am running python 3.7.9, pytest 6.2.1, pytest-asyncio 0.14.0, httpx 0.16.1, respx 0.16.3
To replicate:
test_httpx.py
import httpx
import pytest
import respx
@respx.mock
@pytest.mark.asyncio
async def test_async_stream(tmpdir):
dl_url = 'http://MockNotRealURL/download/?token=IAmAToken'
img_headers = {
'Content-Type': 'image/tiff',
'Content-Length': '6',
'Content-Disposition': 'attachment; filename="img.tif"'
}
async def _stream_img():
yield b'bld'
yield b'dce'
mock_resp = httpx.Response(200, stream=_stream_img(), headers=img_headers)
respx.get(dl_url).return_value = mock_resp
filename = tmpdir / 'img.tif'
async with httpx.AsyncClient() as cl:
async with cl.stream('GET', dl_url) as resp:
with open(filename, 'wb') as fp:
async for chunk in resp.aiter_bytes():
fp.write(chunk)
Full traceback
> async with cl.stream('GET', dl_url) as resp:
test_httpx.py:28:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/httpx/_client.py:1835: in __aenter__
stream=True,
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/httpx/_client.py:1411: in send
history=[],
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/httpx/_client.py:1448: in _send_handling_auth
history=history,
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/httpx/_client.py:1476: in _send_handling_redirects
response = await self._send_single_request(request, timeout)
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/httpx/_client.py:1507: in _send_single_request
ext={"timeout": timeout.as_dict()},
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/respx/mocks.py:137: in amock
response = cls._send(request, instance=self, target_spec=spec, **kwargs)
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/respx/mocks.py:155: in _send
httpx_response = cls.handler(httpx_request)
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/respx/mocks.py:86: in handler
httpx_response = router.handler(httpx_request)
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/respx/router.py:270: in handler
response = self.resolve(request)
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/respx/router.py:243: in resolve
route, mock = self.match(request)
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/respx/router.py:231: in match
response = prospect.match(request)
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/respx/models.py:394: in match
result = self.resolve(request, **context)
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/respx/models.py:370: in resolve
result = clone_response(result, request)
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/respx/models.py:62: in clone_response
response.read()
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/httpx/_models.py:1162: in read
self._content = b"".join(self.iter_bytes())
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/httpx/_models.py:1175: in iter_bytes
for chunk in self.iter_raw():
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Response [200 OK]>
def iter_raw(self) -> typing.Iterator[bytes]:
"""
A byte-iterator over the raw response content.
"""
if self.is_stream_consumed:
raise StreamConsumed()
if self.is_closed:
raise ResponseClosed()
if not isinstance(self.stream, typing.Iterable):
> raise RuntimeError("Attempted to call a sync iterator on an async stream.")
E RuntimeError: Attempted to call a sync iterator on an async stream.
../../.pyenv/versions/3.7.9/envs/planet-client-python-3.7.9/lib/python3.7/site-packages/httpx/_models.py:1209: RuntimeError
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:6 (3 by maintainers)
Top Results From Across the Web
Advanced Usage - HTTPX
By default httpx will use "charset" information included in the response ... async def async_auth_flow(self, request): raise RuntimeError("Cannot use a sync ...
Read more >httpx/test_async_client.py at master - client - GitHub
async with httpx.AsyncClient() as client: async with client.stream("GET", server.url) as response: body = await response.aread().
Read more >pytest_httpx | pytest fixture to mock HTTPX - GitHub Pages
Once installed, httpx_mock pytest fixture will make sure every httpx request will be replied to with user provided responses. Add responses. JSON body;...
Read more >Newest 'httpx' Questions - Stack Overflow
I am new with async functions and i'm trying to make multiple calls from an external API. concurrent.Futures is not quite enough to...
Read more >Async Tests - FastAPI
If we want to call asynchronous functions in our tests, ... def test_root(): async with AsyncClient(app=app, base_url="http://test") as ac: response = await ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@lundberg that fixes it!
The issue is actually caused in
respx.models.clone_response
callingresponse.read()
when the stream is anAsyncIterator
. For an async stream,response.aread()
should be called.Ultimately, looking into
clone_response()
, it looks like it is callingresponse.read()
to populateresponse.content
. I’ve tried adjustingclone_response()
to populateresponse.content
directly, but it is causing some tests to fail so I’m not there yet…