#๐Ÿ”’ How do you transform an async generator into a coroutine?

140 messages ยท Page 1 of 1 (latest)

granite frost
#

Ultimately I want to pass this into asyncio.gather() but I'm stuck on how.

amber kernelBOT
#

@granite frost

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

wheat turret
#

Wrap it in a coroutine that collects it

#

!pip asyncstdlib has some functions that may be useful.

amber kernelBOT
wheat turret
#

Example of a function: ```py
async def alist(aiter: AsyncIterable[T]) -> list[T]:
return [item async for item in aiter]

#

it collects an async iterator (or generator) to a list

granite frost
#

I think this worked:

from asyncstdlib import list as alist

async def async_generator_to_coroutine(async_generator):
    return alist(async_generator)

tasks.append(async_generator_to_coroutine(item))
wheat turret
#

You need to await alist

granite frost
#

i think tasks.append should handle that

wheat turret
#

no, it will make the task return a coroutine

granite frost
#

i mean results = await asyncio.gather(*tasks)

wheat turret
#

Why not just append alist(item) directly?

granite frost
#

i think i'm just realizing that now

#

how can you tell if something is a async_generator specifically

wheat turret
#

it's marked async and has yield

#

the type checker knows. hover over it or pass it to typing.reveal_type

#

You can also use inspect.isasyncgenerator(func)

#

!d inspect.isasyncgenfunction

amber kernelBOT
#

inspect.isasyncgenfunction(object)```
Return `True` if the object is an [asynchronous generator](https://docs.python.org/3/glossary.html#term-asynchronous-generator) function, for example:

```py
>>> async def agen():
...     yield 1
...
>>> inspect.isasyncgenfunction(agen)
True
```   Added in version 3.6.

Changed in version 3.8: Functions wrapped in [`functools.partial()`](https://docs.python.org/3/library/functools.html#functools.partial) now return `True` if the wrapped function is a [asynchronous generator](https://docs.python.org/3/glossary.html#term-asynchronous-generator) function.
granite frost
#

hmm it went past it

#

i guess async_generator_asend is different

silk storm
#

this whole question seems odd; what is the actual use case?

wheat turret
#

yeah, using inspect is odd for adding tasks.

#

if you await a coroutine and get a coroutine back, you fucked up

#

Can you share all your code?

#

!paste

amber kernelBOT
#
Pasting large amounts of code

If your code is too long to fit in a codeblock in Discord, you can paste your code here:
https://paste.pythondiscord.com/

After pasting your code, save it by clicking the Paste! button in the bottom left, or by pressing CTRL + S. After doing that, you will be navigated to the new paste's page. Copy the URL and post it here so others can see it.

granite frost
#

high level i have implemented some utility functions for mangaging concurrency by way of Promise.map_series, Promise.reduce_series and Promise.parallel

#

which lets me do stuff like:

tasks.append(asyncio.create_task(Promise.series([
    Promise.parallel([
        Promise.series([
            lambda subproduct: fetch(session, "get", "/api/sub-product/" + subproduct["id"]),
            lambda data: data.json()
        ], subproduct),
silk storm
#

that looks more JS than Python

granite frost
#

yes

silk storm
#

I come from a very different school of thought in this matter; not sure I can help

granite frost
#

well i think my core issue is fixed with alist

#

now I just need a condition for identifying a async_generator_asend

#

so instanceof? probably?

silk storm
#

instanceof what?

#

there's no defined type for that

granite frost
#

async_generator_asend

silk storm
#

it's not something you can import

wheat turret
#

try the types module

silk storm
#

it's not there

#

or anywhere

wheat turret
#

Why do you need to check the type?

silk storm
#

is that Promise type your code?

granite frost
#

yes

granite frost
wheat turret
#

Why would it not return an async generator?

granite frost
#

instead of async_generator_asend you mean?

wheat turret
#
async def will_always_be_async_generator():
  if False:
    yield
#

That will always be a generator. It's just empty.

silk storm
#

sorry to say but this looks to me like you're making this unnecessarily complicated

wheat turret
#

Can you share your code so we can get a better idea of what you're actually trying to do?

silk storm
#

yes please

wheat turret
granite frost
#

fair warning: you're going to hate this

silk storm
#

this doesn't yet answer the question of what you're actually trying to accomplish

wheat turret
#

just the lambdas

silk storm
#

?

wheat turret
#

I see no generators in your code

granite frost
wheat turret
#

I think you should reconsider what you're doing just for the DX

granite frost
#

i'm thoroughly enjoying the dx ๐Ÿ™‚

#

i could not use lambdas and write actual functions and just pass their names in

wheat turret
#

calling __anext__() doesn't return a generator.

granite frost
#

oh maybe the for _ in "_" bit then

wheat turret
#

yes, that creates a generator

#

the object you call anext on is a generator

#

specifically, it's async _ in ...

#

asyncstdlib has a function to convert a regular iterable into an async iterable

granite frost
#

so i could get rid of the dumb hack?

wheat turret
#

I think it also has map

#

the reduce_series call would be more useful as a function

granite frost
#

i think i'll still have the problem of needing to use the await keyword within the lambdas which isn't allowed

#

but yeah to your point i could just not use lambdas

wheat turret
#
async def do_thing(session):
  foos = await sdk.foo.get_all_foos(session)
  for foo in foos:
    bars = await sdk.bar.get_all_bars_by_foo_name(session, foo["name"])
    for bar in bars:
      results = await acsdk.bar.delete_bar_by_id(session, bar["id"])
      print(results)

async def main():
  session = init_session(api_key)
  await do_thing(session)
#

you could even make this a list comprehension if you wanted

granite frost
#

sure but now it's all in series

wheat turret
#

you never called parallel anyway

granite frost
#

i do in other places in the code

wheat turret
#

The way you're calling parallel isn't how generators work

silk storm
#

here you want the for foo in foos part to work in parallel, right?

granite frost
#

i'm mostly just handling coroutines, i'm only using generators as a part of the async lambda hack

wheat turret
#

You'll probably want to replace inspect.isasyncgenfunction with inspect.isasyncgen

#

generator functions return generators

granite frost
#

yeah just gotta figure out this last bit

wheat turret
#

don't use __anext__

granite frost
#

ah! using __aiter__ may have done it

wheat turret
#

You can also do hasattr(item, "__anext__") to check if something is an async iterator

silk storm
#

I don't think I'm any closer to understanding what this enormous hack intends to accomplish

wheat turret
#

Also hasattr(item, "__aiter__")

granite frost
#

python doesn't really seem to have a good answer to managing concurrency, that's the problem

wheat turret
#

async iterators are also async iterables

silk storm
#

@granite frost that's a really broad topic; what solutions have you researched?

#

have you tried asyncio.gather() or asyncio.TaskGroup yet?

#

in short, what are you missing?

wheat turret
#

Try doing this. ```py
for item in iterable:
if inspect.isasyncgen(item):
tasks.append(alist(item))
else:
tasks.append(item)

granite frost
#

yep that's what i have

wheat turret
#

btw lambdas can't be generators

#

your lambda with get_all_foos is actually returning tuple[Session, generator_send]

granite frost
#

yeah what is a generator_send

silk storm
#

I'm bugging out since I'm not getting any answers

wheat turret
#

I'm curious how you were able to call __anext__ on a non-async generator

granite frost
silk storm
#

what I've been asking is what you're doing these things for?

wheat turret
#

ok, simply awaiting something inside the generator will make it async

silk storm
#

what is the actual use case?

#

there are many ways to handle concurrency in Python

wheat turret
#

I think they're making a toy

silk storm
#

toy?

wheat turret
#

for fun

silk storm
#

eh

granite frost
#

it's for interacting with an api, it's a whole bunch of fetch requests

wheat turret
#

Just use a normal async function

granite frost
#

yeah

wheat turret
#

you're adding unneeded complexity

granite frost
#

no argument here ๐Ÿ˜†

#

okay thanks for all your help guys!

wheat turret
#

!pip aiolimit has some useful functions

amber kernelBOT
wheat turret
#

mainly amap

#

this isn't the right library

granite frost
#

im guessing you meant itertools

wheat turret
#

no

#

itertools is stdlib

#

!pip aiometer

amber kernelBOT
#

A Python concurrency scheduling library, compatible with asyncio and trio

Released on <t:1702324318:D>.

granite frost
#

oh cool i'm using something called throttle i think

#

but this might be better

wheat turret
#

throttle doesn't have native asyncio support

#

only 3 stars on github

granite frost
#

sorry throttler

#

solves a different problem

wheat turret
#

neat

granite frost
#

cheers! thanks for the help!

amber kernelBOT
#
Python help channel closed

This help channel has been closed and it's no longer possible to send messages here. If your question wasn't answered, feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.

#

๐Ÿ”’ How do you transform an async generator into a coroutine?