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

0.19.0: Fixture misses attribute #390

Closed
viralmutant opened this issue Jul 22, 2022 · 12 comments
Closed

0.19.0: Fixture misses attribute #390

viralmutant opened this issue Jul 22, 2022 · 12 comments

Comments

@viralmutant
Copy link

I am working with python3.10 and pytest=7.1.2 version

It seems with the latest version of pytest-asyncio=0.19.0 a regression has been introduced and this bug is back.

Using the same example testcase to reproduce the issue, I observe the same failure

AttributeError: 'async_generator' object has no attribute 'put'

Downgrading pytest-asyncio to version 0.18.3 and the error is gone.

@davidandreoletti
Copy link

davidandreoletti commented Jul 22, 2022

@viralmutant

pytest-asyncio=0.19.0 enforces strict mode. Projects must update their fixture markers as described.

Env: py38 + purest=7.1.2 +pytest-asyncio=0.19.0

@graingert
Copy link
Member

@viralmutant please show the output of the code when you run on python3.10 and pytest-asyncio==0.18.3

@viralmutant
Copy link
Author

viralmutant commented Jul 22, 2022

@viralmutant please show the output of the code when you run on python3.10 and pytest-asyncio==0.18.3

Here is the output with -s

plugins: forked-1.4.0, cov-3.0.0, asyncio-0.18.3, env-0.6.2, sugar-0.9.5, requests-mock-1.9.3, pyfakefs-4.5.0, xdist-2.5.0
asyncio: mode=legacy
collecting ...
 test_bug.py/CodeBox/turbo-test/lib/python3.10/site-packages/pytest_asyncio/plugin.py:191: DeprecationWarning: The 'asyncio_mode' default value will change to 'strict' in future, please explicitly use 'asyncio_mode=strict' or 'asyncio_mode=auto' in pytest configuration file.
    config.issue_config_time_warning(LEGACY_MODE, stacklevel=2)

test_bug.py:12
  
/CodeBox/turbo/test_bug.py:12: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
    def release(self):

../turbo-test/lib/python3.10/site-packages/pytest_asyncio/plugin.py:230
  .../CodeBox/turbo-test/lib/python3.10/site-packages/pytest_asyncio/plugin.py:230: DeprecationWarning: '@pytest.fixture' is applied to <fixture fake_session, file=/CodeBox/turbo/test_bug.py, line=16> in 'legacy' mode, please replace it with '@pytest_asyncio.fixture' as a preparation for switching to 'strict' mode (or use 'auto' mode to seamlessly handle all these fixtures as asyncio-driven).
    warnings.warn(

test_bug.py::test_bug
  /CodeBox/turbo/test_bug.py:20: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
    def _fake_request(method, url, *args, **kwargs):

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html

Results (0.84s):
       1 passed

@seifertm
Copy link
Contributor

Your test output shows a deprecation warning about asyncio-mode. Starting with pytest-asyncio v0.19 asyncio mode defaults to strict. The error you're seeing is likely caused by wrong fixture decorators. The reproducible example you linked suffers from that exact problem.

As @davidandreoletti already mentioned you need to update your fixture definitions if you intend to use strict mode. Strict mode will no longer evaluate async fixtures decorated witih @pytest.fixture. You need to use @pytest_asyncio.fixture.

@viralmutant Please check your fixture definitions and let us know if this fixes your issue.

@viralmutant
Copy link
Author

Yes, after replacing the fixture as @pytest_asyncio.fixture I didn't observe the issue with v0.19
Thanks

The confusing part is that once the version has been upgraded, it just fails and won't tell why. And while being at the earlier version, when a couple of thousand tests are running in CI pipeline, the warnings are ignored

@kiddten
Copy link

kiddten commented Aug 17, 2022

Hi @seifertm

I am trying to use async_generator with fixture in factory mode:

@pytest_asyncio.fixture
async def async_client_factory():
    async def _factory(x):
        print(x) # just to simplify case, we need x as parameter for our factory
        async with AsyncClient() as ac:
            yield ac

    return _factory

@pytest.mark.asyncio
async def test_async_generator_factory(async_client_factory):
    async_client = async_client_factory(1)
    response = await async_client.get("/test")
    assert response.status_code == status.HTTP_200_OK

But I get same error:

>       response = await async_client.get("/test")
E       AttributeError: 'async_generator' object has no attribute 'get'

package versions:

pytest-asyncio = "0.19.0"
httpx = "^0.23.0"
pytest = "^7.1.2"

@kiddten
Copy link

kiddten commented Aug 17, 2022

just a fixture w/o factory works well

@pytest_asyncio.fixture
async def async_client():
    async with AsyncClient() as ac:
        yield ac

@pytest.mark.asyncio
async def test_async_generator(async_client):
    response = await async_client.get("/test")
    assert response.status_code == status.HTTP_200_OK

@seifertm
Copy link
Contributor

Interesting, thanks for the report. I'll look into it tomorrow.

@graingert
Copy link
Member

graingert commented Aug 17, 2022

You can't use a yield inside an httpx.AsyncClient like that, you need the @contextlib.asynccontextmanager

See https://discuss.python.org/t/preventing-yield-inside-certain-context-managers/1091

@kiddten
Copy link

kiddten commented Aug 18, 2022

@graingert it does not help in case with factory

@pytest_asyncio.fixture
async def async_client_factory():
    @asynccontextmanager
    async def _factory(x):
        print(x) # just to simplify case, we need x as parameter for our factory
        async with AsyncClient() as ac:
            yield ac

    return _factory

@pytest.mark.asyncio
async def test_async_generator_factory(async_client_factory):
    async_client = async_client_factory(1)
    response = await async_client.get("/test")
    assert response.status_code == status.HTTP_200_OK

similar error

>       response = await async_client.get("/test")
E       AttributeError: '_AsyncGeneratorContextManager' object has no attribute 'get'

although it works with fixture w/o factory

@asynccontextmanager
@pytest_asyncio.fixture
async def async_client():
    async with AsyncClient() as ac:
        yield ac

@pytest.mark.asyncio
async def test_async_generator(async_client):
    response = await async_client.get("/test")
    assert response.status_code == status.HTTP_200_OK

seems something related to pytest internals

@graingert
Copy link
Member

graingert commented Aug 18, 2022

You need to use

async with async_client_factory(1) as async_client:
    ...

@seifertm
Copy link
Contributor

@kiddick I cannot see anything wrong with the behaviour of pytest-asyncio in the example you provided here. Your fixture returns the _factory async context manager. As Thomas mentioned you need to open the context manager using asnyc with …, in order to access the yielded AsyncClient.

From your reaction on the previous comment I assume the issue is resolved for you.

I initially left this issue open, because I intended to add a warning when people forgot to update their fixtures. I don't think there's a reliable way to do it, though, so I'll close this issue.

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

5 participants