[logging] Tuning windows vs unix display output.
This commit is contained in:
@ -23,17 +23,17 @@ def register_api_group(*args):
|
|||||||
def run(graph, strategy=None, plugins=None, services=None):
|
def run(graph, strategy=None, plugins=None, services=None):
|
||||||
"""
|
"""
|
||||||
Main entry point of bonobo. It takes a graph and creates all the necessary plumbery around to execute it.
|
Main entry point of bonobo. It takes a graph and creates all the necessary plumbery around to execute it.
|
||||||
|
|
||||||
The only necessary argument is a :class:`Graph` instance, containing the logic you actually want to execute.
|
The only necessary argument is a :class:`Graph` instance, containing the logic you actually want to execute.
|
||||||
|
|
||||||
By default, this graph will be executed using the "threadpool" strategy: each graph node will be wrapped in a
|
By default, this graph will be executed using the "threadpool" strategy: each graph node will be wrapped in a
|
||||||
thread, and executed in a loop until there is no more input to this node.
|
thread, and executed in a loop until there is no more input to this node.
|
||||||
|
|
||||||
You can provide plugins factory objects in the plugins list, this function will add the necessary plugins for
|
You can provide plugins factory objects in the plugins list, this function will add the necessary plugins for
|
||||||
interactive console execution and jupyter notebook execution if it detects correctly that it runs in this context.
|
interactive console execution and jupyter notebook execution if it detects correctly that it runs in this context.
|
||||||
|
|
||||||
You'll probably want to provide a services dictionary mapping service names to service instances.
|
You'll probably want to provide a services dictionary mapping service names to service instances.
|
||||||
|
|
||||||
:param Graph graph: The :class:`Graph` to execute.
|
:param Graph graph: The :class:`Graph` to execute.
|
||||||
:param str strategy: The :class:`bonobo.strategies.base.Strategy` to use.
|
:param str strategy: The :class:`bonobo.strategies.base.Strategy` to use.
|
||||||
:param list plugins: The list of plugins to enhance execution.
|
:param list plugins: The list of plugins to enhance execution.
|
||||||
@ -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)
|
||||||
@ -78,7 +81,7 @@ register_api(create_strategy)
|
|||||||
def open_fs(fs_url=None, *args, **kwargs):
|
def open_fs(fs_url=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Wraps :func:`fs.open_fs` function with a few candies.
|
Wraps :func:`fs.open_fs` function with a few candies.
|
||||||
|
|
||||||
:param str fs_url: A filesystem URL
|
:param str fs_url: A filesystem URL
|
||||||
:param parse_result: A parsed filesystem URL.
|
:param parse_result: A parsed filesystem URL.
|
||||||
:type parse_result: :class:`ParseResult`
|
:type parse_result: :class:`ParseResult`
|
||||||
|
|||||||
@ -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, ' ',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user