marimo
marimo copied to clipboard
Formatting of stdout stream
Description
Hi Marimo team
Thanks for the fix for "plotly mapbox styles".
I am running an executable using subprocess.Popen and would like to stream the stdout "live" (the process sometimes run up to minutes and I would like to see the stdout stream as soon as it is generated from the executable) . I have attempted to use https://docs.marimo.io/api/outputs.html#marimo.redirect_stdout but I find it limiting since:
-I am not able to stream the stdout into a text-field or accordion (would like to get the stdout stream into a tab). -I am not able to limit the length of the output view in app mode (like there is limit to cell output in edit mode).
Would you be able to help with this?
Best regards Tobias
Example
Example of capturing subprocess stdout with marimo.redirect_stdout(). This showcases current limitations:
import marimo
__generated_with = "0.2.0"
app = marimo.App()
@app.cell
def __():
import marimo as mo
from subprocess import PIPE, Popen
return PIPE, Popen, mo
@app.cell
def __(mo):
button = mo.ui.button(
label="Run/Reset subprocess", value=False, on_click=lambda value: not value)
button
return button,
@app.cell
def __(PIPE, Popen, button, mo):
mo.stop(button.value == False)
with mo.redirect_stdout():
args = ["timeout", "20"]
with Popen(args, stdout=PIPE) as p:
while True:
text = p.stdout.read1().decode("utf-8")
print(text, end="", flush=True)
if p.poll() is not None:
break
return args, p, text
if __name__ == "__main__":
app.run()
Alternative
No response
Additional context
No response
hi @TobiasEnergyMachines, you might find better success using mo.capture_stdout() to capture the request and then mo.output.append() or mo.output.replace() (outputs)
Can you give those a try (referencing the docs) and let us know if something could be improved?
Thank you for the suggestions. I have tried to use mo.capture_stdout() together with mo.output.append() and mo.output.replace(). In both cases I find that the output from the subprocess is only printed after completion of the subprocess (i.e. blocking). This can be verified from this example:
import marimo
__generated_with = "0.2.1"
app = marimo.App()
@app.cell
def __():
import marimo as mo
from subprocess import Popen, PIPE
return PIPE, Popen, mo
@app.cell
def __(PIPE, Popen):
def run_args(args: list):
output = ""
with Popen(args, stdout=PIPE) as p:
while True:
text = p.stdout.read1().decode("utf-8")
print(text, end="", flush=True)
output += text
if p.poll() is not None:
break
return output
return run_args,
@app.cell
def __():
args = ["timeout", "5"] # "placeholder for executable" subprocess running for 5 seconds
return args,
@app.cell
def __(mo):
button = mo.ui.button(
label="Run/Reset subprocess", value=False, on_click=lambda value: not value)
button
return button,
@app.cell
def __(mo):
mo.md("## Using mo.output.replace()")
return
@app.cell
def __(args, button, mo, run_args):
if button.value:
with mo.capture_stdout() as buffer1:
run_args(args)
mo.output.replace(buffer1.getvalue())
return buffer1,
@app.cell
def __(mo):
mo.md("## Using mo.output.append()")
return
@app.cell
def __(args, button, mo, run_args):
if button.value:
with mo.capture_stdout() as buffer2:
run_args(args)
mo.output.append(buffer2.getvalue())
return buffer2,
if __name__ == "__main__":
app.run()
The closest thing I got to a solution is the following example. The issue I have with this solution is that it is overriding the cell output when it is streaming the subprocess output. Also it is resetting the tab selection which is not ideal:
import marimo
__generated_with = "0.2.1"
app = marimo.App()
@app.cell
def __():
import marimo as mo
from subprocess import Popen, PIPE
return PIPE, Popen, mo
@app.cell
def __(PIPE, Popen):
def run_args(args: list):
output = ""
with Popen(args, stdout=PIPE) as p:
while True:
text = p.stdout.read1().decode("utf-8")
print(text, end="", flush=True)
output += text
if p.poll() is not None:
break
return output
return run_args,
@app.cell
def __():
args = ["timeout", "5"] # "placeholder for executable" subprocess running for 5 seconds
return args,
@app.cell
def __(mo):
button = mo.ui.button(
label="Run/Reset subprocess", value=False, on_click=lambda value: not value)
return button,
@app.cell
def __(mo, run_args):
def stream_and_save_output(_run_, _args_):
if _run_:
with mo.redirect_stdout():
output = run_args(_args_)
return output
else:
return "Press run to stream output!"
return stream_and_save_output,
@app.cell
def __(args, button, mo, stream_and_save_output):
mo.tabs({
"Inputs": mo.plain_text("Provide simulation inputs"),
"Simulate": mo.vstack(items=[
button,
stream_and_save_output(button.value, args)
]),
"Analyze": mo.plain_text("Analyze simulation outputs"),
})
return
if __name__ == "__main__":
app.run()
Best regards Tobias