[logging] Tuning windows vs unix display output.

This commit is contained in:
Romain Dorgueil
2017-07-16 11:15:40 +02:00
3 changed files with 38 additions and 20 deletions

View File

@ -58,7 +58,10 @@ def run(graph, strategy=None, plugins=None, services=None):
from bonobo.ext.jupyter import JupyterOutputPlugin from bonobo.ext.jupyter import JupyterOutputPlugin
except ImportError: except ImportError:
logging.warning( logging.warning(
'Failed to load jupyter widget. Easiest way is to install the optional "jupyter" ' 'dependencies with «pip install bonobo[jupyter]», but you can also install a specific ' 'version by yourself.') 'Failed to load jupyter widget. Easiest way is to install the optional "jupyter" '
'dependencies with «pip install bonobo[jupyter]», but you can also install a specific '
'version by yourself.'
)
else: else:
if JupyterOutputPlugin not in plugins: if JupyterOutputPlugin not in plugins:
plugins.append(JupyterOutputPlugin) plugins.append(JupyterOutputPlugin)

View File

@ -2,7 +2,9 @@ import io
import sys import sys
from contextlib import redirect_stdout from contextlib import redirect_stdout
from colorama import Style, Fore from colorama import Style, Fore, init
init(wrap=True)
from bonobo import settings from bonobo import settings
from bonobo.plugins import Plugin from bonobo.plugins import Plugin
@ -10,6 +12,13 @@ from bonobo.util.term import CLEAR_EOL, MOVE_CURSOR_UP
class IOBuffer(): class IOBuffer():
"""
The role of IOBuffer is to overcome the problem of multiple threads wanting to write to stdout at the same time. It
works a bit like a videogame: there are two buffers, one that is used to write, and one which is used to read from.
On each cycle, we swap the buffers, and the console plugin handle output of the one which is not anymore "active".
"""
def __init__(self): def __init__(self):
self.current = io.StringIO() self.current = io.StringIO()
self.write = self.current.write self.write = self.current.write
@ -32,6 +41,9 @@ class ConsoleOutputPlugin(Plugin):
Outputs status information to the connected stdout. Can be a TTY, with or without support for colors/cursor Outputs status information to the connected stdout. Can be a TTY, with or without support for colors/cursor
movements, or a non tty (pipe, file, ...). The features are adapted to terminal capabilities. movements, or a non tty (pipe, file, ...). The features are adapted to terminal capabilities.
On Windows, we'll play a bit differently because we don't know how to manipulate cursor position. We'll only
display stats at the very end, and there won't be this "buffering" logic we need to display both stats and stdout.
.. attribute:: prefix .. attribute:: prefix
String prefix of output lines. String prefix of output lines.
@ -43,17 +55,18 @@ class ConsoleOutputPlugin(Plugin):
self.counter = 0 self.counter = 0
self._append_cache = '' self._append_cache = ''
self.isatty = sys.stdout.isatty() self.isatty = sys.stdout.isatty()
self.iswindows = (sys.platform == 'win32')
self._stdout = sys.stdout self._stdout = sys.stdout
self.stdout = IOBuffer() self.stdout = IOBuffer()
self.redirect_stdout = redirect_stdout(self.stdout) self.redirect_stdout = redirect_stdout(self._stdout if self.iswindows else self.stdout)
self.redirect_stdout.__enter__() self.redirect_stdout.__enter__()
def run(self): def run(self):
if self.isatty: if self.isatty and not self.iswindows:
self._write(self.context.parent, rewind=True) self._write(self.context.parent, rewind=True)
else: else:
pass # not a tty pass # not a tty, or windows, so we'll ignore stats output
def finalize(self): def finalize(self):
self._write(self.context.parent, rewind=False) self._write(self.context.parent, rewind=False)
@ -62,9 +75,13 @@ class ConsoleOutputPlugin(Plugin):
def write(self, context, prefix='', rewind=True, append=None): def write(self, context, prefix='', rewind=True, append=None):
t_cnt = len(context) t_cnt = len(context)
buffered = self.stdout.switch() if not self.iswindows:
for line in buffered.split('\n')[:-1]: buffered = self.stdout.switch()
print(line + CLEAR_EOL, file=sys.stderr) for line in buffered.split('\n')[:-1]:
print(line + CLEAR_EOL, file=sys.stderr)
alive_color = Style.BRIGHT
dead_color = (Style.BRIGHT + Fore.BLACK) if self.iswindows else Fore.BLACK
for i in context.graph.topologically_sorted_indexes: for i in context.graph.topologically_sorted_indexes:
node = context[i] node = context[i]
@ -72,14 +89,14 @@ class ConsoleOutputPlugin(Plugin):
if node.alive: if node.alive:
_line = ''.join( _line = ''.join(
( (
' ', Style.BRIGHT, '+', Style.RESET_ALL, ' ', node.name, name_suffix, ' ', ' ', alive_color, '+', Style.RESET_ALL, ' ', node.name, name_suffix, ' ',
node.get_statistics_as_string(), Style.RESET_ALL, ' ', node.get_statistics_as_string(), Style.RESET_ALL, ' ',
) )
) )
else: else:
_line = ''.join( _line = ''.join(
( (
' ', Fore.BLACK, '-', ' ', node.name, name_suffix, ' ', node.get_statistics_as_string(), ' ', dead_color, '-', ' ', node.name, name_suffix, ' ', node.get_statistics_as_string(),
Style.RESET_ALL, ' ', Style.RESET_ALL, ' ',
) )
) )

View File

@ -78,8 +78,7 @@ def test_install_requirements_for_dir(runner):
dirname = get_examples_path('types') dirname = get_examples_path('types')
with patch('bonobo.commands.run._install_requirements') as install_mock: with patch('bonobo.commands.run._install_requirements') as install_mock:
runner('run', '--install', dirname) runner('run', '--install', dirname)
install_mock.assert_called_once_with( install_mock.assert_called_once_with(os.path.join(dirname, 'requirements.txt'))
os.path.join(dirname, 'requirements.txt'))
@all_runners @all_runners
@ -87,8 +86,7 @@ def test_install_requirements_for_file(runner):
dirname = get_examples_path('types') dirname = get_examples_path('types')
with patch('bonobo.commands.run._install_requirements') as install_mock: with patch('bonobo.commands.run._install_requirements') as install_mock:
runner('run', '--install', os.path.join(dirname, 'strings.py')) runner('run', '--install', os.path.join(dirname, 'strings.py'))
install_mock.assert_called_once_with( install_mock.assert_called_once_with(os.path.join(dirname, 'requirements.txt'))
os.path.join(dirname, 'requirements.txt'))
@all_runners @all_runners