unasync icon indicating copy to clipboard operation
unasync copied to clipboard

Provide common replacement utils / functions many projects will need

Open sethmlarson opened this issue 5 years ago • 5 comments

Some utility functions I was thinking about:

def azip(*iterables):  # Convert any iterables and async iterables into a zipped async iterable
    iterators = [aiter(x) for x in iterables]

    async def generator():
        while True:
            try:
                yield tuple([await x.__anext__() for x in iterators])
            except StopAsyncIteration:
                break

    return generator().__aiter__()

def aiter(x):  # Convert any iterable or async iterable into an async iterator
    if hasattr(x, "__aiter__"):
        return x.__aiter__()
    elif hasattr(x, "__anext__"):
        return x

    async def aiter_wrapper():
        for item in x:
            yield item

    return aiter_wrapper().__aiter__()

async def anext(i):  # Advance an async iterator, good for mapping to `next()`
    return await i.__anext__()


async def await_or_return(x):  # Maps to 'return_()'?
    if iscoroutine(x):
        return await x
    return x


# All of the sync variations of the above functions
next = next
iter = iter
zip = zip
def return_(x):
    return x

Could also have azip_longest -> zip_longest, etc.

All of the async functions could be available only on Python 3.6+ so this could be used on projects that support 3.5 or less.

Could also provide type hints that are effected by unasync properly as well.

AsyncOrSyncIterable[X] = Union[AsyncIterable[X], Iterable[X]]
SyncIterable[X] = Iterable[X]

This should probably be a separate project to unasync as unasync is primarily a build tool, raising it here because it'd make sense for the tool to live under python-trio, maybe unasync-utils?

sethmlarson avatar May 07 '20 14:05 sethmlarson

Thanks for opening this issue!

I'm a bit surprised here because my understanding is that unasync exists specifically so that you don't need functions like await_or_return. Instead return await x in the async version of a package becomes return x in the sync version.

But since you know that, what am I missing here?

pquentin avatar May 08 '20 19:05 pquentin

I'm imaging a situation where your API takes a callable from the user and you'd like to support both Union[Callable[[...], X], Callable[[...], Awaitable[X]]] on the async implementation but only support Callable[[...], X] on the sync side. To do that you'd need a conditional await I think?

Maybe await_if_coro() or something like this is a better name for this interface?

sethmlarson avatar May 08 '20 19:05 sethmlarson

An example where this would be useful in Hip: When given a body that has read() we want to call await f.read() for async files and f.read() on sync files.

sethmlarson avatar May 09 '20 15:05 sethmlarson

Also anext() would be useful in a similar place in Hip for generating the async iterators of bytes to send.

sethmlarson avatar May 09 '20 15:05 sethmlarson

Right, this is needed when your API accepts both async and sync callables. Even though unasync is only a build dependency, I don't really mind adding utilities like that and making it useful as a runtime dependency too

pquentin avatar May 19 '20 10:05 pquentin