Httpx: Passing AsyncClient from an 'async with' context behaves unexpectedly

Created on 1 May 2020  路  3Comments  路  Source: encode/httpx

See https://stackoverflow.com/questions/61537570/how-do-i-make-parallel-async-http-requests-using-httpx-versus-aiohttp-in-pytho/61537571 for full explanation and context. It also includes a workaround.

Thanks so much for the effort creating this library! I really appreciate it.

Describe the bug

Passing an AsyncClient object as a parameter from inside a 'with' context causes a failure that results in the message "AttributeError: __aexit__"

To reproduce

import aiohttp
import asyncio
import time
import httpx

async def call_url(session):
    url = "http://example.com"        
    response = await session.request(method='GET', url=url)
    #response.raise_for_status() 
    return response

#async with aiohttp.ClientSession() as session: #use aiohttp
async with httpx.AsyncClient as session:  #use httpx
    await asyncio.gather(*[call_url(session) for x in range(i)])

Expected behavior

Context should follow the AsyncClient, as it does with aiohttp (and most other context managers I'm familiar with). For instance, the following doesn't error out, yet still closes the file appropriately.

def write_stuff(f):
    f.write('And stuff with context passed to another method. ')

with open('foo.txt',"w") as f:
    f.write('Start with context manager inside with statement. ')
    write_stuff(f)
    f.write('And back to close the with.')

Actual behavior

Exception is thrown, and code fails.

Workaround

Create and close the client manually. Replace the context manager above with explicit code:

    session = httpx.AsyncClient() #use httpx
    await asyncio.gather(*[call_url(session) for x in range(i)])
    await session.aclose()

Environment

  • OS: Windows 10
  • Python version: 3.7.7
  • HTTPX version: 0.12.1
  • Async environment: did not verify on trio. asyncio only.
  • HTTP proxy: no
  • Custom certificates: no, and http fails just like https.

Additional context

See https://stackoverflow.com/questions/61537570/how-do-i-make-parallel-async-http-requests-using-httpx-versus-aiohttp-in-pytho/61537571 for a full description.

Once again, absolutely HUGE thanks for this library! I'm so glad I found it.

question

Most helpful comment

Reproduced this locally, and confirmed that it's just missing the parentheses, yup. Thanks @florimondmanca!

Fails...

    async with httpx.AsyncClient as session:  #use httpx
        await asyncio.gather(*[call_url(session) for x in [0,1,2]])
Traceback (most recent call last):
  File "example.py", line 18, in <module>
    asyncio.run(main())
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 583, in run_until_complete
    return future.result()
  File "example.py", line 14, in main
    async with httpx.AsyncClient as session:  #use httpx
AttributeError: __aexit__

Passes...

    async with httpx.AsyncClient() as session:  #use httpx
        await asyncio.gather(*[call_url(session) for x in [0,1,2]])

All 3 comments

Hi,

In the HTTPX branch of the repro example pure missing parentheses on AsyncClient() - is that where the missing __aexit__ error comes from?

Reproduced this locally, and confirmed that it's just missing the parentheses, yup. Thanks @florimondmanca!

Fails...

    async with httpx.AsyncClient as session:  #use httpx
        await asyncio.gather(*[call_url(session) for x in [0,1,2]])
Traceback (most recent call last):
  File "example.py", line 18, in <module>
    asyncio.run(main())
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 583, in run_until_complete
    return future.result()
  File "example.py", line 14, in main
    async with httpx.AsyncClient as session:  #use httpx
AttributeError: __aexit__

Passes...

    async with httpx.AsyncClient() as session:  #use httpx
        await asyncio.gather(*[call_url(session) for x in [0,1,2]])

I am SO sorry! Thank you @florimondmanca for catching that. So, so embarrassing...

Was this page helpful?
0 / 5 - 0 ratings