record creation failed when using async
finswimmer opened this issue · comments
Hello,
this one took me a while to find out what's going on and how to reproduce.
To reproduce this I'm using:
- pytest: 7.4.4
- pytest-asyncio: 0.23.4
- pytest-recording: 0.13.1
- httpx: 0.26.0
The following test works if vcrpy
in version 5.1.0
is installed as a dependency for pytest-recording
:
import httpx
import pytest
from httpx import Response
async def get() -> Response:
client = httpx.Client(base_url="https://jsonplaceholder.typicode.com")
result = client.get("/todos/1")
return result
@pytest.mark.asyncio()
@pytest.mark.vcr(record_mode="once")
async def test_get() -> None:
response = await get()
assert response.status_code == 200
But it will fail if version 6.0.1
of vcrpy
is used instead.
pytest tests
================================================================================================================ test session starts =================================================================================================================
platform linux -- Python 3.8.10, pytest-7.4.4, pluggy-1.4.0
rootdir: /home/finswimmer/tmp/vcrpbug
plugins: recording-0.13.1, anyio-4.2.0, asyncio-0.23.4
asyncio: mode=strict
collected 1 item
tests/test_minimal.py F [100%]
====================================================================================================================== FAILURES ======================================================================================================================
______________________________________________________________________________________________________________________ test_get ______________________________________________________________________________________________________________________
@pytest.mark.asyncio()
@pytest.mark.vcr(record_mode="once")
async def test_get() -> None:
> response = await get()
tests/test_minimal.py:16:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/test_minimal.py:8: in get
result = client.get("/todos/1")
.venv/lib/python3.8/site-packages/httpx/_client.py:1055: in get
return self.request(
.venv/lib/python3.8/site-packages/httpx/_client.py:828: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
.venv/lib/python3.8/site-packages/httpx/_client.py:915: in send
response = self._send_handling_auth(
.venv/lib/python3.8/site-packages/httpx/_client.py:943: in _send_handling_auth
response = self._send_handling_redirects(
.venv/lib/python3.8/site-packages/httpx/_client.py:980: in _send_handling_redirects
response = self._send_single_request(request)
.venv/lib/python3.8/site-packages/vcr/stubs/httpx_stubs.py:184: in _inner_send
return _sync_vcr_send(cassette, real_send, *args, **kwargs)
.venv/lib/python3.8/site-packages/vcr/stubs/httpx_stubs.py:177: in _sync_vcr_send
asyncio.run(_record_responses(cassette, vcr_request, real_response, aread=False))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
main = <coroutine object _record_responses at 0x7ffb9e25a6c0>
def run(main, *, debug=None):
"""Execute the coroutine and return the result.
This function runs the passed coroutine, taking care of
managing the asyncio event loop and finalizing asynchronous
generators.
This function cannot be called when another asyncio event loop is
running in the same thread.
If debug is True, the event loop will be run in debug mode.
This function always creates a new event loop and closes it at the end.
It should be used as a main entry point for asyncio programs, and should
ideally only be called once.
Example:
async def main():
await asyncio.sleep(1)
print('hello')
asyncio.run(main())
"""
if events._get_running_loop() is not None:
> raise RuntimeError(
"asyncio.run() cannot be called from a running event loop")
E RuntimeError: asyncio.run() cannot be called from a running event loop
/usr/lib/python3.8/asyncio/runners.py:33: RuntimeError
============================================================================================================== short test summary info ===============================================================================================================
FAILED tests/test_minimal.py::test_get - RuntimeError: asyncio.run() cannot be called from a running event loop
================================================================================================================= 1 failed in 0.46s ==================================================================================================================
sys:1: RuntimeWarning: coroutine '_record_responses' was never awaited
The error only occurs if there is no record already. Once there is one (e.g. created by an older version) the replay works without issues.
I had a very similar issue attempting to use VCR to record the https requests from the OpenAI API client in an async function. The same fix worked for me - downgraded to 5.1.0
, recorded the https calls, and then bumped back to 6.0.1
. Let me know if there specific information I can provide here.
So I think I've figured out what exactly is going on.
If you use the httpx AsyncClient from an async method where the client is created in the call chain (arbitrarily deep) then it works just fine.
If you use the non-async httpx client from a sync method call chain it works fine.
If you use the sync httpx client from an async method, it fails.
This all makes sense and likely the code just needs to check if it is currently running in an event loop and do the right thing accordingly.