Merge branch 'dev_graphviz' into dev_convert
This commit is contained in:
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at bonobo@rdc.li. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
1
CONTRIBUTING.md
Normal file
1
CONTRIBUTING.md
Normal file
@ -0,0 +1 @@
|
||||
See http://docs.bonobo-project.org/en/latest/contribute/index.html
|
||||
6
Makefile
6
Makefile
@ -1,7 +1,7 @@
|
||||
# This file has been auto-generated.
|
||||
# All changes will be lost, see Projectfile.
|
||||
#
|
||||
# Updated at 2017-07-16 18:44:56.169119
|
||||
# Updated at 2017-09-18 17:18:20.676696
|
||||
|
||||
PACKAGE ?= bonobo
|
||||
PYTHON ?= $(shell which python)
|
||||
@ -27,13 +27,13 @@ VERSION ?= $(shell git describe 2>/dev/null || echo dev)
|
||||
# Installs the local project dependencies.
|
||||
install:
|
||||
if [ -z "$(QUICK)" ]; then \
|
||||
$(PIP) install -U pip wheel $(PYTHON_PIP_INSTALL_OPTIONS) -r $(PYTHON_REQUIREMENTS_FILE) ; \
|
||||
$(PIP) install -U pip wheel $(PIP_INSTALL_OPTIONS) -r $(PYTHON_REQUIREMENTS_FILE) ; \
|
||||
fi
|
||||
|
||||
# Installs the local project dependencies, including development-only libraries.
|
||||
install-dev:
|
||||
if [ -z "$(QUICK)" ]; then \
|
||||
$(PIP) install -U pip wheel $(PYTHON_PIP_INSTALL_OPTIONS) -r $(PYTHON_REQUIREMENTS_DEV_FILE) ; \
|
||||
$(PIP) install -U pip wheel $(PIP_INSTALL_OPTIONS) -r $(PYTHON_REQUIREMENTS_DEV_FILE) ; \
|
||||
fi
|
||||
|
||||
# Cleans up the local mess.
|
||||
|
||||
@ -31,6 +31,7 @@ python.setup(
|
||||
'bonobo.commands': [
|
||||
'convert = bonobo.commands.convert:register',
|
||||
'init = bonobo.commands.init:register',
|
||||
'graph = bonobo.commands.graph:register',
|
||||
'run = bonobo.commands.run:register',
|
||||
'version = bonobo.commands.version:register',
|
||||
],
|
||||
@ -57,3 +58,5 @@ python.add_requirements(
|
||||
'ipywidgets >=6.0.0,<7',
|
||||
]
|
||||
)
|
||||
|
||||
# vim: ft=python:
|
||||
|
||||
@ -55,6 +55,8 @@ Homepage: https://www.bonobo-project.org/ (`Roadmap <https://www.bonobo-project.
|
||||
|
||||
Documentation: http://docs.bonobo-project.org/
|
||||
|
||||
Contributing guide: http://docs.bonobo-project.org/en/latest/contribute/index.html
|
||||
|
||||
Issues: https://github.com/python-bonobo/bonobo/issues
|
||||
|
||||
Slack: https://bonobo-slack.herokuapp.com/
|
||||
@ -64,7 +66,7 @@ Release announcements: http://eepurl.com/csHFKL
|
||||
----
|
||||
|
||||
Made with ♥ by `Romain Dorgueil <https://twitter.com/rdorgueil>`_ and `contributors <https://github.com/python-bonobo/bonobo/graphs/contributors>`_.
|
||||
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/bonobo.svg
|
||||
:target: https://pypi.python.org/pypi/bonobo
|
||||
:alt: License
|
||||
|
||||
112
bin/imgcat
Executable file
112
bin/imgcat
Executable file
@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
|
||||
# tmux requires unrecognized OSC sequences to be wrapped with DCS tmux;
|
||||
# <sequence> ST, and for all ESCs in <sequence> to be replaced with ESC ESC. It
|
||||
# only accepts ESC backslash for ST.
|
||||
function print_osc() {
|
||||
if [[ $TERM == screen* ]] ; then
|
||||
printf "\033Ptmux;\033\033]"
|
||||
else
|
||||
printf "\033]"
|
||||
fi
|
||||
}
|
||||
|
||||
# More of the tmux workaround described above.
|
||||
function print_st() {
|
||||
if [[ $TERM == screen* ]] ; then
|
||||
printf "\a\033\\"
|
||||
else
|
||||
printf "\a"
|
||||
fi
|
||||
}
|
||||
|
||||
# print_image filename inline base64contents print_filename
|
||||
# filename: Filename to convey to client
|
||||
# inline: 0 or 1
|
||||
# base64contents: Base64-encoded contents
|
||||
# print_filename: If non-empty, print the filename
|
||||
# before outputting the image
|
||||
function print_image() {
|
||||
print_osc
|
||||
printf '1337;File='
|
||||
if [[ -n "$1" ]]; then
|
||||
printf 'name='`printf "%s" "$1" | base64`";"
|
||||
fi
|
||||
|
||||
VERSION=$(base64 --version 2>&1)
|
||||
if [[ "$VERSION" =~ fourmilab ]]; then
|
||||
BASE64ARG=-d
|
||||
elif [[ "$VERSION" =~ GNU ]]; then
|
||||
BASE64ARG=-di
|
||||
else
|
||||
BASE64ARG=-D
|
||||
fi
|
||||
|
||||
printf "%s" "$3" | base64 $BASE64ARG | wc -c | awk '{printf "size=%d",$1}'
|
||||
printf ";inline=$2"
|
||||
printf ":"
|
||||
printf "%s" "$3"
|
||||
print_st
|
||||
printf '\n'
|
||||
if [[ -n "$4" ]]; then
|
||||
echo $1
|
||||
fi
|
||||
}
|
||||
|
||||
function error() {
|
||||
echo "ERROR: $*" 1>&2
|
||||
}
|
||||
|
||||
function show_help() {
|
||||
echo "Usage: imgcat [-p] filename ..." 1>& 2
|
||||
echo " or: cat filename | imgcat" 1>& 2
|
||||
}
|
||||
|
||||
## Main
|
||||
|
||||
if [ -t 0 ]; then
|
||||
has_stdin=f
|
||||
else
|
||||
has_stdin=t
|
||||
fi
|
||||
|
||||
# Show help if no arguments and no stdin.
|
||||
if [ $has_stdin = f -a $# -eq 0 ]; then
|
||||
show_help
|
||||
exit
|
||||
fi
|
||||
|
||||
# Look for command line flags.
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--h|--help)
|
||||
show_help
|
||||
exit
|
||||
;;
|
||||
-p|--p|--print)
|
||||
print_filename=1
|
||||
;;
|
||||
-*)
|
||||
error "Unknown option flag: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [ -r "$1" ] ; then
|
||||
has_stdin=f
|
||||
print_image "$1" 1 "$(base64 < "$1")" "$print_filename"
|
||||
else
|
||||
error "imgcat: $1: No such file or directory"
|
||||
exit 2
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Read and print stdin
|
||||
if [ $has_stdin = t ]; then
|
||||
print_image "" 1 "$(cat | base64)" ""
|
||||
fi
|
||||
|
||||
exit 0
|
||||
1
bin/test_graph
Normal file
1
bin/test_graph
Normal file
@ -0,0 +1 @@
|
||||
bonobo graph bonobo/examples/tutorials/tut02_03_writeasmap.py | dot -otest.png -Tpng && bin/imgcat test.png
|
||||
@ -1,6 +1,8 @@
|
||||
import logging
|
||||
|
||||
from bonobo.structs import Bag, Graph, Token
|
||||
from bonobo.nodes import CsvReader, CsvWriter, FileReader, FileWriter, Filter, JsonReader, JsonWriter, Limit, \
|
||||
PrettyPrinter, PickleWriter, PickleReader, RateLimited, Tee, count, identity, noop
|
||||
PickleReader, PickleWriter, PrettyPrinter, RateLimited, Tee, arg0_to_kwargs, count, identity, kwargs_to_arg0, noop
|
||||
from bonobo.strategies import create_strategy
|
||||
from bonobo.util.objects import get_name
|
||||
|
||||
@ -21,17 +23,17 @@ def register_api_group(*args):
|
||||
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.
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
|
||||
:param Graph graph: The :class:`Graph` to execute.
|
||||
:param str strategy: The :class:`bonobo.strategies.base.Strategy` to use.
|
||||
:param list plugins: The list of plugins to enhance execution.
|
||||
@ -52,9 +54,17 @@ def run(graph, strategy=None, plugins=None, services=None):
|
||||
plugins.append(ConsoleOutputPlugin)
|
||||
|
||||
if _is_jupyter_notebook():
|
||||
from bonobo.ext.jupyter import JupyterOutputPlugin
|
||||
if JupyterOutputPlugin not in plugins:
|
||||
plugins.append(JupyterOutputPlugin)
|
||||
try:
|
||||
from bonobo.ext.jupyter import JupyterOutputPlugin
|
||||
except ImportError:
|
||||
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.'
|
||||
)
|
||||
else:
|
||||
if JupyterOutputPlugin not in plugins:
|
||||
plugins.append(JupyterOutputPlugin)
|
||||
|
||||
return strategy.execute(graph, plugins=plugins, services=services)
|
||||
|
||||
@ -71,7 +81,7 @@ register_api(create_strategy)
|
||||
def open_fs(fs_url=None, *args, **kwargs):
|
||||
"""
|
||||
Wraps :func:`fs.open_fs` function with a few candies.
|
||||
|
||||
|
||||
:param str fs_url: A filesystem URL
|
||||
:param parse_result: A parsed filesystem URL.
|
||||
:type parse_result: :class:`ParseResult`
|
||||
@ -101,13 +111,15 @@ register_api_group(
|
||||
JsonReader,
|
||||
JsonWriter,
|
||||
Limit,
|
||||
PrettyPrinter,
|
||||
PickleReader,
|
||||
PickleWriter,
|
||||
PrettyPrinter,
|
||||
RateLimited,
|
||||
Tee,
|
||||
arg0_to_kwargs,
|
||||
count,
|
||||
identity,
|
||||
kwargs_to_arg0,
|
||||
noop,
|
||||
)
|
||||
|
||||
|
||||
32
bonobo/commands/graph.py
Normal file
32
bonobo/commands/graph.py
Normal file
@ -0,0 +1,32 @@
|
||||
import json
|
||||
|
||||
import itertools
|
||||
|
||||
from bonobo.util.objects import get_name
|
||||
from bonobo.commands.run import read, register_generic_run_arguments
|
||||
from bonobo.constants import BEGIN
|
||||
|
||||
|
||||
def execute(filename, module, install=False, quiet=False, verbose=False):
|
||||
graph, plugins, services = read(filename, module, install, quiet, verbose)
|
||||
|
||||
print('digraph {')
|
||||
print(' rankdir = LR;')
|
||||
print(' "BEGIN" [shape="point"];')
|
||||
|
||||
for i in graph.outputs_of(BEGIN):
|
||||
print(' "BEGIN" -> ' + json.dumps(get_name(graph[i])) + ';')
|
||||
|
||||
for ix in graph.topologically_sorted_indexes:
|
||||
for iy in graph.outputs_of(ix):
|
||||
print(' {} -> {};'.format(
|
||||
json.dumps(get_name(graph[ix])),
|
||||
json.dumps(get_name(graph[iy]))
|
||||
))
|
||||
|
||||
print('}')
|
||||
|
||||
|
||||
def register(parser):
|
||||
register_generic_run_arguments(parser)
|
||||
return execute
|
||||
@ -1,9 +1,9 @@
|
||||
import os
|
||||
|
||||
DEFAULT_SERVICES_FILENAME = '_services.py'
|
||||
DEFAULT_SERVICES_ATTR = 'get_services'
|
||||
import bonobo
|
||||
from bonobo.constants import DEFAULT_SERVICES_ATTR, DEFAULT_SERVICES_FILENAME
|
||||
|
||||
DEFAULT_GRAPH_FILENAMES = ('__main__.py', 'main.py', )
|
||||
DEFAULT_GRAPH_FILENAMES = ('__main__.py', 'main.py',)
|
||||
DEFAULT_GRAPH_ATTR = 'get_graph'
|
||||
|
||||
|
||||
@ -26,9 +26,23 @@ def get_default_services(filename, services=None):
|
||||
return services or {}
|
||||
|
||||
|
||||
def execute(filename, module, install=False, quiet=False, verbose=False):
|
||||
def _install_requirements(requirements):
|
||||
"""Install requirements given a path to requirements.txt file."""
|
||||
import importlib
|
||||
import pip
|
||||
|
||||
pip.main(['install', '-r', requirements])
|
||||
# Some shenanigans to be sure everything is importable after this, especially .egg-link files which
|
||||
# are referenced in *.pth files and apparently loaded by site.py at some magic bootstrap moment of the
|
||||
# python interpreter.
|
||||
pip.utils.pkg_resources = importlib.reload(pip.utils.pkg_resources)
|
||||
import site
|
||||
importlib.reload(site)
|
||||
|
||||
|
||||
def read(filename, module, install=False, quiet=False, verbose=False):
|
||||
import runpy
|
||||
from bonobo import Graph, run, settings
|
||||
from bonobo import Graph, settings
|
||||
|
||||
if quiet:
|
||||
settings.QUIET.set(True)
|
||||
@ -39,16 +53,8 @@ def execute(filename, module, install=False, quiet=False, verbose=False):
|
||||
if filename:
|
||||
if os.path.isdir(filename):
|
||||
if install:
|
||||
import importlib
|
||||
import pip
|
||||
requirements = os.path.join(filename, 'requirements.txt')
|
||||
pip.main(['install', '-r', requirements])
|
||||
# Some shenanigans to be sure everything is importable after this, especially .egg-link files which
|
||||
# are referenced in *.pth files and apparently loaded by site.py at some magic bootstrap moment of the
|
||||
# python interpreter.
|
||||
pip.utils.pkg_resources = importlib.reload(pip.utils.pkg_resources)
|
||||
import site
|
||||
importlib.reload(site)
|
||||
_install_requirements(requirements)
|
||||
|
||||
pathname = filename
|
||||
for filename in DEFAULT_GRAPH_FILENAMES:
|
||||
@ -58,7 +64,8 @@ def execute(filename, module, install=False, quiet=False, verbose=False):
|
||||
if not os.path.exists(filename):
|
||||
raise IOError('Could not find entrypoint (candidates: {}).'.format(', '.join(DEFAULT_GRAPH_FILENAMES)))
|
||||
elif install:
|
||||
raise RuntimeError('Cannot --install on a file (only available for dirs containing requirements.txt).')
|
||||
requirements = os.path.join(os.path.dirname(filename), 'requirements.txt')
|
||||
_install_requirements(requirements)
|
||||
context = runpy.run_path(filename, run_name='__bonobo__')
|
||||
elif module:
|
||||
context = runpy.run_module(module, run_name='__bonobo__')
|
||||
@ -74,15 +81,21 @@ def execute(filename, module, install=False, quiet=False, verbose=False):
|
||||
).format(len(graphs))
|
||||
|
||||
graph = list(graphs.values())[0]
|
||||
plugins = []
|
||||
services = get_default_services(
|
||||
filename, context.get(DEFAULT_SERVICES_ATTR)() if DEFAULT_SERVICES_ATTR in context else None
|
||||
)
|
||||
|
||||
# todo if console and not quiet, then add the console plugin
|
||||
# todo when better console plugin, add it if console and just disable display
|
||||
return run(
|
||||
return graph, plugins, services
|
||||
|
||||
|
||||
def execute(filename, module, install=False, quiet=False, verbose=False):
|
||||
graph, plugins, services = read(filename, module, install, quiet, verbose)
|
||||
|
||||
return bonobo.run(
|
||||
graph,
|
||||
plugins=[],
|
||||
services=get_default_services(
|
||||
filename, context.get(DEFAULT_SERVICES_ATTR)() if DEFAULT_SERVICES_ATTR in context else None
|
||||
)
|
||||
plugins=plugins,
|
||||
services=services
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -4,3 +4,5 @@ BEGIN = Token('Begin')
|
||||
END = Token('End')
|
||||
INHERIT_INPUT = Token('InheritInput')
|
||||
NOT_MODIFIED = Token('NotModified')
|
||||
DEFAULT_SERVICES_FILENAME = '_services.py'
|
||||
DEFAULT_SERVICES_ATTR = 'get_services'
|
||||
@ -16,8 +16,9 @@ class PluginExecutionContext(LoopingExecutionContext):
|
||||
self.wrapped.initialize()
|
||||
|
||||
def shutdown(self):
|
||||
with recoverable(self.handle_error):
|
||||
self.wrapped.finalize()
|
||||
if self.started:
|
||||
with recoverable(self.handle_error):
|
||||
self.wrapped.finalize()
|
||||
self.alive = False
|
||||
|
||||
def step(self):
|
||||
|
||||
@ -2,7 +2,9 @@ import io
|
||||
import sys
|
||||
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.plugins import Plugin
|
||||
@ -10,6 +12,13 @@ from bonobo.util.term import CLEAR_EOL, MOVE_CURSOR_UP
|
||||
|
||||
|
||||
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):
|
||||
self.current = io.StringIO()
|
||||
self.write = self.current.write
|
||||
@ -23,12 +32,18 @@ class IOBuffer():
|
||||
finally:
|
||||
previous.close()
|
||||
|
||||
def flush(self):
|
||||
self.current.flush()
|
||||
|
||||
|
||||
class ConsoleOutputPlugin(Plugin):
|
||||
"""
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
String prefix of output lines.
|
||||
@ -40,17 +55,18 @@ class ConsoleOutputPlugin(Plugin):
|
||||
self.counter = 0
|
||||
self._append_cache = ''
|
||||
self.isatty = sys.stdout.isatty()
|
||||
self.iswindows = (sys.platform == 'win32')
|
||||
|
||||
self._stdout = sys.stdout
|
||||
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__()
|
||||
|
||||
def run(self):
|
||||
if self.isatty:
|
||||
if self.isatty and not self.iswindows:
|
||||
self._write(self.context.parent, rewind=True)
|
||||
else:
|
||||
pass # not a tty
|
||||
pass # not a tty, or windows, so we'll ignore stats output
|
||||
|
||||
def finalize(self):
|
||||
self._write(self.context.parent, rewind=False)
|
||||
@ -59,9 +75,13 @@ class ConsoleOutputPlugin(Plugin):
|
||||
def write(self, context, prefix='', rewind=True, append=None):
|
||||
t_cnt = len(context)
|
||||
|
||||
buffered = self.stdout.switch()
|
||||
for line in buffered.split('\n')[:-1]:
|
||||
print(line + CLEAR_EOL, file=sys.stderr)
|
||||
if not self.iswindows:
|
||||
buffered = self.stdout.switch()
|
||||
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:
|
||||
node = context[i]
|
||||
@ -69,14 +89,14 @@ class ConsoleOutputPlugin(Plugin):
|
||||
if node.alive:
|
||||
_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, ' ',
|
||||
)
|
||||
)
|
||||
else:
|
||||
_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, ' ',
|
||||
)
|
||||
)
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import logging
|
||||
|
||||
from bonobo.ext.jupyter.widget import BonoboWidget
|
||||
from bonobo.plugins import Plugin
|
||||
|
||||
try:
|
||||
import IPython.core.display
|
||||
except ImportError as e:
|
||||
import logging
|
||||
|
||||
logging.exception(
|
||||
'You must install Jupyter to use the bonobo Jupyter extension. Easiest way is to install the '
|
||||
'optional "jupyter" dependencies with «pip install bonobo[jupyter]», but you can also install a '
|
||||
|
||||
@ -8,19 +8,22 @@ from colorama import Fore, Style
|
||||
from bonobo import settings
|
||||
from bonobo.util.term import CLEAR_EOL
|
||||
|
||||
iswindows = (sys.platform == 'win32')
|
||||
|
||||
|
||||
def get_format():
|
||||
yield '{b}[%(fg)s%(levelname)s{b}][{w}'
|
||||
yield '{b}][{w}'.join(('%(spent)04d', '%(name)s'))
|
||||
yield '{b}]'
|
||||
yield ' %(fg)s%(message)s{r}'
|
||||
yield CLEAR_EOL
|
||||
if not iswindows:
|
||||
yield CLEAR_EOL
|
||||
|
||||
|
||||
colors = {
|
||||
'b': Fore.BLACK,
|
||||
'w': Fore.LIGHTBLACK_EX,
|
||||
'r': Style.RESET_ALL,
|
||||
'b': '' if iswindows else Fore.BLACK,
|
||||
'w': '' if iswindows else Fore.LIGHTBLACK_EX,
|
||||
'r': '' if iswindows else Style.RESET_ALL,
|
||||
}
|
||||
format = (''.join(get_format())).format(**colors)
|
||||
|
||||
@ -28,7 +31,9 @@ format = (''.join(get_format())).format(**colors)
|
||||
class Filter(logging.Filter):
|
||||
def filter(self, record):
|
||||
record.spent = record.relativeCreated // 1000
|
||||
if record.levelname == 'DEBG':
|
||||
if iswindows:
|
||||
record.fg = ''
|
||||
elif record.levelname == 'DEBG':
|
||||
record.fg = Fore.LIGHTBLACK_EX
|
||||
elif record.levelname == 'INFO':
|
||||
record.fg = Fore.LIGHTWHITE_EX
|
||||
@ -46,7 +51,10 @@ class Filter(logging.Filter):
|
||||
class Formatter(logging.Formatter):
|
||||
def formatException(self, ei):
|
||||
tb = super().formatException(ei)
|
||||
return textwrap.indent(tb, Fore.BLACK + ' | ' + Fore.WHITE)
|
||||
if iswindows:
|
||||
return textwrap.indent(tb, ' | ')
|
||||
else:
|
||||
return textwrap.indent(tb, Fore.BLACK + ' | ' + Fore.WHITE)
|
||||
|
||||
|
||||
def setup(level):
|
||||
|
||||
@ -91,8 +91,22 @@ def noop(*args, **kwargs): # pylint: disable=unused-argument
|
||||
|
||||
|
||||
def arg0_to_kwargs(row):
|
||||
"""
|
||||
Transform items in a stream from "arg0" format (each call only has one positional argument, which is a dict-like
|
||||
object) to "kwargs" format (each call only has keyword arguments that represent a row).
|
||||
|
||||
:param row:
|
||||
:return: bonobo.Bag
|
||||
"""
|
||||
return Bag(**row)
|
||||
|
||||
|
||||
def kwargs_to_arg0(**row):
|
||||
"""
|
||||
Transform items in a stream from "kwargs" format (each call only has keyword arguments that represent a row) to
|
||||
"arg0" format (each call only has one positional argument, which is a dict-like object) .
|
||||
|
||||
:param **row:
|
||||
:return: bonobo.Bag
|
||||
"""
|
||||
return Bag(row)
|
||||
|
||||
9
bonobo/util/graphviz.py
Normal file
9
bonobo/util/graphviz.py
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
def render_as_dot(graph):
|
||||
"""
|
||||
|
||||
:param bonobo.Graph graph:
|
||||
:return: str
|
||||
"""
|
||||
|
||||
pass
|
||||
@ -1,6 +1,13 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
v.0.4.3 - 16 july 2017
|
||||
::::::::::::::::::::::
|
||||
|
||||
* #113 - Add flush() method to IOBuffer (Vitalii Vokhmin)
|
||||
* Dependencies updated.
|
||||
* Minor project artifacts updated.
|
||||
|
||||
v.0.4.2 - 18 june 2017
|
||||
::::::::::::::::::::::
|
||||
|
||||
|
||||
@ -59,7 +59,7 @@ available in **Bonobo**'s repository:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ curl https://raw.githubusercontent.com/python-bonobo/bonobo/master/bonobo/examples/datasets/coffeeshops.txt > `python -c 'import bonobo; print(bonobo.get_examples_path("datasets/coffeeshops.txt"))'`
|
||||
$ curl https://raw.githubusercontent.com/python-bonobo/bonobo/master/bonobo/examples/datasets/coffeeshops.txt > `python3 -c 'import bonobo; print(bonobo.get_examples_path("datasets/coffeeshops.txt"))'`
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@ -18,8 +18,8 @@ specialized packages, like SQLAlchemy, or other database access libraries from t
|
||||
First, read https://www.bonobo-project.org/with/sqlalchemy for instructions on how to install. You **do need** the
|
||||
bleeding edge version of `bonobo` and `bonobo-sqlalchemy` to make this work.
|
||||
|
||||
Additional requirements
|
||||
:::::::::::::::::::::::
|
||||
Requirements
|
||||
::::::::::::
|
||||
|
||||
Once you installed `bonobo_sqlalchemy` (read https://www.bonobo-project.org/with/sqlalchemy to use bleeding edge
|
||||
version), install the following additional packages:
|
||||
@ -62,6 +62,9 @@ file and add values for one or more of `POSTGRES_NAME`, `POSTGRES_USER`, 'POSTGR
|
||||
`POSTGRES_PORT`. Please note that kwargs always have precedence on environment, but that you should prefer using
|
||||
environment variables for anything that is not immutable from one platform to another.
|
||||
|
||||
Add database operation to the graph
|
||||
:::::::::::::::::::::::::::::::::::
|
||||
|
||||
Let's create a `tutorial/pgdb.py` job:
|
||||
|
||||
.. code-block:: python
|
||||
@ -110,6 +113,9 @@ If we run this transformation (with `bonobo run tutorial/pgdb.py`), we should ge
|
||||
The database we requested do not exist. It is not the role of bonobo to do database administration, and thus there is
|
||||
no tool here to create neither the database, nor the tables we want to use.
|
||||
|
||||
Create database and table
|
||||
:::::::::::::::::::::::::
|
||||
|
||||
There are however tools in `sqlalchemy` to manage tables, so we'll create the database by ourselves, and ask sqlalchemy
|
||||
to create the table:
|
||||
|
||||
@ -170,6 +176,9 @@ Now run:
|
||||
|
||||
Database and table should now exist.
|
||||
|
||||
Format the data
|
||||
:::::::::::::::
|
||||
|
||||
Let's prepare our data for database, and change the `.add_chain(..)` call to do it prior to `InsertOrUpdate(...)`
|
||||
|
||||
.. code-block:: python
|
||||
@ -193,6 +202,9 @@ Let's prepare our data for database, and change the `.add_chain(..)` call to do
|
||||
_input=split_one_to_map
|
||||
)
|
||||
|
||||
Run!
|
||||
::::
|
||||
|
||||
You can now run the script (either with `bonobo run tutorial/pgdb.py` or directly with the python interpreter, as we
|
||||
added a "main" section) and the dataset should be inserted in your database. If you run it again, no new rows are
|
||||
created.
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
alabaster==0.7.10
|
||||
arrow==0.10.0
|
||||
babel==2.4.0
|
||||
binaryornot==0.4.3
|
||||
certifi==2017.4.17
|
||||
binaryornot==0.4.4
|
||||
certifi==2017.7.27.1
|
||||
chardet==3.0.4
|
||||
click==6.7
|
||||
cookiecutter==1.5.1
|
||||
coverage==4.4.1
|
||||
docutils==0.13.1
|
||||
docutils==0.14
|
||||
future==0.16.0
|
||||
idna==2.5
|
||||
imagesize==0.7.1
|
||||
@ -21,14 +21,14 @@ pygments==2.2.0
|
||||
pytest-cov==2.5.1
|
||||
pytest-sugar==0.8.0
|
||||
pytest-timeout==1.2.0
|
||||
pytest==3.1.2
|
||||
python-dateutil==2.6.0
|
||||
pytest==3.2.1
|
||||
python-dateutil==2.6.1
|
||||
pytz==2017.2
|
||||
requests==2.18.1
|
||||
requests==2.18.3
|
||||
six==1.10.0
|
||||
snowballstemmer==1.2.1
|
||||
sphinx==1.6.3
|
||||
sphinxcontrib-websupport==1.0.1
|
||||
termcolor==1.1.0
|
||||
urllib3==1.21.1
|
||||
urllib3==1.22
|
||||
whichcraft==0.4.1
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
-e .[docker]
|
||||
appdirs==1.4.3
|
||||
bonobo-docker==0.2.9
|
||||
certifi==2017.4.17
|
||||
bonobo-docker==0.2.11
|
||||
certifi==2017.7.27.1
|
||||
chardet==3.0.4
|
||||
colorama==0.3.9
|
||||
docker-pycreds==0.2.1
|
||||
docker==2.3.0
|
||||
fs==2.0.4
|
||||
fs==2.0.7
|
||||
idna==2.5
|
||||
packaging==16.8
|
||||
pbr==3.1.1
|
||||
psutil==5.2.2
|
||||
pyparsing==2.2.0
|
||||
pytz==2017.2
|
||||
requests==2.18.1
|
||||
requests==2.18.3
|
||||
six==1.10.0
|
||||
stevedore==1.23.0
|
||||
urllib3==1.21.1
|
||||
stevedore==1.25.0
|
||||
urllib3==1.22
|
||||
websocket-client==0.44.0
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
-e .[jupyter]
|
||||
appnope==0.1.0
|
||||
bleach==2.0.0
|
||||
decorator==4.0.11
|
||||
decorator==4.1.2
|
||||
entrypoints==0.2.3
|
||||
html5lib==0.999999999
|
||||
ipykernel==4.6.1
|
||||
@ -20,13 +20,13 @@ mistune==0.7.4
|
||||
nbconvert==5.2.1
|
||||
nbformat==4.3.0
|
||||
notebook==5.0.0
|
||||
pandocfilters==1.4.1
|
||||
pandocfilters==1.4.2
|
||||
pexpect==4.2.1
|
||||
pickleshare==0.7.4
|
||||
prompt-toolkit==1.0.14
|
||||
prompt-toolkit==1.0.15
|
||||
ptyprocess==0.5.2
|
||||
pygments==2.2.0
|
||||
python-dateutil==2.6.0
|
||||
python-dateutil==2.6.1
|
||||
pyzmq==16.0.2
|
||||
qtconsole==4.3.0
|
||||
simplegeneric==0.8.1
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
-e .
|
||||
appdirs==1.4.3
|
||||
certifi==2017.4.17
|
||||
certifi==2017.7.27.1
|
||||
chardet==3.0.4
|
||||
colorama==0.3.9
|
||||
fs==2.0.4
|
||||
fs==2.0.7
|
||||
idna==2.5
|
||||
packaging==16.8
|
||||
pbr==3.1.1
|
||||
psutil==5.2.2
|
||||
pyparsing==2.2.0
|
||||
pytz==2017.2
|
||||
requests==2.18.1
|
||||
requests==2.18.3
|
||||
six==1.10.0
|
||||
stevedore==1.23.0
|
||||
urllib3==1.21.1
|
||||
stevedore==1.25.0
|
||||
urllib3==1.22
|
||||
|
||||
3
setup.py
3
setup.py
@ -68,7 +68,8 @@ setup(
|
||||
entry_points={
|
||||
'bonobo.commands': [
|
||||
'convert = bonobo.commands.convert:register', 'init = bonobo.commands.init:register',
|
||||
'run = bonobo.commands.run:register', 'version = bonobo.commands.version:register'
|
||||
'graph = bonobo.commands.graph:register', 'run = bonobo.commands.run:register',
|
||||
'version = bonobo.commands.version:register'
|
||||
],
|
||||
'console_scripts': ['bonobo = bonobo.commands:entrypoint']
|
||||
},
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import os
|
||||
import runpy
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
@ -10,10 +11,12 @@ from bonobo.commands import entrypoint
|
||||
|
||||
|
||||
def runner_entrypoint(*args):
|
||||
""" Run bonobo using the python command entrypoint directly (bonobo.commands.entrypoint). """
|
||||
return entrypoint(list(args))
|
||||
|
||||
|
||||
def runner_module(*args):
|
||||
""" Run bonobo using the bonobo.__main__ file, which is equivalent as doing "python -m bonobo ..."."""
|
||||
with patch.object(sys, 'argv', ['bonobo', *args]):
|
||||
return runpy.run_path(__main__.__file__, run_name='__main__')
|
||||
|
||||
@ -70,6 +73,22 @@ def test_run_path(runner, capsys):
|
||||
assert out[2].startswith('Baz ')
|
||||
|
||||
|
||||
@all_runners
|
||||
def test_install_requirements_for_dir(runner):
|
||||
dirname = get_examples_path('types')
|
||||
with patch('bonobo.commands.run._install_requirements') as install_mock:
|
||||
runner('run', '--install', dirname)
|
||||
install_mock.assert_called_once_with(os.path.join(dirname, 'requirements.txt'))
|
||||
|
||||
|
||||
@all_runners
|
||||
def test_install_requirements_for_file(runner):
|
||||
dirname = get_examples_path('types')
|
||||
with patch('bonobo.commands.run._install_requirements') as install_mock:
|
||||
runner('run', '--install', os.path.join(dirname, 'strings.py'))
|
||||
install_mock.assert_called_once_with(os.path.join(dirname, 'requirements.txt'))
|
||||
|
||||
|
||||
@all_runners
|
||||
def test_version(runner, capsys):
|
||||
runner('version')
|
||||
|
||||
Reference in New Issue
Block a user