Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

record creation failed when using async #817

Open
finswimmer opened this issue Jan 31, 2024 · 3 comments
Open

record creation failed when using async #817

finswimmer opened this issue Jan 31, 2024 · 3 comments

Comments

@finswimmer
Copy link

finswimmer commented Jan 31, 2024

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.

@JSv4
Copy link

JSv4 commented Feb 21, 2024

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.

@dbrewster
Copy link

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.

@finswimmer
Copy link
Author

Hey,

just a gently reminder about this issue 😃

fin swimmer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants