From be844c3ed788a19b04aeb2a8fec79ad36854556a Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Thu, 25 May 2017 16:41:01 +0200 Subject: [PATCH 01/10] WIP GRAPHVIZ --- Makefile | 2 +- Projectfile | 1 + bin/imgcat | 112 +++++++++++++++++++++++++++++++++++++++ bin/test_graph | 1 + bonobo/commands/graph.py | 22 ++++++++ bonobo/commands/run.py | 27 +++++----- bonobo/constants.py | 2 + bonobo/structs/graphs.py | 2 + bonobo/util/graphviz.py | 9 ++++ setup.py | 1 + 10 files changed, 165 insertions(+), 14 deletions(-) create mode 100755 bin/imgcat create mode 100644 bin/test_graph create mode 100644 bonobo/commands/graph.py create mode 100644 bonobo/util/graphviz.py diff --git a/Makefile b/Makefile index 6db0f7c..6c5443d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # This file has been auto-generated. # All changes will be lost, see Projectfile. # -# Updated at 2017-05-03 18:02:59.359160 +# Updated at 2017-05-08 11:34:30.472553 PACKAGE ?= bonobo PYTHON ?= $(shell which python) diff --git a/Projectfile b/Projectfile index 47844ee..95e5afa 100644 --- a/Projectfile +++ b/Projectfile @@ -60,6 +60,7 @@ entry_points = { ], 'bonobo.commands': [ 'init = bonobo.commands.init:register', + 'graph = bonobo.commands.graph:register', 'run = bonobo.commands.run:register', 'version = bonobo.commands.version:register', ], diff --git a/bin/imgcat b/bin/imgcat new file mode 100755 index 0000000..001d2b8 --- /dev/null +++ b/bin/imgcat @@ -0,0 +1,112 @@ +#!/bin/bash + +# tmux requires unrecognized OSC sequences to be wrapped with DCS tmux; +# ST, and for all ESCs in 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 diff --git a/bin/test_graph b/bin/test_graph new file mode 100644 index 0000000..1e5fd85 --- /dev/null +++ b/bin/test_graph @@ -0,0 +1 @@ +bonobo graph bonobo/examples/tutorials/tut02_03_writeasmap.py | dot -otest.png -Tpng && bin/imgcat test.png diff --git a/bonobo/commands/graph.py b/bonobo/commands/graph.py new file mode 100644 index 0000000..b8bbdf9 --- /dev/null +++ b/bonobo/commands/graph.py @@ -0,0 +1,22 @@ +import json + +from bonobo.util.objects import get_name +from bonobo.commands.run import read_file +from bonobo.constants import BEGIN + + +def execute(file): + graph, plugins, services = read_file(file) + + print('digraph {') + print(' rankdir = LR;') + print(' "BEGIN" [shape="point"];') + for i in graph.outputs_of(BEGIN): + print(' "BEGIN" -> ' + json.dumps(get_name(graph.nodes[i])) + ';') + print('}') + + +def register(parser): + import argparse + parser.add_argument('file', type=argparse.FileType()) + return execute diff --git a/bonobo/commands/run.py b/bonobo/commands/run.py index b7872e2..60123c8 100644 --- a/bonobo/commands/run.py +++ b/bonobo/commands/run.py @@ -1,11 +1,7 @@ -import argparse - import os import bonobo - -DEFAULT_SERVICES_FILENAME = '_services.py' -DEFAULT_SERVICES_ATTR = 'get_services' +from bonobo.constants import DEFAULT_SERVICES_ATTR, DEFAULT_SERVICES_FILENAME def get_default_services(filename, services=None): @@ -29,7 +25,7 @@ def get_default_services(filename, services=None): return services or {} -def execute(file, quiet=False): +def read_file(file): with file: code = compile(file.read(), file.name, 'exec') @@ -56,19 +52,24 @@ def execute(file, quiet=False): ).format(len(graphs)) graph = list(graphs.values())[0] + plugins = [] + services = get_default_services( + file.name, context.get(DEFAULT_SERVICES_ATTR)() if DEFAULT_SERVICES_ATTR in context else None + ) + + return graph, plugins, services + + +def execute(file, quiet=False): + graph, plugins, services = read_file(file) # 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 bonobo.run( - graph, - plugins=[], - services=get_default_services( - file.name, context.get(DEFAULT_SERVICES_ATTR)() if DEFAULT_SERVICES_ATTR in context else None - ) - ) + return bonobo.run(graph, plugins=plugins, services=services) def register(parser): + import argparse parser.add_argument('file', type=argparse.FileType()) parser.add_argument('--quiet', action='store_true') return execute diff --git a/bonobo/constants.py b/bonobo/constants.py index d567229..4187197 100644 --- a/bonobo/constants.py +++ b/bonobo/constants.py @@ -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' \ No newline at end of file diff --git a/bonobo/structs/graphs.py b/bonobo/structs/graphs.py index d2d755e..c68186b 100644 --- a/bonobo/structs/graphs.py +++ b/bonobo/structs/graphs.py @@ -29,3 +29,5 @@ class Graph: def __len__(self): return len(self.nodes) + + diff --git a/bonobo/util/graphviz.py b/bonobo/util/graphviz.py new file mode 100644 index 0000000..fa88974 --- /dev/null +++ b/bonobo/util/graphviz.py @@ -0,0 +1,9 @@ + +def render_as_dot(graph): + """ + + :param bonobo.Graph graph: + :return: str + """ + + pass \ No newline at end of file diff --git a/setup.py b/setup.py index 4cd8d82..1153f35 100644 --- a/setup.py +++ b/setup.py @@ -65,6 +65,7 @@ setup( entry_points={ 'bonobo.commands': [ 'init = bonobo.commands.init:register', + 'graph = bonobo.commands.graph:register', 'run = bonobo.commands.run:register', 'version = bonobo.commands.version:register' ], From 3e961776e3b8a7b0b4c0fe1c9be27c1cfb24efbf Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Sun, 16 Jul 2017 18:45:49 +0200 Subject: [PATCH 02/10] [cli] First draft implementation of "convert" command, which builds a simple graph of reader+writer and executes it. --- Makefile | 2 +- Projectfile | 1 + bonobo/commands/convert.py | 61 ++++++++++++++++++++++++++++++++++++++ setup.py | 4 +-- 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 bonobo/commands/convert.py diff --git a/Makefile b/Makefile index 10094af..fcdc7e2 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # This file has been auto-generated. # All changes will be lost, see Projectfile. # -# Updated at 2017-07-04 10:50:55.775681 +# Updated at 2017-07-16 18:44:56.169119 PACKAGE ?= bonobo PYTHON ?= $(shell which python) diff --git a/Projectfile b/Projectfile index 6fe6c2b..117dd76 100644 --- a/Projectfile +++ b/Projectfile @@ -29,6 +29,7 @@ python.setup( 'bonobo = bonobo.commands:entrypoint', ], 'bonobo.commands': [ + 'convert = bonobo.commands.convert:register', 'init = bonobo.commands.init:register', 'run = bonobo.commands.run:register', 'version = bonobo.commands.version:register', diff --git a/bonobo/commands/convert.py b/bonobo/commands/convert.py new file mode 100644 index 0000000..c29f31c --- /dev/null +++ b/bonobo/commands/convert.py @@ -0,0 +1,61 @@ +import mimetypes + +import bonobo + +SHORTCUTS = { + 'plain': 'text/plain', + 'txt': 'text/plain', + 'text': 'text/plain', + 'csv': 'text/csv', + 'json': 'application/json', +} + +REGISTRY = { + 'text/plain': (bonobo.FileReader, bonobo.FileWriter), + 'text/csv': (bonobo.CsvReader, bonobo.CsvWriter), + 'application/json': (bonobo.JsonReader, bonobo.JsonWriter), +} + + +def resolve_factory(name, filename, factory_type): + """ + Try to resolve which transformation factory to use for this filename. User eventually provided a name, which has + priority, otherwise we try to detect it using the mimetype detection on filename. + + """ + if name is None: + name = mimetypes.guess_type(filename)[0] + + if name in SHORTCUTS: + name = SHORTCUTS[name] + + if not name in REGISTRY: + raise RuntimeError('Could not resolve {factory_type} factory for {filename} ({name}). Try providing it explicitely using -{opt} .'.format(name=name, filename=filename, factory_type=factory_type, opt=factory_type[0])) + + if factory_type == 'reader': + return REGISTRY[name][0] + elif factory_type == 'writer': + return REGISTRY[name][1] + else: + raise ValueError('Invalid factory type.') + +def execute(input, output, reader=None, reader_options=None, writer=None, writer_options=None, options=None): + reader = resolve_factory(reader, input, 'reader')(input) + writer = resolve_factory(writer, output, 'writer')(output) + + graph = bonobo.Graph() + graph.add_chain(reader, writer) + + return bonobo.run(graph, services={ + 'fs': bonobo.open_fs(), + }) + +def register(parser): + parser.add_argument('input') + parser.add_argument('output') + parser.add_argument('--reader', '-r') + parser.add_argument('--writer', '-w') + parser.add_argument('--reader-option', '-ro', dest='reader_options') + parser.add_argument('--writer-option', '-wo', dest='writer_options') + parser.add_argument('--option', '-o', dest='options') + return execute diff --git a/setup.py b/setup.py index 89c9ccd..a925811 100644 --- a/setup.py +++ b/setup.py @@ -67,8 +67,8 @@ setup( }, entry_points={ 'bonobo.commands': [ - 'init = bonobo.commands.init:register', 'run = bonobo.commands.run:register', - 'version = bonobo.commands.version:register' + 'convert = bonobo.commands.convert:register', 'init = bonobo.commands.init:register', + 'run = bonobo.commands.run:register', 'version = bonobo.commands.version:register' ], 'console_scripts': ['bonobo = bonobo.commands:entrypoint'] }, From c881ca106155a8777af6bd69aaf007680c7df376 Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Sun, 16 Jul 2017 18:51:17 +0200 Subject: [PATCH 03/10] [cli] Convert: adds pickle format, comment out unused arguments, for now. --- bonobo/commands/convert.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bonobo/commands/convert.py b/bonobo/commands/convert.py index c29f31c..90bd57b 100644 --- a/bonobo/commands/convert.py +++ b/bonobo/commands/convert.py @@ -11,9 +11,10 @@ SHORTCUTS = { } REGISTRY = { - 'text/plain': (bonobo.FileReader, bonobo.FileWriter), - 'text/csv': (bonobo.CsvReader, bonobo.CsvWriter), 'application/json': (bonobo.JsonReader, bonobo.JsonWriter), + 'pickle': (bonobo.PickleReader, bonobo.PickleWriter), + 'text/csv': (bonobo.CsvReader, bonobo.CsvWriter), + 'text/plain': (bonobo.FileReader, bonobo.FileWriter), } @@ -55,7 +56,7 @@ def register(parser): parser.add_argument('output') parser.add_argument('--reader', '-r') parser.add_argument('--writer', '-w') - parser.add_argument('--reader-option', '-ro', dest='reader_options') - parser.add_argument('--writer-option', '-wo', dest='writer_options') - parser.add_argument('--option', '-o', dest='options') + # parser.add_argument('--reader-option', '-ro', dest='reader_options') + # parser.add_argument('--writer-option', '-wo', dest='writer_options') + # parser.add_argument('--option', '-o', dest='options') return execute From 966628e15697d20205e1f5b615b48e5e1257df73 Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Mon, 18 Sep 2017 17:16:18 +0200 Subject: [PATCH 04/10] Implements graphviz output after a graph inspection. --- bonobo/commands/graph.py | 22 ++++++++++++++++------ bonobo/commands/run.py | 8 +++++++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/bonobo/commands/graph.py b/bonobo/commands/graph.py index b8bbdf9..7afa8de 100644 --- a/bonobo/commands/graph.py +++ b/bonobo/commands/graph.py @@ -1,22 +1,32 @@ import json +import itertools + from bonobo.util.objects import get_name -from bonobo.commands.run import read_file +from bonobo.commands.run import read, register_generic_run_arguments from bonobo.constants import BEGIN -def execute(file): - graph, plugins, services = read_file(file) +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.nodes[i])) + ';') + 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): - import argparse - parser.add_argument('file', type=argparse.FileType()) + register_generic_run_arguments(parser) return execute diff --git a/bonobo/commands/run.py b/bonobo/commands/run.py index 58cd82d..604fc39 100644 --- a/bonobo/commands/run.py +++ b/bonobo/commands/run.py @@ -40,7 +40,7 @@ def _install_requirements(requirements): importlib.reload(site) -def execute(filename, module, install=False, quiet=False, verbose=False): +def read(filename, module, install=False, quiet=False, verbose=False): import runpy from bonobo import Graph, settings @@ -86,6 +86,12 @@ def execute(filename, module, install=False, quiet=False, verbose=False): filename, context.get(DEFAULT_SERVICES_ATTR)() if DEFAULT_SERVICES_ATTR in context else None ) + 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=plugins, From 8b9dac50eca8d2737e377c225fdd5e4c4dc948c8 Mon Sep 17 00:00:00 2001 From: cwandrews Date: Mon, 18 Sep 2017 15:24:27 -0400 Subject: [PATCH 05/10] Added optional passing of one or multiple environment variables via --env flag to the bonobo cli. --- bonobo/commands/run.py | 12 +++++++++++- tests/test_commands.py | 27 +++++++++++++++++++++++++++ tests/util/get_passed_env.py | 22 ++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/util/get_passed_env.py diff --git a/bonobo/commands/run.py b/bonobo/commands/run.py index fb93e77..c9bb39e 100644 --- a/bonobo/commands/run.py +++ b/bonobo/commands/run.py @@ -40,7 +40,10 @@ def _install_requirements(requirements): importlib.reload(site) -def execute(filename, module, install=False, quiet=False, verbose=False): +def execute(filename, module, install=False, quiet=False, verbose=False, + env=None): + import re + import runpy from bonobo import Graph, run, settings @@ -50,6 +53,12 @@ def execute(filename, module, install=False, quiet=False, verbose=False): if verbose: settings.DEBUG.set(True) + if env: + quote_killer = re.compile('["\']') + for e in env: + var_name, var_value = e.split('=') + os.environ[var_name] = quote_killer.sub('', var_value) + if filename: if os.path.isdir(filename): if install: @@ -106,4 +115,5 @@ def register(parser): verbosity_group.add_argument('--quiet', '-q', action='store_true') verbosity_group.add_argument('--verbose', '-v', action='store_true') parser.add_argument('--install', '-I', action='store_true') + parser.add_argument('--env', '-e', action='append') return execute diff --git a/tests/test_commands.py b/tests/test_commands.py index daf245f..cff9e38 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -3,6 +3,7 @@ import runpy import sys from unittest.mock import patch +import pathlib import pkg_resources import pytest @@ -96,3 +97,29 @@ def test_version(runner, capsys): out = out.strip() assert out.startswith('bonobo ') assert __version__ in out + + +@all_runners +def test_run_with_env(runner, capsys): + runner('run', '--quiet', + str(pathlib.Path(os.path.dirname(__file__), + 'util', 'get_passed_env.py')), + '--env', 'ENV_TEST_NUMBER=123', '--env', 'ENV_TEST_USER=cwandrews', + '--env', "ENV_TEST_STRING='my_test_string'") + out, err = capsys.readouterr() + out = out.split('\n') + assert out[0] == 'cwandrews' + assert out[1] == '123' + assert out[2] == 'my_test_string' + + +@all_runners +def test_run_module_with_env(runner, capsys): + runner('run', '--quiet', '-m', 'tests.util.get_passed_env', + '--env', 'ENV_TEST_NUMBER=123', '--env', 'ENV_TEST_USER=cwandrews', + '--env', "ENV_TEST_STRING='my_test_string'") + out, err = capsys.readouterr() + out = out.split('\n') + assert out[0] == 'cwandrews' + assert out[1] == '123' + assert out[2] == 'my_test_string' diff --git a/tests/util/get_passed_env.py b/tests/util/get_passed_env.py new file mode 100644 index 0000000..d9c4ba6 --- /dev/null +++ b/tests/util/get_passed_env.py @@ -0,0 +1,22 @@ +import os + +from bonobo import Graph + + +def extract(): + env_test_user = os.getenv('ENV_TEST_USER') + env_test_number = os.getenv('ENV_TEST_NUMBER') + env_test_string = os.getenv('ENV_TEST_STRING') + return env_test_user, env_test_number, env_test_string + + +def load(s: str): + print(s) + + +graph = Graph(extract, load) + +if __name__ == '__main__': + from bonobo import run + + run(graph) From 21514ad670f99da42b2967b89a6ece3d2e7e5ba1 Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Sat, 30 Sep 2017 09:54:30 +0200 Subject: [PATCH 06/10] Update dependencies. --- Makefile | 6 +++--- requirements-dev.txt | 20 ++++++++++---------- requirements-docker.txt | 16 ++++++++-------- requirements-jupyter.txt | 31 ++++++++++++++++--------------- requirements.txt | 16 ++++++++-------- 5 files changed, 45 insertions(+), 44 deletions(-) diff --git a/Makefile b/Makefile index cb5f8bc..8175b3e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # This file has been auto-generated. # All changes will be lost, see Projectfile. # -# Updated at 2017-07-16 10:52:31.093416 +# Updated at 2017-09-30 09:50:47.806007 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. diff --git a/requirements-dev.txt b/requirements-dev.txt index 69d64d8..92123d5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,16 +1,16 @@ -e .[dev] alabaster==0.7.10 arrow==0.10.0 -babel==2.4.0 -binaryornot==0.4.3 -certifi==2017.4.17 +babel==2.5.1 +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 +idna==2.6 imagesize==0.7.1 jinja2-time==0.2.0 jinja2==2.9.6 @@ -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.3 +pytest==3.2.2 python-dateutil==2.6.1 pytz==2017.2 -requests==2.18.1 -six==1.10.0 +requests==2.18.4 +six==1.11.0 snowballstemmer==1.2.1 -sphinx==1.6.3 +sphinx==1.6.4 sphinxcontrib-websupport==1.0.1 termcolor==1.1.0 -urllib3==1.21.1 +urllib3==1.22 whichcraft==0.4.1 diff --git a/requirements-docker.txt b/requirements-docker.txt index f5e74fc..870223d 100644 --- a/requirements-docker.txt +++ b/requirements-docker.txt @@ -1,20 +1,20 @@ -e .[docker] appdirs==1.4.3 bonobo-docker==0.2.11 -certifi==2017.4.17 +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 -idna==2.5 +fs==2.0.11 +idna==2.6 packaging==16.8 pbr==3.1.1 -psutil==5.2.2 +psutil==5.3.1 pyparsing==2.2.0 pytz==2017.2 -requests==2.18.1 -six==1.10.0 -stevedore==1.24.0 -urllib3==1.21.1 +requests==2.18.4 +six==1.11.0 +stevedore==1.27.0 +urllib3==1.22 websocket-client==0.44.0 diff --git a/requirements-jupyter.txt b/requirements-jupyter.txt index 2542040..94b10ea 100644 --- a/requirements-jupyter.txt +++ b/requirements-jupyter.txt @@ -1,40 +1,41 @@ -e .[jupyter] appnope==0.1.0 -bleach==2.0.0 -decorator==4.1.1 +bleach==2.1 +decorator==4.1.2 entrypoints==0.2.3 html5lib==0.999999999 ipykernel==4.6.1 ipython-genutils==0.2.0 -ipython==6.1.0 -ipywidgets==6.0.0 -jedi==0.10.2 +ipython==6.2.1 +ipywidgets==6.0.1 +jedi==0.11.0 jinja2==2.9.6 jsonschema==2.6.0 jupyter-client==5.1.0 -jupyter-console==5.1.0 +jupyter-console==5.2.0 jupyter-core==4.3.0 jupyter==1.0.0 markupsafe==1.0 mistune==0.7.4 -nbconvert==5.2.1 -nbformat==4.3.0 -notebook==5.0.0 -pandocfilters==1.4.1 +nbconvert==5.3.1 +nbformat==4.4.0 +notebook==5.1.0 +pandocfilters==1.4.2 +parso==0.1.0 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.1 pyzmq==16.0.2 -qtconsole==4.3.0 +qtconsole==4.3.1 simplegeneric==0.8.1 -six==1.10.0 +six==1.11.0 terminado==0.6 testpath==0.3.1 -tornado==4.5.1 +tornado==4.5.2 traitlets==4.3.2 wcwidth==0.1.7 webencodings==0.5.1 -widgetsnbextension==2.0.0 +widgetsnbextension==2.0.1 diff --git a/requirements.txt b/requirements.txt index 5ddbb01..61ab27d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 -idna==2.5 +fs==2.0.11 +idna==2.6 packaging==16.8 pbr==3.1.1 -psutil==5.2.2 +psutil==5.3.1 pyparsing==2.2.0 pytz==2017.2 -requests==2.18.1 -six==1.10.0 -stevedore==1.24.0 -urllib3==1.21.1 +requests==2.18.4 +six==1.11.0 +stevedore==1.27.0 +urllib3==1.22 From 7ca3369f7141246823e0fe2dabb0d98e5d91af5a Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Sat, 30 Sep 2017 11:01:53 +0200 Subject: [PATCH 07/10] Rename "bonobo graph" to "bonobo inspect". For now, graphviz is default but there will probably be a humand default in the future, with graphviz source generation set if --graph (or -g) flag is passed. --- Makefile | 2 +- Projectfile | 2 +- bonobo/commands/graph.py | 32 -------------------------------- bonobo/commands/inspect.py | 33 +++++++++++++++++++++++++++++++++ bonobo/commands/run.py | 8 ++------ bonobo/util/graphviz.py | 3 +-- setup.py | 2 +- tests/test_commands.py | 17 +++++++++-------- 8 files changed, 48 insertions(+), 51 deletions(-) delete mode 100644 bonobo/commands/graph.py create mode 100644 bonobo/commands/inspect.py diff --git a/Makefile b/Makefile index 1e617cb..aac0c92 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # This file has been auto-generated. # All changes will be lost, see Projectfile. # -# Updated at 2017-09-30 10:24:51.699716 +# Updated at 2017-09-30 10:57:00.855477 PACKAGE ?= bonobo PYTHON ?= $(shell which python) diff --git a/Projectfile b/Projectfile index eea8cc5..8e848b3 100644 --- a/Projectfile +++ b/Projectfile @@ -30,7 +30,7 @@ python.setup( ], 'bonobo.commands': [ 'init = bonobo.commands.init:register', - 'graph = bonobo.commands.graph:register', + 'inspect = bonobo.commands.inspect:register', 'run = bonobo.commands.run:register', 'version = bonobo.commands.version:register', ], diff --git a/bonobo/commands/graph.py b/bonobo/commands/graph.py deleted file mode 100644 index 7afa8de..0000000 --- a/bonobo/commands/graph.py +++ /dev/null @@ -1,32 +0,0 @@ -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 diff --git a/bonobo/commands/inspect.py b/bonobo/commands/inspect.py new file mode 100644 index 0000000..83b770e --- /dev/null +++ b/bonobo/commands/inspect.py @@ -0,0 +1,33 @@ +import json + +from bonobo.commands.run import read, register_generic_run_arguments +from bonobo.constants import BEGIN +from bonobo.util.objects import get_name + +OUTPUT_GRAPHVIZ = 'graphviz' + +def execute(*, output, **kwargs): + graph, plugins, services = read(**kwargs) + + if output == OUTPUT_GRAPHVIZ: + 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('}') + else: + raise NotImplementedError('Output type not implemented.') + + +def register(parser): + register_generic_run_arguments(parser) + parser.add_argument('--graph', '-g', dest='output', action='store_const', const=OUTPUT_GRAPHVIZ) + parser.set_defaults(output=OUTPUT_GRAPHVIZ) + return execute diff --git a/bonobo/commands/run.py b/bonobo/commands/run.py index 27a2329..2204a3b 100644 --- a/bonobo/commands/run.py +++ b/bonobo/commands/run.py @@ -3,7 +3,7 @@ import os 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' @@ -99,11 +99,7 @@ def read(filename, module, install=False, quiet=False, verbose=False, env=None): def execute(filename, module, install=False, quiet=False, verbose=False, env=None): graph, plugins, services = read(filename, module, install, quiet, verbose, env) - return bonobo.run( - graph, - plugins=plugins, - services=services - ) + return bonobo.run(graph, plugins=plugins, services=services) def register_generic_run_arguments(parser, required=True): diff --git a/bonobo/util/graphviz.py b/bonobo/util/graphviz.py index fa88974..588e374 100644 --- a/bonobo/util/graphviz.py +++ b/bonobo/util/graphviz.py @@ -1,4 +1,3 @@ - def render_as_dot(graph): """ @@ -6,4 +5,4 @@ def render_as_dot(graph): :return: str """ - pass \ No newline at end of file + pass diff --git a/setup.py b/setup.py index 0abee00..08b84e0 100644 --- a/setup.py +++ b/setup.py @@ -67,7 +67,7 @@ setup( }, entry_points={ 'bonobo.commands': [ - 'init = bonobo.commands.init:register', 'graph = bonobo.commands.graph:register', + 'init = bonobo.commands.init:register', 'inspect = bonobo.commands.inspect:register', 'run = bonobo.commands.run:register', 'version = bonobo.commands.version:register' ], 'console_scripts': ['bonobo = bonobo.commands:entrypoint'] diff --git a/tests/test_commands.py b/tests/test_commands.py index cff9e38..730bc0b 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -101,11 +101,11 @@ def test_version(runner, capsys): @all_runners def test_run_with_env(runner, capsys): - runner('run', '--quiet', - str(pathlib.Path(os.path.dirname(__file__), - 'util', 'get_passed_env.py')), - '--env', 'ENV_TEST_NUMBER=123', '--env', 'ENV_TEST_USER=cwandrews', - '--env', "ENV_TEST_STRING='my_test_string'") + runner( + 'run', '--quiet', + str(pathlib.Path(os.path.dirname(__file__), 'util', 'get_passed_env.py')), '--env', 'ENV_TEST_NUMBER=123', + '--env', 'ENV_TEST_USER=cwandrews', '--env', "ENV_TEST_STRING='my_test_string'" + ) out, err = capsys.readouterr() out = out.split('\n') assert out[0] == 'cwandrews' @@ -115,9 +115,10 @@ def test_run_with_env(runner, capsys): @all_runners def test_run_module_with_env(runner, capsys): - runner('run', '--quiet', '-m', 'tests.util.get_passed_env', - '--env', 'ENV_TEST_NUMBER=123', '--env', 'ENV_TEST_USER=cwandrews', - '--env', "ENV_TEST_STRING='my_test_string'") + runner( + 'run', '--quiet', '-m', 'tests.util.get_passed_env', '--env', 'ENV_TEST_NUMBER=123', '--env', + 'ENV_TEST_USER=cwandrews', '--env', "ENV_TEST_STRING='my_test_string'" + ) out, err = capsys.readouterr() out = out.split('\n') assert out[0] == 'cwandrews' From b49ccaa7a77aad78a4560c8469c1e09d37a74abb Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Sat, 30 Sep 2017 11:26:22 +0200 Subject: [PATCH 08/10] Formating. --- bonobo/commands/inspect.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bonobo/commands/inspect.py b/bonobo/commands/inspect.py index 83b770e..bb82704 100644 --- a/bonobo/commands/inspect.py +++ b/bonobo/commands/inspect.py @@ -6,6 +6,7 @@ from bonobo.util.objects import get_name OUTPUT_GRAPHVIZ = 'graphviz' + def execute(*, output, **kwargs): graph, plugins, services = read(**kwargs) From c6cde93f4ee8afe0256b8b46e6f331375551921b Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Sat, 30 Sep 2017 11:38:36 +0200 Subject: [PATCH 09/10] [cli] small refactoring in bonobo convert to use shortcuts as extensions if nothing else matched. --- bonobo/commands/convert.py | 42 ++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/bonobo/commands/convert.py b/bonobo/commands/convert.py index 90bd57b..3c26724 100644 --- a/bonobo/commands/convert.py +++ b/bonobo/commands/convert.py @@ -1,13 +1,15 @@ import mimetypes +import os import bonobo SHORTCUTS = { - 'plain': 'text/plain', - 'txt': 'text/plain', - 'text': 'text/plain', 'csv': 'text/csv', 'json': 'application/json', + 'pickle': 'pickle', + 'plain': 'text/plain', + 'text': 'text/plain', + 'txt': 'text/plain', } REGISTRY = { @@ -17,6 +19,9 @@ REGISTRY = { 'text/plain': (bonobo.FileReader, bonobo.FileWriter), } +READER = 'reader' +WRITER = 'writer' + def resolve_factory(name, filename, factory_type): """ @@ -30,32 +35,43 @@ def resolve_factory(name, filename, factory_type): if name in SHORTCUTS: name = SHORTCUTS[name] - if not name in REGISTRY: - raise RuntimeError('Could not resolve {factory_type} factory for {filename} ({name}). Try providing it explicitely using -{opt} .'.format(name=name, filename=filename, factory_type=factory_type, opt=factory_type[0])) + if name is None: + _, _ext = os.path.splitext(filename) + if _ext: + _ext = _ext[1:] + if _ext in SHORTCUTS: + name = SHORTCUTS[_ext] - if factory_type == 'reader': + if not name in REGISTRY: + raise RuntimeError( + 'Could not resolve {factory_type} factory for {filename} ({name}). Try providing it explicitely using -{opt} .'.format( + name=name, filename=filename, factory_type=factory_type, opt=factory_type[0])) + + if factory_type == READER: return REGISTRY[name][0] - elif factory_type == 'writer': + elif factory_type == WRITER: return REGISTRY[name][1] else: raise ValueError('Invalid factory type.') + def execute(input, output, reader=None, reader_options=None, writer=None, writer_options=None, options=None): - reader = resolve_factory(reader, input, 'reader')(input) - writer = resolve_factory(writer, output, 'writer')(output) + reader = resolve_factory(reader, input, READER)(input) + writer = resolve_factory(writer, output, WRITER)(output) graph = bonobo.Graph() graph.add_chain(reader, writer) return bonobo.run(graph, services={ - 'fs': bonobo.open_fs(), - }) + 'fs': bonobo.open_fs(), + }) + def register(parser): parser.add_argument('input') parser.add_argument('output') - parser.add_argument('--reader', '-r') - parser.add_argument('--writer', '-w') + parser.add_argument('--' + READER, '-r') + parser.add_argument('--' + WRITER, '-w') # parser.add_argument('--reader-option', '-ro', dest='reader_options') # parser.add_argument('--writer-option', '-wo', dest='writer_options') # parser.add_argument('--option', '-o', dest='options') From 7fb572ec90548050dff47ae72a1cc1df70487cd7 Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Sat, 30 Sep 2017 11:45:13 +0200 Subject: [PATCH 10/10] [doc] new commands. --- docs/reference/commands.rst | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/reference/commands.rst b/docs/reference/commands.rst index dcd054a..674d549 100644 --- a/docs/reference/commands.rst +++ b/docs/reference/commands.rst @@ -1,6 +1,21 @@ Command-line ============ + +Bonobo Convert +:::::::::::::: + +Build a simple bonobo graph with one reader and one writer, then execute it, allowing to use bonobo in "no code" mode +for simple file format conversions. + +Syntax: `bonobo convert [-r reader] input_filename [-w writer] output_filename` + +.. todo:: + + add a way to override default options of reader/writers, add a way to add "filters", for example this could be used + to read from csv and write to csv too (or other format) but adding a geocoder filter that would add some fields. + + Bonobo Init ::::::::::: @@ -8,7 +23,17 @@ Create an empty project, ready to use bonobo. Syntax: `bonobo init` -Requires `edgy.project`. +Requires `cookiecutter`. + + +Bonobo Inspect +:::::::::::::: + +Inspects a bonobo graph source files. For now, only support graphviz output. + +Syntax: `bonobo inspect [--graph|-g] filename` + +Requires graphviz if you want to generate an actual graph picture, although the command itself depends on nothing. Bonobo Run @@ -20,6 +45,7 @@ Syntax: `bonobo run [-c cmd | -m mod | file | -] [arg]` .. todo:: implement -m, check if -c is of any use and if yes, implement it too. Implement args, too. + Bonobo RunC :::::::::::