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

Formatting of stdout stream #720

Open
TobiasEnergyMachines opened this issue Feb 2, 2024 · 2 comments
Open

Formatting of stdout stream #720

TobiasEnergyMachines opened this issue Feb 2, 2024 · 2 comments
Assignees

Comments

@TobiasEnergyMachines
Copy link

TobiasEnergyMachines commented Feb 2, 2024

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

@mscolnick
Copy link
Contributor

mscolnick commented Feb 2, 2024

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?

@TobiasEnergyMachines
Copy link
Author

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

@akshayka akshayka self-assigned this Feb 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants