2
Makefile
2
Makefile
@ -1,7 +1,7 @@
|
|||||||
# This file has been auto-generated.
|
# This file has been auto-generated.
|
||||||
# All changes will be lost, see Projectfile.
|
# All changes will be lost, see Projectfile.
|
||||||
#
|
#
|
||||||
# Updated at 2017-09-30 09:50:47.806007
|
# Updated at 2017-09-30 11:26:44.075878
|
||||||
|
|
||||||
PACKAGE ?= bonobo
|
PACKAGE ?= bonobo
|
||||||
PYTHON ?= $(shell which python)
|
PYTHON ?= $(shell which python)
|
||||||
|
|||||||
@ -29,7 +29,9 @@ python.setup(
|
|||||||
'bonobo = bonobo.commands:entrypoint',
|
'bonobo = bonobo.commands:entrypoint',
|
||||||
],
|
],
|
||||||
'bonobo.commands': [
|
'bonobo.commands': [
|
||||||
|
'convert = bonobo.commands.convert:register',
|
||||||
'init = bonobo.commands.init:register',
|
'init = bonobo.commands.init:register',
|
||||||
|
'inspect = bonobo.commands.inspect:register',
|
||||||
'run = bonobo.commands.run:register',
|
'run = bonobo.commands.run:register',
|
||||||
'version = bonobo.commands.version:register',
|
'version = bonobo.commands.version:register',
|
||||||
],
|
],
|
||||||
@ -56,3 +58,5 @@ python.add_requirements(
|
|||||||
'ipywidgets >=6.0.0,<7',
|
'ipywidgets >=6.0.0,<7',
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# vim: ft=python:
|
||||||
|
|||||||
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 inspect --graph bonobo/examples/tutorials/tut02e03_writeasmap.py | dot -o test_output.png -T png && bin/imgcat test_output.png
|
||||||
81
bonobo/commands/convert.py
Normal file
81
bonobo/commands/convert.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import mimetypes
|
||||||
|
import os
|
||||||
|
|
||||||
|
import bonobo
|
||||||
|
|
||||||
|
SHORTCUTS = {
|
||||||
|
'csv': 'text/csv',
|
||||||
|
'json': 'application/json',
|
||||||
|
'pickle': 'pickle',
|
||||||
|
'plain': 'text/plain',
|
||||||
|
'text': 'text/plain',
|
||||||
|
'txt': 'text/plain',
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTRY = {
|
||||||
|
'application/json': (bonobo.JsonReader, bonobo.JsonWriter),
|
||||||
|
'pickle': (bonobo.PickleReader, bonobo.PickleWriter),
|
||||||
|
'text/csv': (bonobo.CsvReader, bonobo.CsvWriter),
|
||||||
|
'text/plain': (bonobo.FileReader, bonobo.FileWriter),
|
||||||
|
}
|
||||||
|
|
||||||
|
READER = 'reader'
|
||||||
|
WRITER = 'writer'
|
||||||
|
|
||||||
|
|
||||||
|
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 name is None:
|
||||||
|
_, _ext = os.path.splitext(filename)
|
||||||
|
if _ext:
|
||||||
|
_ext = _ext[1:]
|
||||||
|
if _ext in SHORTCUTS:
|
||||||
|
name = SHORTCUTS[_ext]
|
||||||
|
|
||||||
|
if not name in REGISTRY:
|
||||||
|
raise RuntimeError(
|
||||||
|
'Could not resolve {factory_type} factory for {filename} ({name}). Try providing it explicitely using -{opt} <format>.'.
|
||||||
|
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
|
||||||
34
bonobo/commands/inspect.py
Normal file
34
bonobo/commands/inspect.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
DEFAULT_SERVICES_FILENAME = '_services.py'
|
import bonobo
|
||||||
DEFAULT_SERVICES_ATTR = 'get_services'
|
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'
|
DEFAULT_GRAPH_ATTR = 'get_graph'
|
||||||
@ -40,9 +40,10 @@ def _install_requirements(requirements):
|
|||||||
importlib.reload(site)
|
importlib.reload(site)
|
||||||
|
|
||||||
|
|
||||||
def execute(filename, module, install=False, quiet=False, verbose=False):
|
def read(filename, module, install=False, quiet=False, verbose=False, env=None):
|
||||||
|
import re
|
||||||
import runpy
|
import runpy
|
||||||
from bonobo import Graph, run, settings
|
from bonobo import Graph, settings
|
||||||
|
|
||||||
if quiet:
|
if quiet:
|
||||||
settings.QUIET.set(True)
|
settings.QUIET.set(True)
|
||||||
@ -50,6 +51,12 @@ def execute(filename, module, install=False, quiet=False, verbose=False):
|
|||||||
if verbose:
|
if verbose:
|
||||||
settings.DEBUG.set(True)
|
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 filename:
|
||||||
if os.path.isdir(filename):
|
if os.path.isdir(filename):
|
||||||
if install:
|
if install:
|
||||||
@ -81,17 +88,19 @@ def execute(filename, module, install=False, quiet=False, verbose=False):
|
|||||||
).format(len(graphs))
|
).format(len(graphs))
|
||||||
|
|
||||||
graph = list(graphs.values())[0]
|
graph = list(graphs.values())[0]
|
||||||
|
plugins = []
|
||||||
# todo if console and not quiet, then add the console plugin
|
services = get_default_services(
|
||||||
# todo when better console plugin, add it if console and just disable display
|
filename, context.get(DEFAULT_SERVICES_ATTR)() if DEFAULT_SERVICES_ATTR in context else None
|
||||||
return run(
|
|
||||||
graph,
|
|
||||||
plugins=[],
|
|
||||||
services=get_default_services(
|
|
||||||
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, env=None):
|
||||||
|
graph, plugins, services = read(filename, module, install, quiet, verbose, env)
|
||||||
|
|
||||||
|
return bonobo.run(graph, plugins=plugins, services=services)
|
||||||
|
|
||||||
|
|
||||||
def register_generic_run_arguments(parser, required=True):
|
def register_generic_run_arguments(parser, required=True):
|
||||||
source_group = parser.add_mutually_exclusive_group(required=required)
|
source_group = parser.add_mutually_exclusive_group(required=required)
|
||||||
@ -106,4 +115,5 @@ def register(parser):
|
|||||||
verbosity_group.add_argument('--quiet', '-q', action='store_true')
|
verbosity_group.add_argument('--quiet', '-q', action='store_true')
|
||||||
verbosity_group.add_argument('--verbose', '-v', action='store_true')
|
verbosity_group.add_argument('--verbose', '-v', action='store_true')
|
||||||
parser.add_argument('--install', '-I', action='store_true')
|
parser.add_argument('--install', '-I', action='store_true')
|
||||||
|
parser.add_argument('--env', '-e', action='append')
|
||||||
return execute
|
return execute
|
||||||
|
|||||||
@ -4,3 +4,5 @@ BEGIN = Token('Begin')
|
|||||||
END = Token('End')
|
END = Token('End')
|
||||||
INHERIT_INPUT = Token('InheritInput')
|
INHERIT_INPUT = Token('InheritInput')
|
||||||
NOT_MODIFIED = Token('NotModified')
|
NOT_MODIFIED = Token('NotModified')
|
||||||
|
DEFAULT_SERVICES_FILENAME = '_services.py'
|
||||||
|
DEFAULT_SERVICES_ATTR = 'get_services'
|
||||||
@ -1,36 +1,37 @@
|
|||||||
les montparnos, 65 boulevard Pasteur, 75015 Paris, France
|
Extérieur Quai, 5, rue d'Alsace, 75010 Paris, France
|
||||||
Coffee Chope, 344Vrue Vaugirard, 75015 Paris, France
|
Le Sully, 6 Bd henri IV, 75004 Paris, France
|
||||||
Café Lea, 5 rue Claude Bernard, 75005 Paris, France
|
|
||||||
Le Bellerive, 71 quai de Seine, 75019 Paris, France
|
|
||||||
Le drapeau de la fidelité, 21 rue Copreaux, 75015 Paris, France
|
|
||||||
O q de poule, 53 rue du ruisseau, 75018 Paris, France
|
O q de poule, 53 rue du ruisseau, 75018 Paris, France
|
||||||
Le café des amis, 125 rue Blomet, 75015 Paris, France
|
Le Pas Sage, 1 Passage du Grand Cerf, 75002 Paris, France
|
||||||
|
La Renaissance, 112 Rue Championnet, 75018 Paris, France
|
||||||
|
La Caravane, Rue de la Fontaine au Roi, 75011 Paris, France
|
||||||
Le chantereine, 51 Rue Victoire, 75009 Paris, France
|
Le chantereine, 51 Rue Victoire, 75009 Paris, France
|
||||||
Le Müller, 11 rue Feutrier, 75018 Paris, France
|
Le Müller, 11 rue Feutrier, 75018 Paris, France
|
||||||
Extérieur Quai, 5, rue d'Alsace, 75010 Paris, France
|
Le drapeau de la fidelité, 21 rue Copreaux, 75015 Paris, France
|
||||||
La Bauloise, 36 rue du hameau, 75015 Paris, France
|
Le café des amis, 125 rue Blomet, 75015 Paris, France
|
||||||
Le Dellac, 14 rue Rougemont, 75009 Paris, France
|
|
||||||
Le Bosquet, 46 avenue Bosquet, 75007 Paris, France
|
|
||||||
Le Sully, 6 Bd henri IV, 75004 Paris, France
|
|
||||||
Le Felteu, 1 rue Pecquay, 75004 Paris, France
|
|
||||||
Le bistrot de Maëlle et Augustin, 42 rue coquillère, 75001 Paris, France
|
|
||||||
Dédé la frite, 52 rue Notre-Dame des Victoires, 75002 Paris, France
|
|
||||||
Cardinal Saint-Germain, 11 boulevard Saint-Germain, 75005 Paris, France
|
|
||||||
Le Reynou, 2 bis quai de la mégisserie, 75001 Paris, France
|
|
||||||
Aux cadrans, 21 ter boulevard Diderot, 75012 Paris, France
|
|
||||||
Le Saint Jean, 23 rue des abbesses, 75018 Paris, France
|
|
||||||
La Renaissance, 112 Rue Championnet, 75018 Paris, France
|
|
||||||
Le Square, 31 rue Saint-Dominique, 75007 Paris, France
|
|
||||||
Les Arcades, 61 rue de Ponthieu, 75008 Paris, France
|
|
||||||
Le Kleemend's, 34 avenue Pierre Mendès-France, 75013 Paris, France
|
|
||||||
Assaporare Dix sur Dix, 75, avenue Ledru-Rollin, 75012 Paris, France
|
|
||||||
Café Pierre, 202 rue du faubourg st antoine, 75012 Paris, France
|
|
||||||
Café antoine, 17 rue Jean de la Fontaine, 75016 Paris, France
|
|
||||||
Au cerceau d'or, 129 boulevard sebastopol, 75002 Paris, France
|
|
||||||
La Caravane, Rue de la Fontaine au Roi, 75011 Paris, France
|
|
||||||
Le Pas Sage, 1 Passage du Grand Cerf, 75002 Paris, France
|
|
||||||
Le Café Livres, 10 rue Saint Martin, 75004 Paris, France
|
Le Café Livres, 10 rue Saint Martin, 75004 Paris, France
|
||||||
|
Le Bosquet, 46 avenue Bosquet, 75007 Paris, France
|
||||||
Le Chaumontois, 12 rue Armand Carrel, 75018 Paris, France
|
Le Chaumontois, 12 rue Armand Carrel, 75018 Paris, France
|
||||||
|
Le Kleemend's, 34 avenue Pierre Mendès-France, 75013 Paris, France
|
||||||
|
Café Pierre, 202 rue du faubourg st antoine, 75012 Paris, France
|
||||||
|
Les Arcades, 61 rue de Ponthieu, 75008 Paris, France
|
||||||
|
Le Square, 31 rue Saint-Dominique, 75007 Paris, France
|
||||||
|
Assaporare Dix sur Dix, 75, avenue Ledru-Rollin, 75012 Paris, France
|
||||||
|
Au cerceau d'or, 129 boulevard sebastopol, 75002 Paris, France
|
||||||
|
Aux cadrans, 21 ter boulevard Diderot, 75012 Paris, France
|
||||||
|
Café antoine, 17 rue Jean de la Fontaine, 75016 Paris, France
|
||||||
|
Café de la Mairie (du VIII), rue de Lisbonne, 75008 Paris, France
|
||||||
|
Café Lea, 5 rue Claude Bernard, 75005 Paris, France
|
||||||
|
Cardinal Saint-Germain, 11 boulevard Saint-Germain, 75005 Paris, France
|
||||||
|
Dédé la frite, 52 rue Notre-Dame des Victoires, 75002 Paris, France
|
||||||
|
La Bauloise, 36 rue du hameau, 75015 Paris, France
|
||||||
|
Le Bellerive, 71 quai de Seine, 75019 Paris, France
|
||||||
|
Le bistrot de Maëlle et Augustin, 42 rue coquillère, 75001 Paris, France
|
||||||
|
Le Dellac, 14 rue Rougemont, 75009 Paris, France
|
||||||
|
Le Felteu, 1 rue Pecquay, 75004 Paris, France
|
||||||
|
Le Reynou, 2 bis quai de la mégisserie, 75001 Paris, France
|
||||||
|
Le Saint Jean, 23 rue des abbesses, 75018 Paris, France
|
||||||
|
les montparnos, 65 boulevard Pasteur, 75015 Paris, France
|
||||||
|
L'antre d'eux, 16 rue DE MEZIERES, 75006 Paris, France
|
||||||
Drole d'endroit pour une rencontre, 58 rue de Montorgueil, 75002 Paris, France
|
Drole d'endroit pour une rencontre, 58 rue de Montorgueil, 75002 Paris, France
|
||||||
Le pari's café, 104 rue caulaincourt, 75018 Paris, France
|
Le pari's café, 104 rue caulaincourt, 75018 Paris, France
|
||||||
Le Poulailler, 60 rue saint-sabin, 75011 Paris, France
|
Le Poulailler, 60 rue saint-sabin, 75011 Paris, France
|
||||||
@ -62,7 +63,6 @@ Denfert café, 58 boulvevard Saint Jacques, 75014 Paris, France
|
|||||||
Le Café frappé, 95 rue Montmartre, 75002 Paris, France
|
Le Café frappé, 95 rue Montmartre, 75002 Paris, France
|
||||||
La Perle, 78 rue vieille du temple, 75003 Paris, France
|
La Perle, 78 rue vieille du temple, 75003 Paris, France
|
||||||
Le Descartes, 1 rue Thouin, 75005 Paris, France
|
Le Descartes, 1 rue Thouin, 75005 Paris, France
|
||||||
Bagels & Coffee Corner, Place de Clichy, 75017 Paris, France
|
|
||||||
Le petit club, 55 rue de la tombe Issoire, 75014 Paris, France
|
Le petit club, 55 rue de la tombe Issoire, 75014 Paris, France
|
||||||
Le Plein soleil, 90 avenue Parmentier, 75011 Paris, France
|
Le Plein soleil, 90 avenue Parmentier, 75011 Paris, France
|
||||||
Le Relais Haussmann, 146, boulevard Haussmann, 75008 Paris, France
|
Le Relais Haussmann, 146, boulevard Haussmann, 75008 Paris, France
|
||||||
@ -75,7 +75,6 @@ Extra old café, 307 fg saint Antoine, 75011 Paris, France
|
|||||||
Chez Fafa, 44 rue Vinaigriers, 75010 Paris, France
|
Chez Fafa, 44 rue Vinaigriers, 75010 Paris, France
|
||||||
En attendant l'or, 3 rue Faidherbe, 75011 Paris, France
|
En attendant l'or, 3 rue Faidherbe, 75011 Paris, France
|
||||||
Brûlerie San José, 30 rue des Petits-Champs, 75002 Paris, France
|
Brûlerie San José, 30 rue des Petits-Champs, 75002 Paris, France
|
||||||
Café de la Mairie (du VIII), rue de Lisbonne, 75008 Paris, France
|
|
||||||
Café Martin, 2 place Martin Nadaud, 75001 Paris, France
|
Café Martin, 2 place Martin Nadaud, 75001 Paris, France
|
||||||
Etienne, 14 rue Turbigo, Paris, 75001 Paris, France
|
Etienne, 14 rue Turbigo, Paris, 75001 Paris, France
|
||||||
L'ingénu, 184 bd Voltaire, 75011 Paris, France
|
L'ingénu, 184 bd Voltaire, 75011 Paris, France
|
||||||
@ -87,96 +86,97 @@ Le Germinal, 95 avenue Emile Zola, 75015 Paris, France
|
|||||||
Le Ragueneau, 202 rue Saint-Honoré, 75001 Paris, France
|
Le Ragueneau, 202 rue Saint-Honoré, 75001 Paris, France
|
||||||
Le refuge, 72 rue lamarck, 75018 Paris, France
|
Le refuge, 72 rue lamarck, 75018 Paris, France
|
||||||
Le sully, 13 rue du Faubourg Saint Denis, 75010 Paris, France
|
Le sully, 13 rue du Faubourg Saint Denis, 75010 Paris, France
|
||||||
|
Coffee Chope, 344Vrue Vaugirard, 75015 Paris, France
|
||||||
|
Le bal du pirate, 60 rue des bergers, 75015 Paris, France
|
||||||
|
zic zinc, 95 rue claude decaen, 75012 Paris, France
|
||||||
|
l'orillon bar, 35 rue de l'orillon, 75011 Paris, France
|
||||||
|
Le Zazabar, 116 Rue de Ménilmontant, 75020 Paris, France
|
||||||
|
L'Inévitable, 22 rue Linné, 75005 Paris, France
|
||||||
Le Dunois, 77 rue Dunois, 75013 Paris, France
|
Le Dunois, 77 rue Dunois, 75013 Paris, France
|
||||||
La Montagne Sans Geneviève, 13 Rue du Pot de Fer, 75005 Paris, France
|
Ragueneau, 202 rue Saint Honoré, 75001 Paris, France
|
||||||
Le Caminito, 48 rue du Dessous des Berges, 75013 Paris, France
|
Le Caminito, 48 rue du Dessous des Berges, 75013 Paris, France
|
||||||
|
Epicerie Musicale, 55bis quai de Valmy, 75010 Paris, France
|
||||||
Le petit Bretonneau, Le petit Bretonneau - à l'intérieur de l'Hôpital, 75018 Paris, France
|
Le petit Bretonneau, Le petit Bretonneau - à l'intérieur de l'Hôpital, 75018 Paris, France
|
||||||
|
Le Centenaire, 104 rue amelot, 75011 Paris, France
|
||||||
|
La Montagne Sans Geneviève, 13 Rue du Pot de Fer, 75005 Paris, France
|
||||||
|
Les Pères Populaires, 46 rue de Buzenval, 75020 Paris, France
|
||||||
|
Cafe de grenelle, 188 rue de Grenelle, 75007 Paris, France
|
||||||
|
Le relais de la victoire, 73 rue de la Victoire, 75009 Paris, France
|
||||||
La chaumière gourmande, Route de la Muette à Neuilly
|
La chaumière gourmande, Route de la Muette à Neuilly
|
||||||
Club hippique du Jardin d’Acclimatation, 75016 Paris, France
|
Club hippique du Jardin d’Acclimatation, 75016 Paris, France
|
||||||
Le bal du pirate, 60 rue des bergers, 75015 Paris, France
|
Le Brio, 216, rue Marcadet, 75018 Paris, France
|
||||||
Le Zazabar, 116 Rue de Ménilmontant, 75020 Paris, France
|
Caves populaires, 22 rue des Dames, 75017 Paris, France
|
||||||
L'antre d'eux, 16 rue DE MEZIERES, 75006 Paris, France
|
Caprice café, 12 avenue Jean Moulin, 75014 Paris, France
|
||||||
l'orillon bar, 35 rue de l'orillon, 75011 Paris, France
|
Tamm Bara, 7 rue Clisson, 75013 Paris, France
|
||||||
zic zinc, 95 rue claude decaen, 75012 Paris, France
|
L'anjou, 1 rue de Montholon, 75009 Paris, France
|
||||||
Les Pères Populaires, 46 rue de Buzenval, 75020 Paris, France
|
Café dans l'aerogare Air France Invalides, 2 rue Robert Esnault Pelterie, 75007 Paris, France
|
||||||
Epicerie Musicale, 55bis quai de Valmy, 75010 Paris, France
|
Chez Prune, 36 rue Beaurepaire, 75010 Paris, France
|
||||||
Le relais de la victoire, 73 rue de la Victoire, 75009 Paris, France
|
Au Vin Des Rues, 21 rue Boulard, 75014 Paris, France
|
||||||
Le Centenaire, 104 rue amelot, 75011 Paris, France
|
bistrot les timbrés, 14 rue d'alleray, 75015 Paris, France
|
||||||
Cafe de grenelle, 188 rue de Grenelle, 75007 Paris, France
|
Café beauveau, 9 rue de Miromesnil, 75008 Paris, France
|
||||||
Ragueneau, 202 rue Saint Honoré, 75001 Paris, France
|
|
||||||
Café Pistache, 9 rue des petits champs, 75001 Paris, France
|
Café Pistache, 9 rue des petits champs, 75001 Paris, France
|
||||||
La Cagnotte, 13 Rue Jean-Baptiste Dumay, 75020 Paris, France
|
La Cagnotte, 13 Rue Jean-Baptiste Dumay, 75020 Paris, France
|
||||||
Le Killy Jen, 28 bis boulevard Diderot, 75012 Paris, France
|
|
||||||
Café beauveau, 9 rue de Miromesnil, 75008 Paris, France
|
|
||||||
le 1 cinq, 172 rue de vaugirard, 75015 Paris, France
|
le 1 cinq, 172 rue de vaugirard, 75015 Paris, France
|
||||||
|
Le Killy Jen, 28 bis boulevard Diderot, 75012 Paris, France
|
||||||
Les Artisans, 106 rue Lecourbe, 75015 Paris, France
|
Les Artisans, 106 rue Lecourbe, 75015 Paris, France
|
||||||
Peperoni, 83 avenue de Wagram, 75001 Paris, France
|
Peperoni, 83 avenue de Wagram, 75001 Paris, France
|
||||||
Le Brio, 216, rue Marcadet, 75018 Paris, France
|
le lutece, 380 rue de vaugirard, 75015 Paris, France
|
||||||
Tamm Bara, 7 rue Clisson, 75013 Paris, France
|
Brasiloja, 16 rue Ganneron, 75018 Paris, France
|
||||||
Café dans l'aerogare Air France Invalides, 2 rue Robert Esnault Pelterie, 75007 Paris, France
|
Rivolux, 16 rue de Rivoli, 75004 Paris, France
|
||||||
bistrot les timbrés, 14 rue d'alleray, 75015 Paris, France
|
|
||||||
Caprice café, 12 avenue Jean Moulin, 75014 Paris, France
|
|
||||||
Caves populaires, 22 rue des Dames, 75017 Paris, France
|
|
||||||
Au Vin Des Rues, 21 rue Boulard, 75014 Paris, France
|
|
||||||
Chez Prune, 36 rue Beaurepaire, 75010 Paris, France
|
|
||||||
L'Inévitable, 22 rue Linné, 75005 Paris, France
|
|
||||||
L'anjou, 1 rue de Montholon, 75009 Paris, France
|
|
||||||
Botak cafe, 1 rue Paul albert, 75018 Paris, France
|
|
||||||
Bistrot Saint-Antoine, 58 rue du Fbg Saint-Antoine, 75012 Paris, France
|
|
||||||
Chez Oscar, 11/13 boulevard Beaumarchais, 75004 Paris, France
|
|
||||||
Le Piquet, 48 avenue de la Motte Picquet, 75015 Paris, France
|
|
||||||
L'avant comptoir, 3 carrefour de l'Odéon, 75006 Paris, France
|
|
||||||
le chateau d'eau, 67 rue du Château d'eau, 75010 Paris, France
|
|
||||||
Les Vendangeurs, 6/8 rue Stanislas, 75006 Paris, France
|
|
||||||
maison du vin, 52 rue des plantes, 75014 Paris, France
|
|
||||||
Le Tournebride, 104 rue Mouffetard, 75005 Paris, France
|
|
||||||
Le Fronton, 63 rue de Ponthieu, 75008 Paris, France
|
|
||||||
Le BB (Bouchon des Batignolles), 2 rue Lemercier, 75017 Paris, France
|
|
||||||
La cantine de Zoé, 136 rue du Faubourg poissonnière, 75010 Paris, France
|
|
||||||
Chez Rutabaga, 16 rue des Petits Champs, 75002 Paris, France
|
|
||||||
Les caves populaires, 22 rue des Dames, 75017 Paris, France
|
|
||||||
Le Plomb du cantal, 3 rue Gaîté, 75014 Paris, France
|
|
||||||
Trois pièces cuisine, 101 rue des dames, 75017 Paris, France
|
|
||||||
La Brocante, 10 rue Rossini, 75009 Paris, France
|
|
||||||
Le Zinc, 61 avenue de la Motte Picquet, 75015 Paris, France
|
|
||||||
Chez Luna, 108 rue de Ménilmontant, 75020 Paris, France
|
|
||||||
Le bar Fleuri, 1 rue du Plateau, 75019 Paris, France
|
|
||||||
La Liberté, 196 rue du faubourg saint-antoine, 75012 Paris, France
|
|
||||||
La cantoche de Paname, 40 Boulevard Beaumarchais, 75011 Paris, France
|
|
||||||
Le Saint René, 148 Boulevard de Charonne, 75020 Paris, France
|
|
||||||
Café Clochette, 16 avenue Richerand, 75010 Paris, France
|
|
||||||
L'européen, 21 Bis Boulevard Diderot, 75012 Paris, France
|
L'européen, 21 Bis Boulevard Diderot, 75012 Paris, France
|
||||||
NoMa, 39 rue Notre Dame de Nazareth, 75003 Paris, France
|
NoMa, 39 rue Notre Dame de Nazareth, 75003 Paris, France
|
||||||
le lutece, 380 rue de vaugirard, 75015 Paris, France
|
|
||||||
O'Paris, 1 Rue des Envierges, 75020 Paris, France
|
O'Paris, 1 Rue des Envierges, 75020 Paris, France
|
||||||
Rivolux, 16 rue de Rivoli, 75004 Paris, France
|
Café Clochette, 16 avenue Richerand, 75010 Paris, France
|
||||||
Brasiloja, 16 rue Ganneron, 75018 Paris, France
|
La cantoche de Paname, 40 Boulevard Beaumarchais, 75011 Paris, France
|
||||||
Institut des Cultures d'Islam, 19-23 rue Léon, 75018 Paris, France
|
Le Saint René, 148 Boulevard de Charonne, 75020 Paris, France
|
||||||
Canopy Café associatif, 19 rue Pajol, 75018 Paris, France
|
La Liberté, 196 rue du faubourg saint-antoine, 75012 Paris, France
|
||||||
Petits Freres des Pauvres, 47 rue de Batignolles, 75017 Paris, France
|
Chez Rutabaga, 16 rue des Petits Champs, 75002 Paris, France
|
||||||
Le Lucernaire, 53 rue Notre-Dame des Champs, 75006 Paris, France
|
Le BB (Bouchon des Batignolles), 2 rue Lemercier, 75017 Paris, France
|
||||||
L'Angle, 28 rue de Ponthieu, 75008 Paris, France
|
La Brocante, 10 rue Rossini, 75009 Paris, France
|
||||||
Le Café d'avant, 35 rue Claude Bernard, 75005 Paris, France
|
Le Plomb du cantal, 3 rue Gaîté, 75014 Paris, France
|
||||||
Café Dupont, 198 rue de la Convention, 75015 Paris, France
|
Les caves populaires, 22 rue des Dames, 75017 Paris, France
|
||||||
Le Sévigné, 15 rue du Parc Royal, 75003 Paris, France
|
Chez Luna, 108 rue de Ménilmontant, 75020 Paris, France
|
||||||
L'Entracte, place de l'opera, 75002 Paris, France
|
Le bar Fleuri, 1 rue du Plateau, 75019 Paris, France
|
||||||
Panem, 18 rue de Crussol, 75011 Paris, France
|
Trois pièces cuisine, 101 rue des dames, 75017 Paris, France
|
||||||
Au pays de Vannes, 34 bis rue de Wattignies, 75012 Paris, France
|
Le Zinc, 61 avenue de la Motte Picquet, 75015 Paris, France
|
||||||
l'Eléphant du nil, 125 Rue Saint-Antoine, 75004 Paris, France
|
La cantine de Zoé, 136 rue du Faubourg poissonnière, 75010 Paris, France
|
||||||
L'âge d'or, 26 rue du Docteur Magnan, 75013 Paris, France
|
Les Vendangeurs, 6/8 rue Stanislas, 75006 Paris, France
|
||||||
Le Comptoir, 354 bis rue Vaugirard, 75015 Paris, France
|
L'avant comptoir, 3 carrefour de l'Odéon, 75006 Paris, France
|
||||||
L'horizon, 93, rue de la Roquette, 75011 Paris, France
|
Botak cafe, 1 rue Paul albert, 75018 Paris, France
|
||||||
L'empreinte, 54, avenue Daumesnil, 75012 Paris, France
|
le chateau d'eau, 67 rue du Château d'eau, 75010 Paris, France
|
||||||
Café Victor, 10 boulevard Victor, 75015 Paris, France
|
Bistrot Saint-Antoine, 58 rue du Fbg Saint-Antoine, 75012 Paris, France
|
||||||
Café Varenne, 36 rue de Varenne, 75007 Paris, France
|
Chez Oscar, 11/13 boulevard Beaumarchais, 75004 Paris, France
|
||||||
Le Brigadier, 12 rue Blanche, 75009 Paris, France
|
Le Fronton, 63 rue de Ponthieu, 75008 Paris, France
|
||||||
Waikiki, 10 rue d"Ulm, 75005 Paris, France
|
Le Piquet, 48 avenue de la Motte Picquet, 75015 Paris, France
|
||||||
Le Parc Vaugirard, 358 rue de Vaugirard, 75015 Paris, France
|
Le Tournebride, 104 rue Mouffetard, 75005 Paris, France
|
||||||
Pari's Café, 174 avenue de Clichy, 75017 Paris, France
|
maison du vin, 52 rue des plantes, 75014 Paris, France
|
||||||
Melting Pot, 3 rue de Lagny, 75020 Paris, France
|
L'entrepôt, 157 rue Bercy 75012 Paris, 75012 Paris, France
|
||||||
le Zango, 58 rue Daguerre, 75014 Paris, France
|
|
||||||
Chez Miamophile, 6 rue Mélingue, 75019 Paris, France
|
|
||||||
Le café Monde et Médias, Place de la République, 75003 Paris, France
|
Le café Monde et Médias, Place de la République, 75003 Paris, France
|
||||||
Café rallye tournelles, 11 Quai de la Tournelle, 75005 Paris, France
|
Café rallye tournelles, 11 Quai de la Tournelle, 75005 Paris, France
|
||||||
Brasserie le Morvan, 61 rue du château d'eau, 75010 Paris, France
|
Brasserie le Morvan, 61 rue du château d'eau, 75010 Paris, France
|
||||||
L'entrepôt, 157 rue Bercy 75012 Paris, 75012 Paris, France
|
Chez Miamophile, 6 rue Mélingue, 75019 Paris, France
|
||||||
|
Panem, 18 rue de Crussol, 75011 Paris, France
|
||||||
|
Petits Freres des Pauvres, 47 rue de Batignolles, 75017 Paris, France
|
||||||
|
Café Dupont, 198 rue de la Convention, 75015 Paris, France
|
||||||
|
L'Angle, 28 rue de Ponthieu, 75008 Paris, France
|
||||||
|
Institut des Cultures d'Islam, 19-23 rue Léon, 75018 Paris, France
|
||||||
|
Canopy Café associatif, 19 rue Pajol, 75018 Paris, France
|
||||||
|
L'Entracte, place de l'opera, 75002 Paris, France
|
||||||
|
Le Sévigné, 15 rue du Parc Royal, 75003 Paris, France
|
||||||
|
Le Café d'avant, 35 rue Claude Bernard, 75005 Paris, France
|
||||||
|
Le Lucernaire, 53 rue Notre-Dame des Champs, 75006 Paris, France
|
||||||
|
Le Brigadier, 12 rue Blanche, 75009 Paris, France
|
||||||
|
L'âge d'or, 26 rue du Docteur Magnan, 75013 Paris, France
|
||||||
|
Bagels & Coffee Corner, Place de Clichy, 75017 Paris, France
|
||||||
|
Café Victor, 10 boulevard Victor, 75015 Paris, France
|
||||||
|
L'empreinte, 54, avenue Daumesnil, 75012 Paris, France
|
||||||
|
L'horizon, 93, rue de la Roquette, 75011 Paris, France
|
||||||
|
Waikiki, 10 rue d"Ulm, 75005 Paris, France
|
||||||
|
Au pays de Vannes, 34 bis rue de Wattignies, 75012 Paris, France
|
||||||
|
Café Varenne, 36 rue de Varenne, 75007 Paris, France
|
||||||
|
l'Eléphant du nil, 125 Rue Saint-Antoine, 75004 Paris, France
|
||||||
|
Le Comptoir, 354 bis rue Vaugirard, 75015 Paris, France
|
||||||
|
Le Parc Vaugirard, 358 rue de Vaugirard, 75015 Paris, France
|
||||||
|
le Zango, 58 rue Daguerre, 75014 Paris, France
|
||||||
|
Melting Pot, 3 rue de Lagny, 75020 Paris, France
|
||||||
|
Pari's Café, 174 avenue de Clichy, 75017 Paris, France
|
||||||
@ -48,11 +48,6 @@ def normalize(row):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def filter_france(row):
|
|
||||||
if row.get('country') == 'France':
|
|
||||||
yield row
|
|
||||||
|
|
||||||
|
|
||||||
def display(row):
|
def display(row):
|
||||||
print(Style.BRIGHT, row.get('name'), Style.RESET_ALL, sep='')
|
print(Style.BRIGHT, row.get('name'), Style.RESET_ALL, sep='')
|
||||||
|
|
||||||
@ -95,7 +90,7 @@ graph = bonobo.Graph(
|
|||||||
dataset=API_DATASET, netloc=API_NETLOC, timezone='Europe/Paris'
|
dataset=API_DATASET, netloc=API_NETLOC, timezone='Europe/Paris'
|
||||||
),
|
),
|
||||||
normalize,
|
normalize,
|
||||||
filter_france,
|
bonobo.Filter(filter=lambda row: row.get('country') == 'France'),
|
||||||
bonobo.JsonWriter(path='fablabs.txt', ioformat='arg0'),
|
bonobo.JsonWriter(path='fablabs.txt', ioformat='arg0'),
|
||||||
bonobo.Tee(display),
|
bonobo.Tee(display),
|
||||||
)
|
)
|
||||||
|
|||||||
41
bonobo/examples/nodes/bags.py
Normal file
41
bonobo/examples/nodes/bags.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
"""
|
||||||
|
Example on how to use :class:`bonobo.Bag` instances to pass flexible args/kwargs to the next callable.
|
||||||
|
|
||||||
|
.. graphviz::
|
||||||
|
|
||||||
|
digraph {
|
||||||
|
rankdir = LR;
|
||||||
|
stylesheet = "../_static/graphs.css";
|
||||||
|
|
||||||
|
BEGIN [shape="point"];
|
||||||
|
BEGIN -> "extract()" -> "transform(...)" -> "load(...)";
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
from bonobo import Bag, Graph
|
||||||
|
|
||||||
|
|
||||||
|
def extract():
|
||||||
|
yield Bag(topic='foo')
|
||||||
|
yield Bag(topic='bar')
|
||||||
|
yield Bag(topic='baz')
|
||||||
|
|
||||||
|
|
||||||
|
def transform(topic: str):
|
||||||
|
return Bag.inherit(title=topic.title(), rand=randint(10, 99))
|
||||||
|
|
||||||
|
|
||||||
|
def load(topic: str, title: str, rand: int):
|
||||||
|
print('{} ({}) wait={}'.format(title, topic, rand))
|
||||||
|
|
||||||
|
|
||||||
|
graph = Graph()
|
||||||
|
graph.add_chain(extract, transform, load)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from bonobo import run
|
||||||
|
|
||||||
|
run(graph)
|
||||||
43
bonobo/examples/nodes/dicts.py
Normal file
43
bonobo/examples/nodes/dicts.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
"""
|
||||||
|
Example on how to use symple python dictionaries to communicate between transformations.
|
||||||
|
|
||||||
|
.. graphviz::
|
||||||
|
|
||||||
|
digraph {
|
||||||
|
rankdir = LR;
|
||||||
|
stylesheet = "../_static/graphs.css";
|
||||||
|
|
||||||
|
BEGIN [shape="point"];
|
||||||
|
BEGIN -> "extract()" -> "transform(row: dict)" -> "load(row: dict)";
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
from bonobo import Graph
|
||||||
|
|
||||||
|
|
||||||
|
def extract():
|
||||||
|
yield {'topic': 'foo'}
|
||||||
|
yield {'topic': 'bar'}
|
||||||
|
yield {'topic': 'baz'}
|
||||||
|
|
||||||
|
|
||||||
|
def transform(row: dict):
|
||||||
|
return {
|
||||||
|
'topic': row['topic'].title(),
|
||||||
|
'randint': randint(10, 99),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load(row: dict):
|
||||||
|
print(row)
|
||||||
|
|
||||||
|
|
||||||
|
graph = Graph(extract, transform, load)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from bonobo import run
|
||||||
|
|
||||||
|
run(graph)
|
||||||
18
bonobo/examples/nodes/factory.py
Normal file
18
bonobo/examples/nodes/factory.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import bonobo
|
||||||
|
from bonobo.commands.run import get_default_services
|
||||||
|
from bonobo.nodes.factory import Factory
|
||||||
|
from bonobo.nodes.io.json import JsonDictItemsReader
|
||||||
|
|
||||||
|
normalize = Factory()
|
||||||
|
normalize[0].str().title()
|
||||||
|
normalize.move(0, 'title')
|
||||||
|
normalize.move(0, 'address')
|
||||||
|
|
||||||
|
graph = bonobo.Graph(
|
||||||
|
JsonDictItemsReader('datasets/coffeeshops.json'),
|
||||||
|
normalize,
|
||||||
|
bonobo.PrettyPrinter(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
bonobo.run(graph, services=get_default_services(__file__))
|
||||||
39
bonobo/examples/nodes/strings.py
Normal file
39
bonobo/examples/nodes/strings.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
"""
|
||||||
|
Example on how to use symple python strings to communicate between transformations.
|
||||||
|
|
||||||
|
.. graphviz::
|
||||||
|
|
||||||
|
digraph {
|
||||||
|
rankdir = LR;
|
||||||
|
stylesheet = "../_static/graphs.css";
|
||||||
|
|
||||||
|
BEGIN [shape="point"];
|
||||||
|
BEGIN -> "extract()" -> "transform(s: str)" -> "load(s: str)";
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
from bonobo import Graph
|
||||||
|
|
||||||
|
|
||||||
|
def extract():
|
||||||
|
yield 'foo'
|
||||||
|
yield 'bar'
|
||||||
|
yield 'baz'
|
||||||
|
|
||||||
|
|
||||||
|
def transform(s: str):
|
||||||
|
return '{} ({})'.format(s.title(), randint(10, 99))
|
||||||
|
|
||||||
|
|
||||||
|
def load(s: str):
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
|
||||||
|
graph = Graph(extract, transform, load)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from bonobo import run
|
||||||
|
|
||||||
|
run(graph)
|
||||||
@ -81,7 +81,7 @@ class ConsoleOutputPlugin(Plugin):
|
|||||||
print(line + CLEAR_EOL, file=sys.stderr)
|
print(line + CLEAR_EOL, file=sys.stderr)
|
||||||
|
|
||||||
alive_color = Style.BRIGHT
|
alive_color = Style.BRIGHT
|
||||||
dead_color = (Style.BRIGHT + Fore.BLACK) if self.iswindows else Fore.BLACK
|
dead_color = Style.BRIGHT + Fore.BLACK
|
||||||
|
|
||||||
for i in context.graph.topologically_sorted_indexes:
|
for i in context.graph.topologically_sorted_indexes:
|
||||||
node = context[i]
|
node = context[i]
|
||||||
|
|||||||
219
bonobo/nodes/factory.py
Normal file
219
bonobo/nodes/factory.py
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
import functools
|
||||||
|
import warnings
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from bonobo import Bag
|
||||||
|
from bonobo.config import Configurable, Method
|
||||||
|
|
||||||
|
_isarg = lambda item: type(item) is int
|
||||||
|
_iskwarg = lambda item: type(item) is str
|
||||||
|
|
||||||
|
|
||||||
|
class Operation():
|
||||||
|
def __init__(self, item, callable):
|
||||||
|
self.item = item
|
||||||
|
self.callable = callable
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<operation {} on {}>'.format(self.callable.__name__, self.item)
|
||||||
|
|
||||||
|
def apply(self, *args, **kwargs):
|
||||||
|
if _isarg(self.item):
|
||||||
|
return (*args[0:self.item], self.callable(args[self.item]), *args[self.item + 1:]), kwargs
|
||||||
|
if _iskwarg(self.item):
|
||||||
|
return args, {**kwargs, self.item: self.callable(kwargs.get(self.item))}
|
||||||
|
raise RuntimeError('Houston, we have a problem...')
|
||||||
|
|
||||||
|
|
||||||
|
class FactoryOperation():
|
||||||
|
def __init__(self, factory, callable):
|
||||||
|
self.factory = factory
|
||||||
|
self.callable = callable
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<factory operation {}>'.format(self.callable.__name__)
|
||||||
|
|
||||||
|
def apply(self, *args, **kwargs):
|
||||||
|
return self.callable(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
CURSOR_TYPES = {}
|
||||||
|
|
||||||
|
|
||||||
|
def operation(mixed):
|
||||||
|
def decorator(m, ctype=mixed):
|
||||||
|
def lazy_operation(self, *args, **kwargs):
|
||||||
|
@functools.wraps(m)
|
||||||
|
def actual_operation(x):
|
||||||
|
return m(self, x, *args, **kwargs)
|
||||||
|
|
||||||
|
self.factory.operations.append(Operation(self.item, actual_operation))
|
||||||
|
return CURSOR_TYPES[ctype](self.factory, self.item) if ctype else self
|
||||||
|
|
||||||
|
return lazy_operation
|
||||||
|
|
||||||
|
return decorator if isinstance(mixed, str) else decorator(mixed, ctype=None)
|
||||||
|
|
||||||
|
|
||||||
|
def factory_operation(m):
|
||||||
|
def lazy_operation(self, *config):
|
||||||
|
@functools.wraps(m)
|
||||||
|
def actual_operation(*args, **kwargs):
|
||||||
|
return m(self, *config, *args, **kwargs)
|
||||||
|
|
||||||
|
self.operations.append(FactoryOperation(self, actual_operation))
|
||||||
|
return self
|
||||||
|
|
||||||
|
return lazy_operation
|
||||||
|
|
||||||
|
|
||||||
|
class Cursor():
|
||||||
|
_type = None
|
||||||
|
|
||||||
|
def __init__(self, factory, item):
|
||||||
|
self.factory = factory
|
||||||
|
self.item = item
|
||||||
|
|
||||||
|
@operation('dict')
|
||||||
|
def dict(self, x):
|
||||||
|
return x if isinstance(x, dict) else dict(x)
|
||||||
|
|
||||||
|
@operation('int')
|
||||||
|
def int(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@operation('str')
|
||||||
|
def str(self, x):
|
||||||
|
return x if isinstance(x, str) else str(x)
|
||||||
|
|
||||||
|
@operation('list')
|
||||||
|
def list(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@operation('tuple')
|
||||||
|
def tuple(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
"""
|
||||||
|
Fallback to type methods if they exist, for example StrCursor.upper will use str.upper if not overriden, etc.
|
||||||
|
|
||||||
|
:param item:
|
||||||
|
"""
|
||||||
|
if self._type and item in self._type.__dict__:
|
||||||
|
method = self._type.__dict__[item]
|
||||||
|
|
||||||
|
@operation
|
||||||
|
@functools.wraps(method)
|
||||||
|
def _operation(self, x, *args, **kwargs):
|
||||||
|
return method(x, *args, **kwargs)
|
||||||
|
|
||||||
|
setattr(self, item, partial(_operation, self))
|
||||||
|
return getattr(self, item)
|
||||||
|
|
||||||
|
raise AttributeError('Unknown operation {}.{}().'.format(
|
||||||
|
type(self).__name__,
|
||||||
|
item,
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
CURSOR_TYPES['default'] = Cursor
|
||||||
|
|
||||||
|
|
||||||
|
class DictCursor(Cursor):
|
||||||
|
_type = dict
|
||||||
|
|
||||||
|
@operation('default')
|
||||||
|
def get(self, x, path):
|
||||||
|
return x.get(path)
|
||||||
|
|
||||||
|
@operation
|
||||||
|
def map_keys(self, x, mapping):
|
||||||
|
return {mapping.get(k): v for k, v in x.items()}
|
||||||
|
|
||||||
|
|
||||||
|
CURSOR_TYPES['dict'] = DictCursor
|
||||||
|
|
||||||
|
|
||||||
|
class StringCursor(Cursor):
|
||||||
|
_type = str
|
||||||
|
|
||||||
|
|
||||||
|
CURSOR_TYPES['str'] = StringCursor
|
||||||
|
|
||||||
|
|
||||||
|
class Factory(Configurable):
|
||||||
|
initialize = Method(required=False)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
warnings.warn(
|
||||||
|
__file__ +
|
||||||
|
' is experimental, API may change in the future, use it as a preview only and knowing the risks.',
|
||||||
|
FutureWarning
|
||||||
|
)
|
||||||
|
super(Factory, self).__init__(*args, **kwargs)
|
||||||
|
self.default_cursor_type = 'default'
|
||||||
|
self.operations = []
|
||||||
|
|
||||||
|
if self.initialize is not None:
|
||||||
|
self.initialize(self)
|
||||||
|
|
||||||
|
@factory_operation
|
||||||
|
def move(self, _from, _to, *args, **kwargs):
|
||||||
|
if _from == _to:
|
||||||
|
return args, kwargs
|
||||||
|
|
||||||
|
if _isarg(_from):
|
||||||
|
value = args[_from]
|
||||||
|
args = args[:_from] + args[_from + 1:]
|
||||||
|
elif _iskwarg(_from):
|
||||||
|
value = kwargs[_from]
|
||||||
|
kwargs = {k: v for k, v in kwargs if k != _from}
|
||||||
|
else:
|
||||||
|
raise RuntimeError('Houston, we have a problem...')
|
||||||
|
|
||||||
|
if _isarg(_to):
|
||||||
|
return (*args[:_to], value, *args[_to + 1:]), kwargs
|
||||||
|
elif _iskwarg(_to):
|
||||||
|
return args, {**kwargs, _to: value}
|
||||||
|
else:
|
||||||
|
raise RuntimeError('Houston, we have a problem...')
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
print('factory call on', args, kwargs)
|
||||||
|
for operation in self.operations:
|
||||||
|
args, kwargs = operation.apply(*args, **kwargs)
|
||||||
|
print(' ... after', operation, 'got', args, kwargs)
|
||||||
|
return Bag(*args, **kwargs)
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return CURSOR_TYPES[self.default_cursor_type](self, item)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
f = Factory()
|
||||||
|
|
||||||
|
f[0].dict().map_keys({'foo': 'F00'})
|
||||||
|
f['foo'].str().upper()
|
||||||
|
|
||||||
|
print('operations:', f.operations)
|
||||||
|
print(f({'foo': 'bisou'}, foo='blah'))
|
||||||
|
'''
|
||||||
|
specs:
|
||||||
|
|
||||||
|
- rename keys of an input dict (in args, or kwargs) using a translation map.
|
||||||
|
|
||||||
|
|
||||||
|
f = Factory()
|
||||||
|
|
||||||
|
f[0]
|
||||||
|
f['xxx'] =
|
||||||
|
|
||||||
|
f[0].dict().get('foo.bar').move_to('foo.baz').apply(str.upper)
|
||||||
|
f[0].get('foo.*').items().map(str.lower)
|
||||||
|
|
||||||
|
f['foo'].keys_map({
|
||||||
|
'a': 'b'
|
||||||
|
})
|
||||||
|
|
||||||
|
'''
|
||||||
@ -4,6 +4,7 @@ from bonobo.config.processors import ContextProcessor
|
|||||||
from bonobo.constants import NOT_MODIFIED
|
from bonobo.constants import NOT_MODIFIED
|
||||||
from bonobo.nodes.io.base import FileHandler, IOFormatEnabled
|
from bonobo.nodes.io.base import FileHandler, IOFormatEnabled
|
||||||
from bonobo.nodes.io.file import FileReader, FileWriter
|
from bonobo.nodes.io.file import FileReader, FileWriter
|
||||||
|
from bonobo.structs.bags import Bag
|
||||||
|
|
||||||
|
|
||||||
class JsonHandler(FileHandler):
|
class JsonHandler(FileHandler):
|
||||||
@ -19,6 +20,12 @@ class JsonReader(IOFormatEnabled, FileReader, JsonHandler):
|
|||||||
yield self.get_output(line)
|
yield self.get_output(line)
|
||||||
|
|
||||||
|
|
||||||
|
class JsonDictItemsReader(JsonReader):
|
||||||
|
def read(self, fs, file):
|
||||||
|
for line in self.loader(file).items():
|
||||||
|
yield Bag(*line)
|
||||||
|
|
||||||
|
|
||||||
class JsonWriter(IOFormatEnabled, FileWriter, JsonHandler):
|
class JsonWriter(IOFormatEnabled, FileWriter, JsonHandler):
|
||||||
@ContextProcessor
|
@ContextProcessor
|
||||||
def envelope(self, context, fs, file, lineno):
|
def envelope(self, context, fs, file, lineno):
|
||||||
|
|||||||
@ -41,15 +41,12 @@ class RateLimited(Configurable):
|
|||||||
|
|
||||||
@ContextProcessor
|
@ContextProcessor
|
||||||
def bucket(self, context):
|
def bucket(self, context):
|
||||||
print(context)
|
|
||||||
bucket = RateLimitBucket(self.initial, self.amount, self.period)
|
bucket = RateLimitBucket(self.initial, self.amount, self.period)
|
||||||
bucket.start()
|
bucket.start()
|
||||||
print(bucket)
|
|
||||||
yield bucket
|
yield bucket
|
||||||
bucket.stop()
|
bucket.stop()
|
||||||
bucket.join()
|
bucket.join()
|
||||||
|
|
||||||
def call(self, bucket, *args, **kwargs):
|
def call(self, bucket, *args, **kwargs):
|
||||||
print(bucket, args, kwargs)
|
|
||||||
bucket.wait()
|
bucket.wait()
|
||||||
return self.handler(*args, **kwargs)
|
return self.handler(*args, **kwargs)
|
||||||
|
|||||||
@ -1,6 +1,21 @@
|
|||||||
Command-line
|
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
|
Bonobo Init
|
||||||
:::::::::::
|
:::::::::::
|
||||||
|
|
||||||
@ -8,7 +23,17 @@ Create an empty project, ready to use bonobo.
|
|||||||
|
|
||||||
Syntax: `bonobo init`
|
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
|
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.
|
.. todo:: implement -m, check if -c is of any use and if yes, implement it too. Implement args, too.
|
||||||
|
|
||||||
|
|
||||||
Bonobo RunC
|
Bonobo RunC
|
||||||
:::::::::::
|
:::::::::::
|
||||||
|
|
||||||
|
|||||||
3
setup.py
3
setup.py
@ -67,7 +67,8 @@ setup(
|
|||||||
},
|
},
|
||||||
entry_points={
|
entry_points={
|
||||||
'bonobo.commands': [
|
'bonobo.commands': [
|
||||||
'init = bonobo.commands.init:register', 'run = bonobo.commands.run:register',
|
'convert = bonobo.commands.convert:register', 'init = bonobo.commands.init:register',
|
||||||
|
'inspect = bonobo.commands.inspect:register', 'run = bonobo.commands.run:register',
|
||||||
'version = bonobo.commands.version:register'
|
'version = bonobo.commands.version:register'
|
||||||
],
|
],
|
||||||
'console_scripts': ['bonobo = bonobo.commands:entrypoint']
|
'console_scripts': ['bonobo = bonobo.commands:entrypoint']
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import runpy
|
|||||||
import sys
|
import sys
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pathlib
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -96,3 +97,30 @@ def test_version(runner, capsys):
|
|||||||
out = out.strip()
|
out = out.strip()
|
||||||
assert out.startswith('bonobo ')
|
assert out.startswith('bonobo ')
|
||||||
assert __version__ in out
|
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'
|
||||||
|
|||||||
22
tests/util/get_passed_env.py
Normal file
22
tests/util/get_passed_env.py
Normal file
@ -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)
|
||||||
Reference in New Issue
Block a user