Skip to content

Question: how to set fixture and async function same loop? #983

Description

@Provinm

here is my code

test_something.py

@pytest_asyncio.fixture(scope="session")
async def setup_env():
    import asyncio

    loop1 = asyncio.get_running_loop()
    print(f"setup_env {loop1} - {id(loop1)}")
    
    yield 




async def test_endpoints(setup_env: t.Generator) -> None:
    import asyncio

    loop2 = asyncio.get_running_loop()
    print(f"test_endpoints {loop2} - {id(loop2)}")

and conftest.py

def pytest_collection_modifyitems(items: t.List[Item]) -> None:
    pytest_asyncio_tests = (item for item in items if is_async_test(item))
    session_scope_marker = pytest.mark.asyncio(loop_scope="session")
    for async_test in pytest_asyncio_tests:
        async_test.add_marker(session_scope_marker, append=False)

my question is how to make loop1 == loop2

Activity

  1. Provinm commented on Nov 11, 2024

    @Provinm
    Author

    solved, thx!

  2. scastlara commented on Nov 13, 2024

    @scastlara

    How did you solve it?

    EDIT: For me it was adding this to pyproject.toml

    [tool.pytest.ini_options]
    asyncio_default_fixture_loop_scope = "session"

    And following the docs and adding this to conftest.py: https://pytest-asyncio.readthedocs.io/en/latest/how-to-guides/run_session_tests_in_same_loop.html

    That way all the tests run on the same loop as the fixtures (I have session-scoped db fixture that runs migrations, and a function scoped db fixture that wraps everything in a transaction; so I needed everything to run on the same loop).

  3. Provinm commented on Nov 18, 2024

    @Provinm
    Author

    @scastlara

    change

    @pytest_asyncio.fixture(scope="session")
    async def setup_env():
        import asyncio
    
        loop1 = asyncio.get_running_loop()
        print(f"setup_env {loop1} - {id(loop1)}")
        
        yield 

    to

    @pytest_asyncio.fixture(loop_scope="session")
    async def setup_env():
        import asyncio
    
        loop1 = asyncio.get_running_loop()
        print(f"setup_env {loop1} - {id(loop1)}")
        
        yield 

    this makes loop1 == lop2

  4. nekeal commented on Nov 20, 2024

    @nekeal

    @scastlara, have you encountered any issues with this approach? I have a similar case:

    • A session-scoped fixture for AsyncEngine from SQLAlchemy
    • A function-scoped fixture for AsyncSession
    • Setting asyncio_default_fixture_loop_scope = "session"

    I'm curious if the approach you mentioned is the best way to handle this, or if there might be a better alternative.

  5. scastlara commented on Nov 20, 2024

    @scastlara

    @scastlara, have you encountered any issues with this approach? I have a similar case:

    • A session-scoped fixture for AsyncEngine from SQLAlchemy
    • A function-scoped fixture for AsyncSession
    • Setting asyncio_default_fixture_loop_scope = "session"

    I'm curious if the approach you mentioned is the best way to handle this, or if there might be a better alternative.

    Hey @nekeal ! No problems at all.

    • In pyproject.toml
    asyncio_default_fixture_loop_scope = "session"
    • In conftest.py
    def pytest_collection_modifyitems(items: Any) -> None:
        """
        Make all tests run on the same event loop.
    
        This is necessary because our db session is created on the event loop,
        and we need to ensure all tests run on the same event loop as the db!
        See: https://pytest-asyncio.readthedocs.io/en/latest/how-to-guides/run_session_tests_in_same_loop.html
        """
        pytest_asyncio_tests = (item for item in items if is_async_test(item))
        session_scope_marker = pytest.mark.asyncio(loop_scope="session")
        for async_test in pytest_asyncio_tests:
            async_test.add_marker(session_scope_marker, append=False)
    
    
    
    @pytest.fixture(scope="session", autouse=True)
    async def prepare_testsuite_db() -> AsyncIterator[None]:
        """Set up test database and engine."""
        engine = create_async_engine(get_database_url())
    
        async with engine.begin() as conn:
            await conn.run_sync(Base.metadata.drop_all)
            await conn.run_sync(Base.metadata.create_all)
            await conn.commit()
    
            yield
    
    # Then the func scope db fixtures providing a transaction for each test

    The testsuite is just as fast as before, no errors at all. Only thing lacking is being able to configure the loop scope of tests in pyproject.toml instead of having to do tricks with pytest hooks. But I think there was an open issue about this and the lib maintainer is fully aware. For now this is sufficient for me.

  6. nekeal commented on Nov 20, 2024

    @nekeal

    Thank you, @scastlara! I agree that the hook is not an ideal solution, but just to be thorough, I ran a small benchmark. For a test suite consisting of 1,100 synchronous and 820 asynchronous tests, it takes less than a millisecond. So that's a good enough solution 🎉

  7. seifertm commented on Dec 6, 2024

    @seifertm
    Contributor

    #793 aims to add a configuration option for the default loop scope of tests in favour of the hook.
    Hence, I'll convert this to a discussion :)

  8. locked and limited conversation to collaborators on Dec 6, 2024
  9. converted this issue into a discussion #1007 on Dec 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions