python-trio / trio-asyncio

a re-implementation of the asyncio mainloop on top of Trio

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

run_asyncio() and context managers

belm0 opened this issue · comments

I'd like to have the equivalent of async with aiohttp.ClientSession().ws_connect('wss://echo.websocket.org') as ws wrapped in run_asyncio(). However it yields AttributeError: __aexit__. Is it merely a matter of propagating __aexit__?

Something like this should probably either be documented or part of trio_asyncio.

import aiohttp
import trio_asyncio
import asyncio
from async_generator import asynccontextmanager

@asynccontextmanager
async def run_asynccontext(proc):
    r = await trio_asyncio.run_asyncio(proc.__aenter__)
    try:
        yield r
    except BaseException as exc:
        if not await trio_asyncio.run_asyncio(type(exc), exc, exc.__traceback__):
            raise
    else:
        await trio_asyncio.run_asyncio(proc.__aexit__, None, None, None)

async def t():
    async with run_asynccontext(aiohttp.ClientSession()) as c:
        async with run_asynccontext(c.ws_connect('wss://echo.websocket.org')) as ws:
            await trio_asyncio.run_asyncio(ws.send_str,"Hello")
            r = await trio_asyncio.run_asyncio(ws.receive)
            print(r)

trio_asyncio.run(t)

Thank you, I'd been fighting with trying to implement that myself.

I wonder if it's possible to make run_asyncio() support both cases. My attempt at it has failed.

Possible, if you convert run_asyncio from an async function to something that returns an object with an __await__ method (and, for context management, __aenter__ and __aexit__).

I'll check whether that works, because if it does we can dispense with all those different wrappers and just use run_asyncio (or run_trio) for everything, which would simplify usage a lot.

Works. The next version will have a single run_asyncio method that can be used for plain function calls, iterators/generators, and context managers. I'm investigating whether run_trio can safely be convinced to behave the same way, but the interplay with asyncio.Future might prevent that.

Maybe making aio2trio and trio2aio magic might be nicer? Like:

class aio2trio:
    def __init__(self, thing):
        self._thing = thing

    async def __call__(self, *args, **kwargs):
        return await run_asyncio(partial(self._thing, *args, **kwargs))

    async def __aenter__(self):
        return await run_asyncio(self._thing.__aenter__)

    async def __aexit__(self, exc_value, exc_type, exc_tb):
        return await run_asyncio(self._thing.__aexit__, exc_value, exc_type, exc_tb)

    async def __aiter__(self):
        aio_ait = self._thing.__aiter__()
        try:
            while True:
                yield await run_asyncio(aio_ait.__anext__)
        except StopAsyncIteration:
            pass

@aio2trio
async def ...

async with aio2trio(aio_cm):
    ...

async for ... in aio2trio(aio_async_iterable):
    ...

Hmm. Possibly. Though first I'll check how much @Fuyukai 's magic shim works out, because frankly all those converters and impositors are scary.

For reference, I believe the "magic shim" is this: https://gitter.im/python-trio/general?at=5b7011345ec2bc174fea1137

IIRC the reason we didn't go with a magic shim in the first place is that it doesn't give us a place to convert back and forth between trio and asyncio's cancellation semantics, so asyncio-mode code sees asyncio.CancelledError and trio-mode code sees trio.Cancelled?

Hmm, run_asyncio is still called when fed with a future, so this might actually work. Will test.

The magic is included in 0.8.2 but needs to be refactored. See #36