Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forward stdout to some widget #268

Open
johngull opened this issue Oct 4, 2019 · 25 comments
Open

Forward stdout to some widget #268

johngull opened this issue Oct 4, 2019 · 25 comments
Labels
area:widgets status:unlikely Will probably not implement but looking for further feedback type:enhancement Requests for feature enhancements or new features type:possible-component

Comments

@johngull
Copy link

johngull commented Oct 4, 2019

Problem

I tried to use streamlit for the interactive clustering app.
In my case clustering is long. And the only way to see any progress is to use verbose=1. In such a case, sklearn algorithm put some information to stdout.
But I see no possibility to show this information with streamlit.


Community voting on feature requests enables the Streamlit team to understand which features are most important to our users.

If you'd like the Streamlit team to prioritize this feature request, please use the 👍 (thumbs up emoji) reaction in response to the initial post.

@johngull johngull added the type:enhancement Requests for feature enhancements or new features label Oct 4, 2019
@tvst
Copy link
Contributor

tvst commented Oct 6, 2019

Hi @johngull !

This is something we've been considering for a while now. We're actually considering different APIs and architectures for this right now. We'll post here when we have something a little more concrete to talk about.

@EricSpeidel
Copy link

Are there any workarounds or progress on this that you are aware of? This would make a powerful feature!

@zblz
Copy link

zblz commented Feb 10, 2020

This would be an extremely useful feature for streamlit as it would also enable many exploration and coding workflows for which notebooks are usually used now. I have an imperfect workaround that prints stdout and stderr to the frontend after the command is finished, as opposed to streamed during execution:

    stdout = io.StringIO()
    stderr = io.StringIO()
    try:
        with contextlib.redirect_stdout(stdout):
            with contextlib.redirect_stderr(stderr):
                <code to be run>
    except Exception as e:
        st.write(f"Failure while executing: {e}")
    finally:
        st.write("Execution output:\n", stdout.getvalue())
        st.write("Execution error output:\n", stderr.getvalue())

@jesserobertson
Copy link

jesserobertson commented Aug 6, 2020

I built on zblz's workaround and turned it into a wrapper that might be useful for some people:

import contextlib
from functools import wraps
from io import StringIO

import streamlit as st

def capture_output(func):
    """Capture output from running a function and write using streamlit."""

    @wraps(func)
    def wrapper(*args, **kwargs):
        # Redirect output to string buffers
        stdout, stderr = StringIO(), StringIO()
        try:
            with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
                return func(*args, **kwargs)
        except Exception as err:
            st.write(f"Failure while executing: {err}")
        finally:
            if _stdout := stdout.getvalue():
                st.write("Execution stdout:")
                st.code(_stdout)
            if _stderr := stderr.getvalue():
                st.write("Execution stderr:")
                st.code(_stderr)

    return wrapper

Then you can do something like

stprint = capture_output(print)

stprint(some_variables)

which makes print debugging easier

edit: added relevant import statements for code snippet

@durandg12
Copy link

Hello @tvst, are there any news on this matter?

@rjvtww
Copy link

rjvtww commented Oct 16, 2020

+1 on this! Super needed for training models which take time.

@wrist
Copy link

wrist commented Oct 20, 2020

I could capture stdout by reading a StringIO instance shared between the main thread and sub thread.
I assume print_func is time-consuming processing, the function is called from thread_func with stdout redirection to StringIO.
And thread_func is invoked in sub thread, so the main thread observes the stdout of print_func through StringIO.

import time
import contextlib

from concurrent.futures import ThreadPoolExecutor
from io import StringIO

import streamlit as st


def print_func(sec: int):
    for i in range(sec):
        print(f"{i} [sec] printed in sub thread\n")
        time.sleep(1)
    return "print finished"

def thread_func(sio: StringIO):
    with contextlib.redirect_stdout(sio):
        result = print_func(10)
        return result


if __name__ == '__main__':
    st.write("# thread test")

    shared_sio = StringIO()

    with ThreadPoolExecutor() as executor:
        future = executor.submit(thread_func, shared_sio)

        # observe shared_sio
        placeholder = st.empty()
        while future.running():
            placeholder.empty()
            placeholder.write(shared_sio.getvalue())
            time.sleep(1)

        # after the end of sub thread
        placeholder.empty()
        placeholder.write(shared_sio.getvalue())

        result = future.result()

        st.write(result)

@kcm117
Copy link

kcm117 commented Oct 26, 2020

+1 - I also have some use cases this would be helpful on.

@schaumb
Copy link
Contributor

schaumb commented Mar 30, 2021

I created a gist from this problem
https://gist.github.com/schaumb/037f139035d93cff3ad9f4f7e5f739ce
with a simplified syntax, and with realtime output:

