Cannot print the terminal output in Streamlit?

Hi guys,

Iā€™m trying to get a terminal output to be printed in Streamlit. I tried various codes including this one

import searchconsole
account = searchconsole.authenticate(client_config="GSCTatieLouCredentials.json", serialize='credentials.json', flow="console")
st.write(account)

but nothing seems to work - See screenshot below:

Any idea on how to print in Streamlit?

Thanks,
Charly

2 Likes

Hello @Charly_Wargnier,

One solution would be to redirect stdout/stderr to st.write (or anything you want).

Hereā€™s a quick example for stdout:

from contextlib import contextmanager, redirect_stdout
from io import StringIO
from time import sleep
import streamlit as st

@contextmanager
def st_capture(output_func):
    with StringIO() as stdout, redirect_stdout(stdout):
        old_write = stdout.write

        def new_write(string):
            ret = old_write(string)
            output_func(stdout.getvalue())
            return ret
        
        stdout.write = new_write
        yield


output = st.empty()
with st_capture(output.code):
    print("Hello")
    sleep(1)
    print("World")

output = st.empty()
with st_capture(output.info):
    print("Goodbye")
    sleep(1)
    print("World")

4 Likes

Thank you Synode! :pray:

Trying your code Iā€™ve had an error, along with the message:

Please report this bug at https://github.com/streamlit/streamlit/issues.

See error below:

(venv) C:\Users\Charly\Desktop\StreamForecast>streamlit run PrintToTerminalSynode.py
2020-10-25 10:28:54.198 Thread 'MainThread': missing ReportContext
2020-10-25 10:28:54.200 Thread 'MainThread': missing ReportContext
2020-10-25 10:28:54.201 Thread 'MainThread': missing ReportContext
Traceback (most recent call last):
  File "c:\users\charly\desktop\streamforecast\venv\lib\site-packages\streamlit\server\server.py", line 412, in _loop_coroutine
    on_started(self)
  File "c:\users\charly\desktop\streamforecast\venv\lib\site-packages\streamlit\bootstrap.py", line 135, in _on_server_start
    _print_url(server.is_running_hello)
  File "c:\users\charly\desktop\streamforecast\venv\lib\site-packages\streamlit\bootstrap.py", line 210, in _print_url
    click.secho("")
  File "c:\users\charly\desktop\streamforecast\venv\lib\site-packages\click\termui.py", line 548, in secho
    return echo(message, file=file, nl=nl, err=err, color=color)
  File "c:\users\charly\desktop\streamforecast\venv\lib\site-packages\click\utils.py", line 272, in echo
    file.write(message)
  File "C:\Users\Charly\Desktop\StreamForecast\PrintToTerminalSynode.py", line 13, in new_write
    output_func(stdout.getvalue())
  File "c:\users\charly\desktop\streamforecast\venv\lib\site-packages\streamlit\elements\markdown.py", line 137, in code
    return dg._enqueue("markdown", code_proto)  # type: ignore
  File "c:\users\charly\desktop\streamforecast\venv\lib\site-packages\streamlit\delta_generator.py", line 335, in _enqueue
    _enqueue_message(msg)
  File "c:\users\charly\desktop\streamforecast\venv\lib\site-packages\streamlit\delta_generator.py", line 714, in _enqueue_message
    raise NoSessionContext()
streamlit.errors.NoSessionContext
2020-10-25 10:28:54.239
Please report this bug at https://github.com/streamlit/streamlit/issues.

Is this expected?

Thanks,
Charly

Hmm, not quite :sweat_smile:
Can you share your code?

It was literally your code pasted verbatim in a Venv folder! :sweat_smile:

Hmm, indeed. Iā€™ve implemented that while streamlit was already running. I didnā€™t run it from scratch though. Iā€™ll fix that a little bit later today

1 Like

Thanks Synode. I actually found a workaround for my issue, although I (and others Iā€™m sure :)) would still be keen to see the above code working :raised_hands: :slight_smile:

Charly

Hi @okld,

It looks like I may have spoken too fast! :sweat_smile: The workaround I found earlier doesnā€™t quite work, issue logged here:

I believe that being able to print the consoleā€™s output to Streamlit would likely fix the issue. As you can see in the screenshot below, users would need to click on the URL in the console for the app to work:

Instead, I either get the Errno 98 issue (issue above) or nothing happensā€¦ whereas ideally users should be redirected to Googleā€™s consent screen.

Thanks,
Charly

Alright, new version. Tell me if it works in your case

EDIT: removed output = st.empty(). Now you just have to put streamlitā€™s function name as parameter.

from contextlib import contextmanager
from io import StringIO
from streamlit.report_thread import REPORT_CONTEXT_ATTR_NAME
from threading import current_thread
import streamlit as st
import sys


@contextmanager
def st_redirect(src, dst):
    placeholder = st.empty()
    output_func = getattr(placeholder, dst)

    with StringIO() as buffer:
        old_write = src.write

        def new_write(b):
            if getattr(current_thread(), REPORT_CONTEXT_ATTR_NAME, None):
                buffer.write(b)
                output_func(buffer.getvalue())
            else:
                old_write(b)

        try:
            src.write = new_write
            yield
        finally:
            src.write = old_write


@contextmanager
def st_stdout(dst):
    with st_redirect(sys.stdout, dst):
        yield


