'Is there any (non-blocking) native python solution to force unbuffer stdout of a process (to get output like a console does, in real-time)?

There's is no shortage of questions on SO about getting real-time stdout from a process with python. We also need it to be non-blocking, so:

while True:
  line = p.stdout.readline()
  if not line: break
  ...

...is not an option. Edit: Actually testing this solution now, it doesn't even work.

It's not even quite clear why that works and non-blocking versions using bufsize=1 don't. This is not a duplicate of Getting realtime output using subprocess, as none of the solutions suggested there seem to apply to a non-blocking version of the function. I've tested them. Not sure why bufsize=1 does not work.

There doesn't actually seem to be any solution now, because as the author of that answer cited above points out, it might have been a bug to begin with.

There are other questions about non-blocking versions of the the function too, but no question I've found asks for / receives answers to the problem of both non-blocking and forced unbuffering.

Here's a non-blocking func to run a command and poll for output:

import subprocess
from set_interval import set_interval

def call_command(cmd, callback=None):

    try:
        p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, encoding='utf-8', bufsize=1)

        def polling():

            for line in iter(p.stdout.readline, ''):

                if p.returncode != 0 or (not line and p.poll() is not None):
                    p.kill()
                    return False # stop interval
                else:
                    if callback is not None:
                        callback(line.strip())
                    else:
                        print(line.strip())
                    return True # continue interval

        set_interval(polling, 1)

        if p.returncode != 0:
            err = p.communicate()
            print(err[1].strip())

    except FileNotFoundError as e:
        print(f'{e} "{cmd[0]}"')

set_interval.py, referenced above:

import threading

def set_interval(func, sec):
    result = func()

    def func_wrapper():

        if result is True:
            set_interval(func, sec)
        
    t = threading.Timer(sec, func_wrapper)
    t.start()

The challenge we're facing is let's say the program generating output isn't flushing the output buffer:

output_unflushed.py:

from time import sleep
for i in range(10):
    print('i:', i)
    sleep(1)

Well if you do call_command('python output_unflushed.py'.split(' ')) you'll get no output for 10 seconds, then you'll get it all at once. This is because Python is just buffering all the stdout. As @radekholy24 pointed out we can fix that easily by modifying output_unflushed.py:

from time import sleep
import sys

for i in range(10):
    print('i:', i)
    sys.stdout.flush()
    sleep(1)

Unfortunately, we don't control the source of every process we want to run. Maybe we do, but not always/easily. For example, git clone seems to suffer from this same issue.

There are binaries that can be used to solve this (in theory, I tried the winpty solution and it didn't work for me), but that adds a lot of complexity:

This feels like major overkill, complicating my code, running it through another program, having to involve mingw dlls, etc. No thanks.

Also PYTHONUNBUFFERED=1 and similar process-specific solutions aren't what I'm interested in.

Is there any way to accomplish this with Python?

This must be possible in universally applicable way, since obviously the command prompts all manage it. Whether it's possible in Python (with non-blocking code) is a different story...



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source