I ran into this issue a little while ago. I tried storing the aiohttp.ClientSession
in the init function, but whenever I tried to use it, I got RuntimeError: Timeout context manager should be used inside a task
.
This is OK, but the trick is to do it right. The aiohttp.ClientSession
must be initialized inside a task, meaning an async function. The init function is not async, however it is still OK, as long as any parent function is async. Let me clarify with some code snippets.
import aiohttp
import asyncio
class MyClass:
def __init__(self) -> None:
self._session = aiohttp.ClientSession()
async def make_http_get_request(self, url: str) -> str:
async with self._session.request("GET", url) as response:
response.raise_for_status()
return await response.text()
def main() -> None:
my_class = MyClass()
html = asyncio.run(my_class.make_http_get_request("http://github.com/"))
print(html)
if __name__ == "__main__":
main()
This code will result in a RuntimeError: Timeout context manager should be used inside a task
because the MyClass
, and it's aiohttp.ClientSession
is initialized outside of a task. The correct way would be:
import aiohttp
import asyncio
class MyClass:
def __init__(self) -> None:
self._session = aiohttp.ClientSession()
async def make_http_get_request(self, url: str) -> str:
async with self._session.request("GET", url) as response:
response.raise_for_status()
return await response.text()
async def main() -> None:
my_class = MyClass() # This is now being initilized inside a task (async function)
html = await my_class.make_http_get_request("http://github.com/")
print(html)
if __name__ == "__main__":
asyncio.run(main())
Of course, you will still get some warnings like Unclosed client session
and Unclosed connector
with code like this, because you are not closing your resources properly. Let's write better code to take care of that:
import aiohttp
import asyncio
class MyClass:
def __init__(self) -> None:
self._session = aiohttp.ClientSession()
async def close(self) -> None:
await self._session.close()
async def make_http_get_request(self, url: str) -> str:
async with self._session.request("GET", url) as response:
response.raise_for_status()
return await response.text()
async def main() -> None:
my_class = MyClass()
html = await my_class.make_http_get_request("http://github.com/")
print(html)
await my_class.close()
if __name__ == "__main__":
asyncio.run(main())
However, this would still leave us with these pesky warnings if we for some reason crash, due to some unforeseen exception. We can make this better yet, using context managers:
import aiohttp
import asyncio
from traceback import TracebackException
from types import TracebackType
class MyClass:
def __init__(self) -> None:
self._session = aiohttp.ClientSession()
async def __aenter__(self) -> "MyClass":
return self
async def __aexit__(
self,
exc_type: Exception,
exc_val: TracebackException,
traceback: TracebackType,
) -> None:
await self.close()
async def close(self) -> None:
await self._session.close()
async def make_http_get_request(self, url: str) -> str:
async with self._session.request("GET", url) as response:
response.raise_for_status()
return await response.text()
async def main() -> None:
async with MyClass() as my_class:
html = await my_class.make_http_get_request("http://github.com/")
print(html)
if __name__ == "__main__":
asyncio.run(main())
I hope this clears things up.