Не работает session. Потеря контекста
Приведу несколько примеров. В одних случаях, я получаю ошибку вида:
def __enter__(self) -> BaseTimerContext:
task = current_task(loop=self._loop)
if task is None:
> raise RuntimeError('Timeout context manager should be used '
'inside a task')
E RuntimeError: Timeout context manager should be used inside a task
В других случаях, всё работает корректно. Все примеры выполняются в связке с pytest (для работы требуется pytest-asyncio==0.12.0). Для того, чтобы было проще понять проблему, я формализую её как задачу с параметрами. В примерах есть несколько "параметров":
aiohttp.ClientSession(). Создание сессии -- первый "параметр" (A). Будем объявлять его в различных местах: в конструкторе, в теле методаclient_storage = AsyncClient(). Создание экземпляра класса -- второй "параметр" (B). Будем объявлять его в различных местах: вне теста, в файле с тестом (ВНУТРИ функции), в файле с тестом (ВНЕ функции).
Таким образом, у нас есть 6 различных конфигураций.
- A = в конструкторе; B = вне теста -> Ошибка
- A = в конструкторе; B = ВНУТРИ функции -> Корректная работа
A = в конструкторе; B = ВНЕ функции -> Ошибка
A = в теле метода; B = ВНЕ функции -> Корректная работа
- A = в теле метода; B = ВНУТРИ функции -> Корректная работа
- A = в теле метода; B = ВНЕ функции -> Корректная работа
REVIEW: Таким образом, почему-то теряется сессия, если её объявлять не там, где используешь. В чём причина. Как исправить эту проблему?
Теперь приведу рабочие примеры, чтобы стало понятнее, в чём ошибка. Буду приводить примеры также, как они расположены в списке:
1. A = в конструкторе; B = вне теста -> Ошибка
Импорт client_storage из другого module. ClientSession объявляется в конструкторе:
tests/__init__.py:
import aiohttp
class AsyncClient:
def __init__(self):
self.__session = aiohttp.ClientSession() # Создание объекта сессии
async def request(self):
resp = await self.__session.request(method='get', url='https://yandex.ru')
return resp
client_storage = AsyncClient() # Создание экземпляра класса клиента
tests/test_user.py:
import pytest
import tests
@pytest.mark.asyncio
async def test_real_request():
await tests.client_storage.request()
print('x')
2. A = в конструкторе; B = ВНУТРИ функции -> Корректная работа
Импорт client_storage из другого module. ClientSession объявляется в конструкторе:
tests/__init__.py:
import aiohttp
class AsyncClient:
def __init__(self):
self.__session = aiohttp.ClientSession() # Создание экземпляра класса
async def request(self):
resp = await self.__session.request(method='get', url='https://yandex.ru')
return resp
tests/test_user.py:
import pytest
import tests
@pytest.mark.asyncio
async def test_real_request():
client_storage = AsyncClient() # Создание экземпляра класса клиента
await client_storage.request()
print('x')
3. A = в конструкторе; B = ВНЕ функции -> Ошибка
ClientSession объявляется в конструкторе. client_storage объявляется в том же module, в тесте:
tests/__init__.py:
import aiohttp
class AsyncClient:
def __init__(self):
self.__session = aiohttp.ClientSession() # Создание экземпляра класса
async def request(self):
resp = await self.__session.request(method='get', url='https://yandex.ru')
return resp
tests/test_user.py:
import pytest
import tests
client_storage = AsyncClient() # Создание экземпляра класса клиента
@pytest.mark.asyncio
async def test_real_request():
client_storage = tests.AsyncClient()
await client_storage.request()
4. A = в методе; B = вне теста -> Корректная работа
Импорт client_storage из другого module. ClientSession объявляется в конструкторе:
tests/__init__.py:
import aiohttp
class AsyncClient:
def __init__(self):
self.__session = None
async def request(self):
self.__session = aiohttp.ClientSession() # Создание объекта сессии
resp = await self.__session.request(method='get', url='https://yandex.ru')
return resp
client_storage = AsyncClient() # Создание экземпляра класса клиента
tests/test_user.py:
import pytest
import tests
@pytest.mark.asyncio
async def test_real_request():
await tests.client_storage.request()
print('x')
5. A = в методе; B = ВНУТРИ функции -> Корректная работа
Импорт client_storage из другого module. ClientSession объявляется в конструкторе:
tests/__init__.py:
import aiohttp
class AsyncClient:
def __init__(self):
self.__session = None
async def request(self):
self.__session = aiohttp.ClientSession() # Создание объекта сессии
resp = await self.__session.request(method='get', url='https://yandex.ru')
return resp
tests/test_user.py:
import pytest
import tests
@pytest.mark.asyncio
async def test_real_request():
client_storage = AsyncClient() # Создание экземпляра класса клиента
await client_storage.request()
print('x')
6. A = в методе; B = ВНЕ функции -> Корректная работа
ClientSession объявляется в конструкторе. client_storage объявляется в том же module, в тесте:
tests/__init__.py:
import aiohttp
class AsyncClient:
def __init__(self):
self.__session = None
async def request(self):
self.__session = aiohttp.ClientSession() # Создание объекта сессии
resp = await self.__session.request(method='get', url='https://yandex.ru')
return resp
tests/test_user.py:
import pytest
import tests
client_storage = AsyncClient() # Создание экземпляра класса клиента
@pytest.mark.asyncio
async def test_real_request():
client_storage = tests.AsyncClient()
await client_storage.request()
Таким образом, вопрос сводится к следующему: почему при определении ClientSession в асинхронной функции или вне её, не удаётся сделать запрос?
Если копнуть в глубь, оказывается, что под капотом в aiohttp создаётся loop. Если его создавать в асинхронном методе, то переменная _thread_id is not None. Если его создавать в других местах (там, где возникают ошибки), то она равна None. Проблема, видимо, кроется где-то в этом направлении.
Я попытался разобраться, в чём тут проблема: залез в asyncio.get_event_loop() и далее в asyncio.get_event_loop_policy(), которые вызывается внутри aiohttp и asyncio соответсвенно. Но здесь меня постигла неудача, поскольку get_event_loop_policy возвращает объект типа _UnixDefaultEventLoopPolicy, а снаружи оказывается уже объект другого типа с соверенно другим набором полей: _UnixEventLoopPolicy. Как такое происходит, я не понял.
Частичное решение подсказали в этом вопросе.
class AsyncClient:
def __init__(self):
self.__session = None
async def request(self):
if self.__session is None:
self.__session = aiohttp.ClientSession()
resp = await self.__session.request(method='get', url='https://yandex.ru')
return resp