streamlit / streamlit

Streamlit — A faster way to build and share data apps.
https://streamlit.io
Apache License 2.0
35.37k stars 3.07k forks source link

Add support for async generators in `write_stream` #8161

Open evertoncolling opened 8 months ago

evertoncolling commented 8 months ago

Checklist

Summary

Add support for async_generator in write_stream. This allows to stream data from async requests in Streamlit.

Why?

Today I can only stream data from synchronous HTTP clients while using write_stream in Streamlit.

How?

Add support for async_generator in write_stream.

Additional Context

No response

github-actions[bot] commented 8 months ago

To help Streamlit prioritize this feature, react with a 👍 (thumbs up emoji) to the initial post.

Your vote helps us identify which enhancements matter most to our users.

Visits

whitphx commented 6 months ago

I want this async support too to use write_stream on stlite because blocking code doesn't work on it.

lukasmasuch commented 5 months ago

@evertoncolling Thanks for the suggestion. We are considering adding this natively to write_stream. In the meantime, here is a workaround to transform async generators to sync generators and use them with st.write_stream:

import asyncio
from typing import AsyncGenerator

import streamlit as st

async def async_generator():
    yield "hello"
    await asyncio.sleep(2)
    yield "world"

def to_sync_generator(async_gen: AsyncGenerator):
    while True:
        try:
            yield asyncio.run(anext(async_gen))
        except StopAsyncIteration:
            break

st.write_stream(to_sync_generator(async_generator()))
FilippTrigub commented 4 months ago

@evertoncolling Thanks for the suggestion. We are considering adding this natively to write_stream. In the meantime, here is a workaround to transform async generators to sync generators and use them with st.write_stream:

import asyncio
from typing import AsyncGenerator

import streamlit as st

async def async_generator():
    yield "hello"
    await asyncio.sleep(2)
    yield "world"

def to_sync_generator(async_gen: AsyncGenerator):
    while True:
        try:
            yield asyncio.run(anext(async_gen))
        except StopAsyncIteration:
            break

st.write_stream(to_sync_generator(async_generator()))

This seem to work for 1 step and then break immediately.

lukasmasuch commented 4 months ago

@FilippTrigub do you have more information on how it breaks? Is there any kind of error?

lucasboscatti commented 3 months ago

@evertoncolling Thanks for the suggestion. We are considering adding this natively to write_stream. In the meantime, here is a workaround to transform async generators to sync generators and use them with st.write_stream:

import asyncio
from typing import AsyncGenerator

import streamlit as st

async def async_generator():
    yield "hello"
    await asyncio.sleep(2)
    yield "world"

def to_sync_generator(async_gen: AsyncGenerator):
    while True:
        try:
            yield asyncio.run(anext(async_gen))
        except StopAsyncIteration:
            break

st.write_stream(to_sync_generator(async_generator()))

This seem to work for 1 step and then break immediately.

Hello. Claude AI fixed it for me, now it is working:

def to_sync_generator(async_gen: AsyncGenerator):
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)

    try:
        while True:
            try:
                yield loop.run_until_complete(anext(async_gen))
            except StopAsyncIteration:
                break
    finally:
        loop.close()