вложенный "асинхронный с" с помощью aiohttp - PullRequest
0 голосов
/ 09 ноября 2018

Я хотел бы создать класс планировщика, который использует aiohttp для выполнения вызовов API. Я попробовал это:

import asyncio
import aiohttp

class MySession:
    def __init__(self):
        self.session = None

    async def __aenter__(self):
        async with aiohttp.ClientSession() as session:
            self.session = session
            return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.session:
            await self.session.close()

async def method1():
    async with MySession() as s:
        async with s.session.get("https://www.google.com") as resp:
            if resp.status == 200:
                print("successful call!")

loop = asyncio.get_event_loop()
loop.run_until_complete(method1())
loop.close()

но это просто приводит к ошибке: RuntimeError: Session is closed. Второй подход для функции __aenter__:

    async def __aenter__(self):
        self.session = aiohttp.ClientSession()
        return self

работает хорошо. Это хорошая конструкция? Это не придерживается примеров того, как использовать aiohttp. Также интересно, почему первый подход не работает?

Ответы [ 2 ]

0 голосов
/ 11 января 2019

Вы можете управлять этой зависимостью извне:

import asyncio
import aiohttp

class MySession:

    def __init__(self, client_session):
        self.session = client_session

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        pass

async def method1():
    async with aiohttp.ClientSession() as client_session:
        async with MySession(client_session) as s:
            async with s.session.get("https://www.google.com") as resp:
                if resp.status == 200:
                    print("successful call!")

asyncio.run(method1())

Когда эта цепь async with становится слишком нелепой, вы можете использовать AsyncExitStack:

from contextlib import AsyncExitStack

async def method1():
    async with AsyncExitStack() as stack:
        client_session = await stack.enter_async_context(aiohttp.ClientSession())
        s = await stack.enter_async_context(MySession(client_session))
        async with s.session.get("https://www.google.com") as resp:
            if resp.status == 200:
                print("successful call!")
0 голосов
/ 09 ноября 2018

Вы не можете использовать with внутри функции, и контекстный менеджер остается открытым, нет. Блок with with aiohttp.ClientSession() as session: выходит, как только вы используете return для выхода из сопрограммы __aenter__!

Для конкретного случая ввод aiohttp.ClientSession() диспетчера контекста ничего не делает, кроме как возвращает self. Так что для этого типа достаточно просто создать экземпляр и сохранить его в self.session и ожидать self.session.close(), да.

Общий общий шаблон для вложенного асинхронного диспетчера контекста заключается в ожидании методов __aenter__ и __aexit__ вложенного асинхронного диспетчера контекста от ваших собственных таких методов (и, возможно, передачи информации об исключении) :

class MySession:
    def __init__(self):
        self.session = None

    async def __aenter__(self):
        self.session = aiohttp.ClientSession()
        await self.session.__aenter__()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.session:
            return await self.session.__aexit__(exc_type, exc_val, exc_tb)

Технически говоря, вы должны сначала убедиться в наличии фактического атрибута __aexit__ перед тем, как войти во вложенный менеджер контекста:

class MySession:
    def __init__(self):
        self.session = None
        self._session_aexit = None

    async def __aenter__(self):
        self.session = aiohttp.ClientSession()
        self._session_aexit = type(self.session).__aexit__
        await self.session.__aenter__()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.session:
            return await self._session_aexit.__aexit__(
                self.session, exc_type, exc_val, exc_tb)

См. официальный PEP, который добавил концепцию .

...