@contextmanager
def st_stderr(dst):
    with st_redirect(sys.stderr, dst):
        yield


with st_stdout("code"):
    print("Prints as st.code()")

with st_stdout("info"):
    print("Prints as st.info()")

with st_stdout("markdown"):
    print("Prints as st.markdown()")

with st_stdout("success"), st_stderr("error"):
    print("You can print regular success messages")
    print("And you can redirect errors as well at the same time", file=sys.stderr)

3 Likes

Thanks! Will try now! :slight_smile:

Hi Synode.

Thanks again for taking the time to write this code, much appreciated! :pray:

For some reason, I still canā€™t get the message from my terminal printed.

Iā€™ve put together a little mock-up which may shed a bit more light on what Iā€™m after :slight_smile:

  • #1 Your code, which I pasted at the end of my script.
  • #2 the terminal text I want printed
  • #3 the terminal text doesnā€™t print in Streamlit
  • #4 FYI the Google Consent Screen which appears locally gets blocked when deployed in Heroku or Streamlit Sharing - This is the main reason why I want the terminal message to be printed :slight_smile:

Note that Iā€™ve pasted your code in my script without any modifications - Maybe I should have added something to it?

Thanks in advance :slight_smile:

Charly

Love those mock-ups :laughing:

My guess is that your google API does some sort of print (which uses sys.stdout internally).
What my proof-of-concept does is redirect sys.stdout to a streamlit function of your choice.

To make it work in your case, you have to put your functions that print messages in your terminal inside that kind of code block:

with st_stdout("info"):
    # Inside this block, every function that prints to your terminal
    # will have their output redirected to output.info(), a streamlit function.

I guess that the function which prints to your terminal is searchconsole.authenticate(), right? Youā€™d do something like this:

with st_stdout("info"):
    searchconsole.authenticate(client_config="GSCTatieLouCredentials.json", serialize='credentials.json', flow="console")
1 Like

Small notice, Iā€™ve edited my two last replies. Iā€™ve updated the code to avoid using that output = st.empty() in your code.

1 Like

Thanks Synode! It works great! :raised_hands:

Iā€™ve added your suggested code as the solution.

On a rather cosmetic note, I was wondering whether the displayed text could be ā€œwrappedā€?

Reason being: The URL that is retrieved from the terminal is very long and far overflows the boxā€™s bounds, if that makes sense.

Thanks,
Charly

The only streamlit element that could work in your case is ā€œcodeā€ I guess:

with st_stdout("code"):
    # Your function

At least it wonā€™t overflow the boxā€™s bounds.

1 Like

Nice, thanks Synode!

That would be ideal yet I assume itā€™s not possible to wrap Python variables in a Markdown hyperlink via st.markdown?

Hi All,
Trying to fit this proof of concept into my use case, which involves tracking the number of iterations through a function, think:

for x in range(10):

Iā€™m using with st_stdout(ā€œsuccessā€): to print out x and Iā€™m getting a success box that looks like

0 1 2 3 4 5 6 7 8 9

Ideally I would just get the current x value, and previous values would be overwritten, Iā€™ve tried carriage returns and flushing stdout to no avail.

Any thoughts are much appreciated.

Is there any way to also print the captured output to the terminal? Now I only get visual output, but I lose terminal loggers.

I guess you could wrap it in

print(stuff)
with st_stdout():
    print(stuff)

and wrap it in print_here_and_there(obj) or even print = print_here_and_there if you so choose, but it seems a bitā€¦ I donā€™t knowā€¦ roundabout?

Edit:
I did it anyway, and itā€™s actually not half bad:

def print_st(*args: Any) -> None:
    for arg in args:
        print(arg, "\n")
    with st_stdout():
        for arg in args:
            print(arg, "\n")

Now Streamlit and the terminal are completely in sync.

1 Like

You could also remove that else statement from my new_write() function:

# Before
def new_write(b):
    if getattr(current_thread(), REPORT_CONTEXT_ATTR_NAME, None):
        buffer.write(b)
        output_func(buffer.getvalue())
    else:
        old_write(b)

# After
def new_write(b):
    if getattr(current_thread(), REPORT_CONTEXT_ATTR_NAME, None):
        buffer.write(b)
        output_func(buffer.getvalue())
    old_write(b)
1 Like

Hola Synode!

Is it possible to display coutdown timer via your above helpers?

See the code below:

import time
import datetime
import os

_clear_cmd = 'cls' if os.name == 'nt' else 'clear'      

def test(): 
  d0 = datetime.datetime.now()
  d1 = datetime.datetime(2021, 3, 15)
  days_dif = d1 - d0
  seconds = int(days_dif.total_seconds())
  while seconds:
    days = seconds // (24 * 60 * 60)
    hours = (seconds-days * 86400) // 3600
    minutes = (seconds-days * 86400-hours*3600) // 60
    seconds_timer = (seconds-days *86400-hours*3600-minutes*60)
    timer = (f"{days} days {hours} hours {minutes} minutes and {seconds_timer} seconds")
    print('test')

    print(timer, end='\r')
    time.sleep(1)
    os.system(_clear_cmd)
    seconds -= 1   

    if d0 >= d1:
      print('test', end='\n')
      break     

test()

Thanks,
Charly