h2non/pook

aiohttp mock only works when using request methods as context managers, but not when awaited

sarayourfriend opened this issue · 3 comments

Hello, thank you for the lovely project! It makes HTTP mocking really pleasant to work with.

I noticed a small issue with the implementation of the aiohttp mock: it appears to only work when using the request methods as context managers, but not when their results are awaited.

This gist has example code to show this in a pytest unit test: https://gist.github.com/sarayourfriend/44a33e7397849939156247457d8bb773

Here is the full output of running the tests:

➜  aiohttp-example pdm run pytest
/home/sara/.local/lib/python3.10/site-packages/requests/__init__.py:102: RequestsDependencyWarning: urllib3 (1.26.12) or chardet (5.0.0)/charset_normalizer (2.0.12) doesn't match a supported version!
  warnings.warn("urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported "
=================================== test session starts ===================================
platform linux -- Python 3.10.8, pytest-7.2.0, pluggy-1.0.0
rootdir: /home/sara/projects/aiohttp-example
plugins: anyio-3.6.2, asyncio-0.20.2
asyncio: mode=strict
collected 2 items                                                                         

main_test.py .F                                                                     [100%]

======================================== FAILURES =========================================
_________________________________ test_make_request_await _________________________________

    async def test_make_request_await():
        pook.head(URL).reply(200)
    
        pook.on()
>       res = await make_request_await()

main_test.py:36: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
main_test.py:19: in make_request_await
    response = await session.head(URL)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <aiohttp.client._RequestContextManager object at 0x7f7cb9dfc9d0>

    def __await__(self) -> Generator[Any, None, _RetType]:
>       ret = self._coro.__await__()
E       AttributeError: 'generator' object has no attribute '__await__'. Did you mean: '__init__'?

.venv/lib/python3.10/site-packages/aiohttp/client.py:1134: AttributeError
==================================== warnings summary =====================================
.venv/lib/python3.10/site-packages/pook/interceptors/aiohttp.py:48
  /home/sara/projects/aiohttp-example/.venv/lib/python3.10/site-packages/pook/interceptors/aiohttp.py:48: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
    def read(self, n=-1):

.venv/lib/python3.10/site-packages/pook/interceptors/aiohttp.py:80
  /home/sara/projects/aiohttp-example/.venv/lib/python3.10/site-packages/pook/interceptors/aiohttp.py:80: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
    def _on_request(self, _request, session, method, url,

main_test.py::test_make_request_ctx
main_test.py::test_make_request_await
  /home/sara/projects/aiohttp-example/.venv/lib/python3.10/site-packages/pook/interceptors/aiohttp.py:153: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
    def handler(session, method, url, data=None, headers=None, **kw):

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================= short test summary info =================================
FAILED main_test.py::test_make_request_await - AttributeError: 'generator' object has no attribute '__await__'. Did you mean: '__init...
========================= 1 failed, 1 passed, 4 warnings in 0.09s =========================

As you can see, test_make_request_await fails whereas the other test case which uses the request method as a context manger passes fine.

It looks like this comes down to an issue with the return value of the _request mock that pook is applying: it must be a coroutine but is currently a generator. I'm not sure exactly why this is the case, because the mock handler is wrapped in @asyncio.coroutine.

I've managed to patch this locally by removing the usage of the asyncio.coroutine decorator and replacing the methods with regular async def methods. I'll open a PR with these changes later this week.

I was reading through the implementation and realised it is backwards compatible even with 2.7. I'll try and see if it is fixable using asyncio.coroutine in order to maintain that backwards compatibility.

h2non commented

Hello! 2.7 compatibility is not an issue, the problem is 3.4 which does not support async/await syntax operators, but I'm willing to deprecate 3.4 at this point and only support 3.5+ versions, so feel free to send a PR.

Right on! I'll have a PR for you within the next couple of days.