0

I'm using the InteractiveInterpreter to execute code that has delays in it (see below). I'd like to capture the output from stdout as the output is written. I'm currently using contextlib to redirect stdout along with StringIO to buffer the output:

code = """import time
for i in range(3):
    print(i)
    time.sleep(1)"""

f = io.StringIO()
inter = InteractiveInterpreter()

with redirect_stdout(f):
    inter.runcode(code)

out = f.getvalue()
print(out)

Naturally, since runcode runs synchronously, the output is only available after the code finishes executing. I'd like to capture the output as it becomes available and do something with it (this code is running in a gRPC environment, so yielding the output in real time).

My initial thought was to wrap the running of the code and reading the output in two separate asyncio coroutines and somehow have stdout write to a stream and have the other task read from that stream.

Is the async approach feasible/reasonable? Any other suggestions? Thanks.

naivedeveloper
  • 2,814
  • 8
  • 34
  • 43

1 Answers1

1

Borrowing from the custom file object suggested here, the code snippet below will allow you to do anything with each captured output.

from code import InteractiveInterpreter
from contextlib import redirect_stdout
import sys


code = """import time
for i in range(3):
    print(i)
    time.sleep(1)"""


real_stdout_for_test = sys.stdout


def do_something_with_output(output: str) -> None:
    """Handling each line of output"""
    real_stdout_for_test.write('fiddled with ' + output)


class WriteProcessor:
    # borrowed from https://stackoverflow.com/a/71025286/9723036
    def __init__(self):
        self.buf = ""

    def write(self, buf):
        # emit on each newline
        while buf:
            try:
                newline_index = buf.index("\n")
            except ValueError:
                # no newline, buffer for next call
                self.buf += buf
                break
            # get data to next newline and combine with any buffered data
            data = self.buf + buf[:newline_index + 1]
            self.buf = ""
            buf = buf[newline_index + 1:]
            # perform complex calculations... or just print with a note.
            do_something_with_output(data)
    

inter = InteractiveInterpreter()

with redirect_stdout(WriteProcessor()):
    inter.runcode(code)

Caveat

WriteProcessor uses new line as the indicator for a print call. However, if the printed string contains new line in itself, the original printed output will not be handled as a whole, but one line at a time.

Fanchen Bao
  • 3,310
  • 1
  • 21
  • 34