from streamlit.redirect as rd

with rd.stdout:
  print('blablabla')
  time.sleep(1)
  print('Hello from code block')

It can be configure easily:

st.sidebar.text("Standard output message here:")
to_out = st.sidebar.empty()
...
with rd.stdout(to=to_out, format='markdown'):
  print('**Hello** from markdown')

It can be nesting:

garbage = st.empty()

with rd.stdout:
  print('important message')
  with rd.stdout(to=garbage):
    print("some garbage")
  garbage.write()
  print("another important message")

What do you think?

@Mjboothaus
Copy link

Mjboothaus commented Jul 26, 2022

I created a gist from this problem https://gist.github.com/schaumb/037f139035d93cff3ad9f4f7e5f739ce with a simplified syntax, and with realtime output:

from streamlit.redirect as rd

with rd.stdout:
  print('blablabla')
  time.sleep(1)
  print('Hello from code block')

It can be configure easily:

st.sidebar.text("Standard output message here:")
to_out = st.sidebar.empty()
...
with rd.stdout(to=to_out, format='markdown'):
  print('**Hello** from markdown')

It can be nesting:

garbage = st.empty()

with rd.stdout:
  print('important message')
  with rd.stdout(to=garbage):
    print("some garbage")
  garbage.write()
  print("another important message")

What do you think?

Hi - used your Gist and it works excellently. Nice work! :)

I'm wondering as I have output which is long (which I don't have control over) - could I overwrite one line at a time rather than the text just scrolling down the page?

Cheers,
Michael

p.s. I am more of a Data Scientist rather than a Computer Scientist!

@schaumb
Copy link
Contributor

schaumb commented Jul 26, 2022

Hi Michael,

I extended the gist with 2 more parameters:
max_buffer (default is None) and buffer_separator (default is '\n').

It can be used as following:

with rd.stdout(max_buffer=55):
  i = 0
  while True:
    print("Hello ", i)
    i += 1

These new parameters truncates the stdout to maximum max_buffer character (in this example only 55). If log extends this size, we rotate the logs.
If buffer_separator is not set to None, we split the log on the first occurence (it means the log is not cutted half on the oldest 'line').

I think you want something like this 😄

@Mjboothaus
Copy link

G'day Bela (from Sydney, Aus)

Thanks so much for your prompt and thoughtful reply.

It is essentially exactly what I needed!

I am tweaking it for my precise purposes (e.g. parsing the iterative output from an optimisation routine). Here is the repo if you're interested - https://github.com/Mjboothaus/PhD-Thesis

I have an app front end on it - deployed here (I don't have it working precisely just yet):
https://michael-booth-phd-thesis-7awa5rjwgq-et.a.run.app/

@antopj
Copy link

antopj commented Oct 6, 2022

Is this possible now?

I was trying to deploy some application + docker containers in a remote system using streamlit + ansible. (already have existing playbook).
I wish I could stream the ansible output to streamlit so that I could manage everything from UI.

@carolinedlu
Copy link
Collaborator

Sharing @fredzannarbor's thoughts from #5842 --

Problem
I find myself frequently creating large numbers of "print" and streamlit write commands that have overlapping content. This is a big waste of my time. It seems it would be more efficient to be able to selectively route stdout/stderr to streamlit write commands.

This has come up before: https://discuss.streamlit.io/t/cannot-print-the-terminal-output-in-streamlit/6602. The solutions discussed in this thread seem pretty reasonable but IMHO stdout<=>st.write should be part of streamlit core because the activities involved are fundamental to python, the shell, user interaction, and debugging.

I don't necessarily care how this is accomplished, but here are a few ideas.

st.print(object, {write, info, error}) sends object to both console and streamlit write. In essence, "promotes" certain st.write operations to being superpowered ones that also write to the console. A filter could look at all stdout and filter only messages matching regex.

st.redirect_stdout per thread, with addition of regex filter that only redirects certain stdout/stderr)

etc.

Fred

@schaumb
Copy link
Contributor

schaumb commented Dec 16, 2022

Hi @carolinedlu!

Thanks for the comment sharing.

I extend my gist with another 2 arguments duplicate_out and regex.

For working regex argument, buffer_separator must be not none.

import streamlit as st
from random import randint
import time

debug, info, error = st.columns(3)

i = 0
with rd.stdout(to=debug, regex='DEBUG', duplicate_out=True):  # duplicate_out == True -> writes to original stdout
    with rd.stdout(to=info, regex='INFO', duplicate_out=True):  # duplicate_out == True -> writes to upper layer too
        with rd.stdout(to=error, regex='ERROR', duplicate_out=True):
            while True:
                severity = ('DEBUG', 'INFO', 'ERROR')[randint(0, 2)]
                i += 1
                print('[{}] Message {}'.format(severity, i))
                time.sleep(1)
streamlit-main-2022-12-16-15-12-55.webm

@jrieke jrieke added the status:unlikely Will probably not implement but looking for further feedback label Feb 10, 2023
@jrieke
Copy link
Collaborator

jrieke commented Feb 10, 2023

Hey! We are unlikely to implement this directly since it seems like more of an edge case. This would be great for a custom component though!

@123sin123
Copy link

I created a gist from this problem https://gist.github.com/schaumb/037f139035d93cff3ad9f4f7e5f739ce with a simplified syntax, and with realtime output:

from streamlit.redirect as rd

with rd.stdout:
  print('blablabla')
  time.sleep(1)
  print('Hello from code block')

It can be configure easily:

st.sidebar.text("Standard output message here:")
to_out = st.sidebar.empty()
...
with rd.stdout(to=to_out, format='markdown'):
  print('**Hello** from markdown')

It can be nesting:

garbage = st.empty()

with rd.stdout:
  print('important message')
  with rd.stdout(to=garbage):
    print("some garbage")
  garbage.write()
  print("another important message")

What do you think?

Hi @schaumb

Thank you for creating this gist!

I would like to know about the licensing of this gist.
Is it available for commercial use?

@schaumb
Copy link
Contributor

schaumb commented Feb 27, 2023

I created a gist from this problem https://gist.github.com/schaumb/037f139035d93cff3ad9f4f7e5f739ce with a simplified syntax, and with realtime output:

from streamlit.redirect as rd

with rd.stdout:
  print('blablabla')
  time.sleep(1)
  print('Hello from code block')

It can be configure easily:

st.sidebar.text("Standard output message here:")
to_out = st.sidebar.empty()
...
with rd.stdout(to=to_out, format='markdown'):
  print('**Hello** from markdown')

It can be nesting:

garbage = st.empty()

with rd.stdout:
  print('important message')
  with rd.stdout(to=garbage):
    print("some garbage")
  garbage.write()
  print("another important message")

What do you think?

Hi @schaumb

Thank you for creating this gist!

I would like to know about the licensing of this gist. Is it available for commercial use?

Hi @123sin123 ,
Yes of course, it is available for commercial use.

@123sin123
Copy link

Hi @123sin123 ,
Yes of course, it is available for commercial use.

Thank you @schaumb.

I'm sorry for asking you many questions.
Please let me check the license details for your gist.

Are there any restrictions on the use of it?
May I use it under the "Apache-2.0 license" as well as Streamlit?

@schaumb
Copy link
Contributor

schaumb commented Feb 28, 2023

@123sin123 Use the gist, as it has an Apache 2.0 license.

@lazyhope
Copy link

Hey! We are unlikely to implement this directly since it seems like more of an edge case. This would be great for a custom component though!

I believe implementing this feature would greatly enhance the user experience especially when running large models or processing a large number of inputs, such as with certain HuggingFace pipelines. They often provide useful information like progress bars or prompts to indicate the progress. As someone who wants to showcase a model through HF pipeline and streamlit I really hope to see official components or functions implemented to support this feature!

@fredzannarbor
Copy link

fredzannarbor commented May 15, 2023

I don't agree that this should be classified as an edge case. If you think of it as "make it easier for both developers and users to get reports from machine learning internals", it is squarely in Streamlit's core mission. That said, I have experimented with the gist and redirecting a boatload of stdoout/strerr to streamlit. A huge stream of stdout looks pretty clunky in streamlit, and I find myself exploring alternatives. the problem is that if you have a streamlit front end and a pure python back end module communicating, to get back end info in the front end you have to either a) modify the function signature to add selected returns for streamlit to consume or b) put st.infos inside the back end module. Both of these are a PITA.

What one really wants, perhaps, is a way to dynamically and conditionally filter stdout from the back end into the front end or b) maybe to use generative AI to do the same task.

@luigibrancati
Copy link

Hi, this would be VERY helpful. I have an application that downloads data using an sdk, and it logs the progress to stdout. No way to show the download progress otherwise

@fredzannarbor
Copy link

@Bombras
Copy link

Bombras commented Feb 16, 2024

Hi, I am also interested in this. We use 3rd party solver for solving linear problem and it would be great to see progress, which is now only printed into terminal, then I can print results after the solver finish.
There is new function [https://docs.streamlit.io/library/api-reference/write-magic/st.write_stream], can it be used for our purposes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:widgets status:unlikely Will probably not implement but looking for further feedback type:enhancement Requests for feature enhancements or new features type:possible-component
Projects
None yet
Development

No branches or pull requests