Merge pull request #33 from hartym/stdlib_context_and_utils
Stdlib context and utils
This commit is contained in:
@ -10,7 +10,7 @@ install:
|
||||
- pip install coveralls
|
||||
script:
|
||||
- make clean docs test
|
||||
# - pip install pycountry
|
||||
# - bin/run_all_examples.sh
|
||||
- pip install pycountry
|
||||
- bin/run_all_examples.sh
|
||||
after_success:
|
||||
- coveralls
|
||||
|
||||
2
Makefile
2
Makefile
@ -1,7 +1,7 @@
|
||||
# This file has been auto-generated.
|
||||
# All changes will be lost, see Projectfile.
|
||||
#
|
||||
# Updated at 2017-04-24 21:23:56.683388
|
||||
# Updated at 2017-04-24 23:47:46.325867
|
||||
|
||||
PYTHON ?= $(shell which python)
|
||||
PYTHON_BASENAME ?= $(shell basename $(PYTHON))
|
||||
|
||||
@ -21,11 +21,10 @@ enable_features = {
|
||||
}
|
||||
|
||||
install_requires = [
|
||||
'blessings >=1.6,<1.7',
|
||||
'psutil >=5.0,<5.1',
|
||||
'colorama >=0.3,<0.4',
|
||||
'psutil >=5.2,<5.3',
|
||||
'requests >=2.12,<2.13',
|
||||
'stevedore >=1.19,<1.20',
|
||||
'toolz >=0.8,<0.9',
|
||||
]
|
||||
|
||||
extras_require = {
|
||||
@ -58,7 +57,6 @@ data_files = [
|
||||
entry_points = {
|
||||
'console_scripts': [
|
||||
'bonobo = bonobo.commands:entrypoint',
|
||||
'bb = bonobo.commands:entrypoint',
|
||||
],
|
||||
'bonobo.commands': [
|
||||
'init = bonobo.commands.init:register',
|
||||
|
||||
@ -19,6 +19,10 @@ from .io import __all__ as __all_io__
|
||||
from .util import __all__ as __all_util__
|
||||
|
||||
__all__ = __all_config__ + __all_context__ + __all_core__ + __all_io__ + __all_util__ + [
|
||||
'Bag',
|
||||
'ErrorBag'
|
||||
'Graph',
|
||||
'Token',
|
||||
'__version__',
|
||||
'create_strategy',
|
||||
'get_examples_path',
|
||||
@ -29,6 +33,9 @@ from .config import *
|
||||
from .context import *
|
||||
from .core import *
|
||||
from .io import *
|
||||
from .structs.bags import *
|
||||
from .structs.graphs import *
|
||||
from .structs.tokens import *
|
||||
from .util import *
|
||||
|
||||
DEFAULT_STRATEGY = 'threadpool'
|
||||
@ -67,16 +74,19 @@ def create_strategy(name=None):
|
||||
|
||||
return factory()
|
||||
|
||||
|
||||
def _is_interactive_console():
|
||||
import sys
|
||||
return sys.stdout.isatty()
|
||||
|
||||
|
||||
def _is_jupyter_notebook():
|
||||
try:
|
||||
return get_ipython().__class__.__name__ == 'ZMQInteractiveShell'
|
||||
except NameError:
|
||||
return False
|
||||
|
||||
|
||||
def run(graph, *chain, strategy=None, plugins=None):
|
||||
if len(chain):
|
||||
warnings.warn('DEPRECATED. You should pass a Graph instance instead of a chain.')
|
||||
@ -98,5 +108,6 @@ def run(graph, *chain, strategy=None, plugins=None):
|
||||
|
||||
return strategy.execute(graph, plugins=plugins)
|
||||
|
||||
|
||||
del sys
|
||||
del warnings
|
||||
|
||||
6
bonobo/constants.py
Normal file
6
bonobo/constants.py
Normal file
@ -0,0 +1,6 @@
|
||||
from bonobo.structs.tokens import Token
|
||||
|
||||
BEGIN = Token('Begin')
|
||||
END = Token('End')
|
||||
INHERIT_INPUT = Token('InheritInput')
|
||||
NOT_MODIFIED = Token('NotModified')
|
||||
@ -1,15 +1,16 @@
|
||||
import traceback
|
||||
import sys
|
||||
from functools import partial
|
||||
from queue import Empty
|
||||
from time import sleep
|
||||
|
||||
from bonobo.constants import BEGIN, END, NOT_MODIFIED, INHERIT_INPUT
|
||||
from bonobo.context.processors import get_context_processors
|
||||
from bonobo.core.bags import Bag, INHERIT_INPUT, ErrorBag
|
||||
from bonobo.core.errors import InactiveReadableError
|
||||
from bonobo.core.inputs import Input
|
||||
from bonobo.core.statistics import WithStatistics
|
||||
from bonobo.errors import InactiveReadableError
|
||||
from bonobo.structs.bags import Bag, ErrorBag
|
||||
from bonobo.util.objects import Wrapper
|
||||
from bonobo.util.tokens import BEGIN, END, NOT_MODIFIED
|
||||
|
||||
|
||||
class GraphExecutionContext:
|
||||
@ -76,7 +77,7 @@ class GraphExecutionContext:
|
||||
def ensure_tuple(tuple_or_mixed):
|
||||
if isinstance(tuple_or_mixed, tuple):
|
||||
return tuple_or_mixed
|
||||
return (tuple_or_mixed,)
|
||||
return (tuple_or_mixed, )
|
||||
|
||||
|
||||
class LoopingExecutionContext(Wrapper):
|
||||
@ -164,9 +165,15 @@ class LoopingExecutionContext(Wrapper):
|
||||
:return: to hell
|
||||
"""
|
||||
|
||||
from blessings import Terminal
|
||||
term = Terminal()
|
||||
print(term.bold(term.red('\U0001F4A3 {} in {}'.format(type(exc).__name__, self.wrapped))))
|
||||
from colorama import Fore, Style
|
||||
print(
|
||||
Style.BRIGHT,
|
||||
Fore.RED,
|
||||
'\U0001F4A3 {} in {}'.format(type(exc).__name__, self.wrapped),
|
||||
Style.RESET_ALL,
|
||||
sep='',
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(trace)
|
||||
|
||||
|
||||
@ -178,6 +185,14 @@ class PluginExecutionContext(LoopingExecutionContext):
|
||||
# plugins, for example if it depends on an external service.
|
||||
super().__init__(wrapped(self), parent)
|
||||
|
||||
def start(self):
|
||||
super().start()
|
||||
|
||||
try:
|
||||
self.wrapped.initialize()
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
self.handle_error(exc, traceback.format_exc())
|
||||
|
||||
def shutdown(self):
|
||||
try:
|
||||
self.wrapped.finalize()
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from functools import partial
|
||||
|
||||
import types
|
||||
|
||||
_CONTEXT_PROCESSORS_ATTR = '__processors__'
|
||||
@ -34,7 +36,19 @@ class ContextProcessor:
|
||||
return self.func(*args, **kwargs)
|
||||
|
||||
|
||||
def add_context_processor(cls_or_func, context_processor):
|
||||
getattr(cls_or_func, _CONTEXT_PROCESSORS_ATTR).append(context_processor)
|
||||
|
||||
|
||||
def contextual(cls_or_func):
|
||||
"""
|
||||
Make sure an element has the context processors collection.
|
||||
|
||||
:param cls_or_func:
|
||||
"""
|
||||
if not add_context_processor.__name__ in cls_or_func.__dict__:
|
||||
setattr(cls_or_func, add_context_processor.__name__, partial(add_context_processor, cls_or_func))
|
||||
|
||||
if isinstance(cls_or_func, types.FunctionType):
|
||||
try:
|
||||
getattr(cls_or_func, _CONTEXT_PROCESSORS_ATTR)
|
||||
@ -44,6 +58,7 @@ def contextual(cls_or_func):
|
||||
|
||||
if not _CONTEXT_PROCESSORS_ATTR in cls_or_func.__dict__:
|
||||
setattr(cls_or_func, _CONTEXT_PROCESSORS_ATTR, [])
|
||||
|
||||
_processors = getattr(cls_or_func, _CONTEXT_PROCESSORS_ATTR)
|
||||
for name, value in cls_or_func.__dict__.items():
|
||||
if isinstance(value, ContextProcessor):
|
||||
|
||||
@ -1,15 +1,10 @@
|
||||
""" Core required libraries. """
|
||||
|
||||
from .bags import Bag, ErrorBag
|
||||
from .graphs import Graph
|
||||
from .services import inject, service
|
||||
from .strategies.executor import ThreadPoolExecutorStrategy, ProcessPoolExecutorStrategy
|
||||
from .strategies.naive import NaiveStrategy
|
||||
|
||||
__all__ = [
|
||||
'Bag',
|
||||
'ErrorBag',
|
||||
'Graph',
|
||||
'NaiveStrategy',
|
||||
'ProcessPoolExecutorStrategy',
|
||||
'ThreadPoolExecutorStrategy',
|
||||
|
||||
@ -17,9 +17,9 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from queue import Queue
|
||||
|
||||
from bonobo.core.errors import AbstractError, InactiveWritableError, InactiveReadableError
|
||||
from bonobo.errors import AbstractError, InactiveWritableError, InactiveReadableError
|
||||
from bonobo.constants import BEGIN, END
|
||||
from bonobo.util import noop
|
||||
from bonobo.util.tokens import BEGIN, END
|
||||
|
||||
BUFFER_SIZE = 8192
|
||||
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
class Plugin:
|
||||
def initialize(self, context):
|
||||
pass
|
||||
|
||||
def run(self, context):
|
||||
pass
|
||||
|
||||
def finalize(self, context):
|
||||
pass
|
||||
@ -1,12 +1,10 @@
|
||||
import time
|
||||
from concurrent.futures import Executor
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from threading import Thread
|
||||
|
||||
from concurrent.futures import Executor, ProcessPoolExecutor, ThreadPoolExecutor
|
||||
|
||||
from bonobo.constants import BEGIN, END
|
||||
from bonobo.core.strategies.base import Strategy
|
||||
from bonobo.util.tokens import BEGIN, END
|
||||
from ..bags import Bag
|
||||
from bonobo.structs.bags import Bag
|
||||
|
||||
|
||||
class ExecutorStrategy(Strategy):
|
||||
@ -29,13 +27,16 @@ class ExecutorStrategy(Strategy):
|
||||
futures = []
|
||||
|
||||
for plugin_context in context.plugins:
|
||||
|
||||
def _runner(plugin_context=plugin_context):
|
||||
plugin_context.start()
|
||||
plugin_context.loop()
|
||||
plugin_context.stop()
|
||||
|
||||
futures.append(executor.submit(_runner))
|
||||
|
||||
for node_context in context.nodes:
|
||||
|
||||
def _runner(node_context=node_context):
|
||||
node_context.start()
|
||||
node_context.loop()
|
||||
@ -59,32 +60,3 @@ class ThreadPoolExecutorStrategy(ExecutorStrategy):
|
||||
|
||||
class ProcessPoolExecutorStrategy(ExecutorStrategy):
|
||||
executor_factory = ProcessPoolExecutor
|
||||
|
||||
|
||||
class ThreadCollectionStrategy(Strategy):
|
||||
def execute(self, graph, *args, plugins=None, **kwargs):
|
||||
print(type(self), 'execute', graph, args, plugins, kwargs)
|
||||
context = self.create_graph_execution_context(graph, plugins=plugins)
|
||||
context.recv(BEGIN, Bag(), END)
|
||||
|
||||
threads = []
|
||||
|
||||
# for plugin_context in context.plugins:
|
||||
# threads.append(executor.submit(plugin_context.run))
|
||||
|
||||
for component_context in context.components:
|
||||
thread = Thread(target=component_context.run)
|
||||
threads.append(thread)
|
||||
thread.start()
|
||||
|
||||
# XXX TODO PLUGINS
|
||||
while context.alive and len(threads):
|
||||
time.sleep(0.1)
|
||||
threads = list(filter(lambda thread: thread.is_alive, threads))
|
||||
|
||||
# for plugin_context in context.plugins:
|
||||
# plugin_context.shutdown()
|
||||
|
||||
# executor.shutdown()
|
||||
|
||||
return context
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
from bonobo.constants import BEGIN, END
|
||||
from bonobo.core.strategies.base import Strategy
|
||||
from bonobo.util.tokens import BEGIN, END
|
||||
|
||||
from ..bags import Bag
|
||||
from bonobo.structs.bags import Bag
|
||||
|
||||
|
||||
class NaiveStrategy(Strategy):
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from blessings import Terminal
|
||||
|
||||
from bonobo import Tee, JsonWriter, Graph, get_examples_path
|
||||
from bonobo.ext.opendatasoft import OpenDataSoftAPI
|
||||
|
||||
from colorama import Fore, Style
|
||||
try:
|
||||
import pycountry
|
||||
except ImportError as exc:
|
||||
@ -15,7 +14,6 @@ API_DATASET = 'fablabs-in-the-world'
|
||||
API_NETLOC = 'datanova.laposte.fr'
|
||||
ROWS = 100
|
||||
|
||||
t = Terminal()
|
||||
__path__ = os.path.dirname(__file__)
|
||||
|
||||
|
||||
@ -39,7 +37,7 @@ def filter_france(row):
|
||||
|
||||
|
||||
def display(row):
|
||||
print(t.bold(row.get('name')))
|
||||
print(Style.BRIGHT, row.get('name'), Style.RESET_ALL, sep='')
|
||||
|
||||
address = list(
|
||||
filter(
|
||||
@ -50,10 +48,10 @@ def display(row):
|
||||
)
|
||||
)
|
||||
|
||||
print(' - {}: {address}'.format(t.blue('address'), address=', '.join(address)))
|
||||
print(' - {}: {links}'.format(t.blue('links'), links=', '.join(row['links'])))
|
||||
print(' - {}: {geometry}'.format(t.blue('geometry'), **row))
|
||||
print(' - {}: {source}'.format(t.blue('source'), source='datanova/' + API_DATASET))
|
||||
print(' - {}address{}: {address}'.format(Fore.BLUE, Style.RESET_ALL, address=', '.join(address)))
|
||||
print(' - {}links{}: {links}'.format(Fore.BLUE, Style.RESET_ALL, links=', '.join(row['links'])))
|
||||
print(' - {}geometry{}: {geometry}'.format(Fore.BLUE, Style.RESET_ALL, **row))
|
||||
print(' - {}source{}: {source}'.format(Fore.BLUE, Style.RESET_ALL, source='datanova/' + API_DATASET))
|
||||
|
||||
|
||||
graph = Graph(
|
||||
|
||||
133
bonobo/examples/datasets/fablabs.txt
Normal file
133
bonobo/examples/datasets/fablabs.txt
Normal file
File diff suppressed because one or more lines are too long
@ -1,12 +1,7 @@
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
import bonobo
|
||||
|
||||
workdir = pathlib.Path(os.path.dirname(__file__))
|
||||
|
||||
graph = bonobo.Graph(
|
||||
bonobo.FileReader(path=workdir.joinpath('datasets/coffeeshops.txt')),
|
||||
bonobo.FileReader(path=bonobo.get_examples_path('datasets/coffeeshops.txt')),
|
||||
print,
|
||||
)
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from bonobo import run
|
||||
import bonobo
|
||||
|
||||
|
||||
def generate_data():
|
||||
@ -15,4 +15,11 @@ def output(x: str):
|
||||
print(x)
|
||||
|
||||
|
||||
run(generate_data, uppercase, output)
|
||||
graph = bonobo.Graph(
|
||||
generate_data,
|
||||
uppercase,
|
||||
output,
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
bonobo.run(graph)
|
||||
|
||||
0
bonobo/examples/utils/__init__.py
Normal file
0
bonobo/examples/utils/__init__.py
Normal file
6
bonobo/examples/utils/count.py
Normal file
6
bonobo/examples/utils/count.py
Normal file
@ -0,0 +1,6 @@
|
||||
import bonobo
|
||||
|
||||
graph = bonobo.Graph(range(42), bonobo.count, print)
|
||||
|
||||
if __name__ == '__main__':
|
||||
bonobo.run(graph)
|
||||
@ -14,22 +14,20 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import functools
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
|
||||
import blessings
|
||||
import os
|
||||
import psutil
|
||||
from colorama import Fore, Style
|
||||
|
||||
from bonobo.core.plugins import Plugin
|
||||
|
||||
t = blessings.Terminal()
|
||||
from bonobo.plugins import Plugin
|
||||
from bonobo.util.term import CLEAR_EOL, MOVE_CURSOR_UP
|
||||
|
||||
|
||||
@lru_cache(1)
|
||||
@functools.lru_cache(1)
|
||||
def memory_usage():
|
||||
import os, psutil
|
||||
process = psutil.Process(os.getpid())
|
||||
return process.get_memory_info()[0] / float(2 ** 20)
|
||||
return process.get_memory_info()[0] / float(2**20)
|
||||
|
||||
|
||||
# @lru_cache(64)
|
||||
@ -48,8 +46,7 @@ class ConsoleOutputPlugin(Plugin):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
def initialize(self):
|
||||
self.prefix = ''
|
||||
|
||||
def _write(self, graph_context, rewind):
|
||||
@ -80,26 +77,32 @@ class ConsoleOutputPlugin(Plugin):
|
||||
if component.alive:
|
||||
_line = ''.join(
|
||||
(
|
||||
t.black('({})'.format(i + 1)), ' ', t.bold(t.white('+')), ' ', component.name, ' ',
|
||||
component.get_statistics_as_string(debug=debug, profile=profile), ' ',
|
||||
Fore.BLACK, '({})'.format(i + 1), Style.RESET_ALL, ' ', Style.BRIGHT, '+', Style.RESET_ALL, ' ',
|
||||
component.name, ' ', component.get_statistics_as_string(debug=debug,
|
||||
profile=profile), Style.RESET_ALL, ' ',
|
||||
)
|
||||
)
|
||||
else:
|
||||
_line = t.black(
|
||||
''.join(
|
||||
(
|
||||
'({})'.format(i + 1), ' - ', component.name, ' ',
|
||||
component.get_statistics_as_string(debug=debug, profile=profile), ' ',
|
||||
)
|
||||
_line = ''.join(
|
||||
(
|
||||
Fore.BLACK, '({})'.format(i + 1), ' - ', component.name, ' ',
|
||||
component.get_statistics_as_string(debug=debug, profile=profile), Style.RESET_ALL, ' ',
|
||||
)
|
||||
)
|
||||
print(prefix + _line + t.clear_eol)
|
||||
print(prefix + _line + '\033[0K')
|
||||
|
||||
if append:
|
||||
# todo handle multiline
|
||||
print(' `->', ' '.join('{0}: {1}'.format(t.bold(t.white(k)), v) for k, v in append), t.clear_eol)
|
||||
print(
|
||||
''.join(
|
||||
(
|
||||
' `-> ', ' '.join('{}{}{}: {}'.format(Style.BRIGHT, k, Style.RESET_ALL, v)
|
||||
for k, v in append), CLEAR_EOL
|
||||
)
|
||||
)
|
||||
)
|
||||
t_cnt += 1
|
||||
|
||||
if rewind:
|
||||
print(t.clear_eol)
|
||||
print(t.move_up * (t_cnt + 2))
|
||||
print(CLEAR_EOL)
|
||||
print(MOVE_CURSOR_UP(t_cnt + 2))
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from bonobo.core.plugins import Plugin
|
||||
from bonobo.ext.jupyter.widget import BonoboWidget
|
||||
from bonobo.plugins import Plugin
|
||||
|
||||
try:
|
||||
import IPython.core.display
|
||||
@ -14,8 +14,7 @@ except ImportError as e:
|
||||
|
||||
|
||||
class JupyterOutputPlugin(Plugin):
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
def initialize(self):
|
||||
self.widget = BonoboWidget()
|
||||
IPython.core.display.display(self.widget)
|
||||
|
||||
|
||||
@ -19,13 +19,14 @@ class OpenDataSoftAPI(Configurable):
|
||||
scheme = Option(str, default='https')
|
||||
netloc = Option(str, default='data.opendatasoft.com')
|
||||
path = Option(path_str, default='/api/records/1.0/search/')
|
||||
rows = Option(int, default=100)
|
||||
rows = Option(int, default=500)
|
||||
limit = Option(int, default=None)
|
||||
timezone = Option(str, default='Europe/Paris')
|
||||
kwargs = Option(dict, default=dict)
|
||||
|
||||
@ContextProcessor
|
||||
def compute_path(self, context):
|
||||
params = (('dataset', self.dataset), ('rows', self.rows), ('timezone', self.timezone)) + tuple(sorted(self.kwargs.items()))
|
||||
params = (('dataset', self.dataset), ('timezone', self.timezone)) + tuple(sorted(self.kwargs.items()))
|
||||
yield self.endpoint.format(scheme=self.scheme, netloc=self.netloc, path=self.path) + '?' + urlencode(params)
|
||||
|
||||
@ContextProcessor
|
||||
@ -33,8 +34,10 @@ class OpenDataSoftAPI(Configurable):
|
||||
yield ValueHolder(0)
|
||||
|
||||
def __call__(self, base_url, start, *args, **kwargs):
|
||||
while True:
|
||||
url = '{}&start={start}'.format(base_url, start=start.value)
|
||||
while (not self.limit) or (self.limit > start):
|
||||
url = '{}&start={start}&rows={rows}'.format(
|
||||
base_url, start=start.value, rows=self.rows if not self.limit else min(self.rows, self.limit - start)
|
||||
)
|
||||
resp = requests.get(url)
|
||||
records = resp.json().get('records', [])
|
||||
|
||||
|
||||
23
bonobo/plugins.py
Normal file
23
bonobo/plugins.py
Normal file
@ -0,0 +1,23 @@
|
||||
class Plugin:
|
||||
"""
|
||||
A plugin is an extension to the core behavior of bonobo. If you're writing transformations, you should not need
|
||||
to use this interface.
|
||||
|
||||
For examples, you can read bonobo.ext.console.ConsoleOutputPlugin, or bonobo.ext.jupyter.JupyterOutputPlugin that
|
||||
respectively permits an interactive output on an ANSI console and a rich output in a jupyter notebook.
|
||||
|
||||
Warning: THE PLUGIN API IS PRE-ALPHA AND WILL EVOLVE BEFORE 1.0, DO NOT RELY ON IT BEING STABLE!
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
def initialize(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
def finalize(self):
|
||||
pass
|
||||
0
bonobo/structs/__init__.py
Normal file
0
bonobo/structs/__init__.py
Normal file
@ -1,14 +1,12 @@
|
||||
import itertools
|
||||
|
||||
from bonobo.util.tokens import Token
|
||||
from bonobo.constants import INHERIT_INPUT
|
||||
|
||||
__all__ = [
|
||||
'Bag',
|
||||
'ErrorBag',
|
||||
]
|
||||
|
||||
INHERIT_INPUT = Token('InheritInput')
|
||||
|
||||
|
||||
class Bag:
|
||||
def __init__(self, *args, _flags=None, _parent=None, **kwargs):
|
||||
@ -28,8 +26,8 @@ class Bag:
|
||||
if self._parent is None:
|
||||
return self._kwargs
|
||||
return {
|
||||
** self._parent.kwargs,
|
||||
** self._kwargs,
|
||||
**self._parent.kwargs,
|
||||
**self._kwargs,
|
||||
}
|
||||
|
||||
@property
|
||||
@ -1,4 +1,4 @@
|
||||
from bonobo.util.tokens import BEGIN
|
||||
from bonobo.constants import BEGIN
|
||||
|
||||
|
||||
class Graph:
|
||||
@ -6,9 +6,3 @@ class Token:
|
||||
|
||||
def __repr__(self):
|
||||
return '<{}>'.format(self.__name__)
|
||||
|
||||
|
||||
BEGIN = Token('Begin')
|
||||
END = Token('End')
|
||||
|
||||
NOT_MODIFIED = Token('NotModified')
|
||||
@ -3,18 +3,20 @@
|
||||
import functools
|
||||
from pprint import pprint as _pprint
|
||||
|
||||
import blessings
|
||||
from colorama import Fore, Style
|
||||
|
||||
from .helpers import console_run, jupyter_run
|
||||
from .tokens import NOT_MODIFIED
|
||||
from bonobo.constants import NOT_MODIFIED
|
||||
from bonobo.context.processors import contextual
|
||||
from bonobo.structs.bags import Bag
|
||||
from bonobo.util.objects import ValueHolder
|
||||
from bonobo.util.term import CLEAR_EOL
|
||||
|
||||
__all__ = [
|
||||
'Limit',
|
||||
'NOT_MODIFIED',
|
||||
'PrettyPrint',
|
||||
'Tee',
|
||||
'console_run',
|
||||
'jupyter_run',
|
||||
'count',
|
||||
'noop',
|
||||
'pprint',
|
||||
]
|
||||
@ -33,7 +35,7 @@ def Limit(n=10):
|
||||
if i <= n:
|
||||
yield NOT_MODIFIED
|
||||
|
||||
_limit.__name__ = 'limit({})'.format(n)
|
||||
_limit.__name__ = 'Limit({})'.format(n)
|
||||
return _limit
|
||||
|
||||
|
||||
@ -47,26 +49,47 @@ def Tee(f):
|
||||
return wrapped
|
||||
|
||||
|
||||
@contextual
|
||||
def count(counter, *args, **kwargs):
|
||||
counter += 1
|
||||
|
||||
|
||||
@count.add_context_processor
|
||||
def _count_counter(self, context):
|
||||
counter = ValueHolder(0)
|
||||
yield counter
|
||||
context.send(Bag(counter.value))
|
||||
|
||||
|
||||
pprint = Tee(_pprint)
|
||||
|
||||
|
||||
def PrettyPrint(title_keys=('title', 'name', 'id'), print_values=True, sort=True):
|
||||
term = blessings.Terminal()
|
||||
|
||||
def _pprint(*args, **kwargs):
|
||||
nonlocal title_keys, term, sort, print_values
|
||||
nonlocal title_keys, sort, print_values
|
||||
|
||||
row = args[0]
|
||||
for key in title_keys:
|
||||
if key in row:
|
||||
print(term.bold(row.get(key)))
|
||||
print(Style.BRIGHT, row.get(key), Style.RESET_ALL, sep='')
|
||||
break
|
||||
|
||||
if print_values:
|
||||
for k in sorted(row) if sort else row:
|
||||
print(
|
||||
' • {t.blue}{k}{t.normal} : {t.black}({tp}){t.normal} {v}{t.clear_eol}'.
|
||||
format(k=k, v=repr(row[k]), t=term, tp=type(row[k]).__name__)
|
||||
' • ',
|
||||
Fore.BLUE,
|
||||
k,
|
||||
Style.RESET_ALL,
|
||||
' : ',
|
||||
Fore.BLACK,
|
||||
'(',
|
||||
type(row[k]).__name__,
|
||||
')',
|
||||
Style.RESET_ALL,
|
||||
' ',
|
||||
repr(row[k]),
|
||||
CLEAR_EOL,
|
||||
)
|
||||
|
||||
yield NOT_MODIFIED
|
||||
@ -76,41 +99,5 @@ def PrettyPrint(title_keys=('title', 'name', 'id'), print_values=True, sort=True
|
||||
return _pprint
|
||||
|
||||
|
||||
'''
|
||||
Old code from rdc.etl
|
||||
|
||||
def writehr(self, label=None):
|
||||
width = t.width or 80
|
||||
|
||||
if label:
|
||||
label = str(label)
|
||||
sys.stderr.write(t.black('·' * 4) + shade('{') + label + shade('}') + t.black('·' * (width - (6+len(label)) - 1)) + '\n')
|
||||
else:
|
||||
sys.stderr.write(t.black('·' * (width-1) + '\n'))
|
||||
|
||||
|
||||
def writeln(self, s):
|
||||
"""Output method."""
|
||||
sys.stderr.write(self.format(s) + '\n')
|
||||
|
||||
def initialize(self):
|
||||
self.lineno = 0
|
||||
|
||||
def transform(self, hash, channel=STDIN):
|
||||
"""Actual transformation."""
|
||||
self.lineno += 1
|
||||
if not self.condition or self.condition(hash):
|
||||
hash = hash.copy()
|
||||
hash = hash if not isinstance(self.field_filter, collections.Callable) else hash.restrict(self.field_filter)
|
||||
if self.clean:
|
||||
hash = hash.restrict(lambda k: len(k) and k[0] != '_')
|
||||
self.writehr(self.lineno)
|
||||
self.writeln(hash)
|
||||
self.writehr()
|
||||
sys.stderr.write('\n')
|
||||
yield hash
|
||||
'''
|
||||
|
||||
|
||||
def noop(*args, **kwargs): # pylint: disable=unused-argument
|
||||
return NOT_MODIFIED
|
||||
|
||||
@ -17,6 +17,168 @@ class Wrapper:
|
||||
|
||||
|
||||
class ValueHolder:
|
||||
"""
|
||||
Decorator holding a value in a given memory adress, effectively allowing to "hold" even an immutable typed value as the state of a
|
||||
node, allowing different actors to mutate it durably.
|
||||
|
||||
For the sake of concistency, all operator methods have been implemented (see https://docs.python.org/3/reference/datamodel.html) or
|
||||
at least all in a certain category, but it feels like a more correct method should exist, like with a getattr-something on the
|
||||
value. Let's see later.
|
||||
"""
|
||||
def __init__(self, value, *, type=None):
|
||||
self.value = value
|
||||
self.type = type
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.value < other
|
||||
|
||||
def __le__(self, other):
|
||||
return self.value <= other
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.value == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.value != other
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.value > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.value >= other
|
||||
|
||||
def __add__(self, other):
|
||||
return self.value + other
|
||||
|
||||
def __radd__(self, other):
|
||||
return other + self.value
|
||||
|
||||
def __iadd__(self, other):
|
||||
self.value += other
|
||||
|
||||
def __sub__(self, other):
|
||||
return self.value - other
|
||||
|
||||
def __rsub__(self, other):
|
||||
return other - self.value
|
||||
|
||||
def __isub__(self, other):
|
||||
self.value -= other
|
||||
|
||||
def __mul__(self, other):
|
||||
return self.value * other
|
||||
|
||||
def __rmul__(self, other):
|
||||
return other * self.value
|
||||
|
||||
def __imul__(self, other):
|
||||
self.value *= other
|
||||
|
||||
def __matmul__(self, other):
|
||||
return self.value @ other
|
||||
|
||||
def __rmatmul__(self, other):
|
||||
return other @ self.value
|
||||
|
||||
def __imatmul__(self, other):
|
||||
self.value @= other
|
||||
|
||||
def __truediv__(self, other):
|
||||
return self.value / other
|
||||
|
||||
def __rtruediv__(self, other):
|
||||
return other / self.value
|
||||
|
||||
def __itruediv__(self, other):
|
||||
self.value /= other
|
||||
|
||||
def __floordiv__(self, other):
|
||||
return self.value // other
|
||||
|
||||
def __rfloordiv__(self, other):
|
||||
return other // self.value
|
||||
|
||||
def __ifloordiv__(self, other):
|
||||
self.value //= other
|
||||
|
||||
def __mod__(self, other):
|
||||
return self.value % other
|
||||
|
||||
def __rmod__(self, other):
|
||||
return other % self.value
|
||||
|
||||
def __imod__(self, other):
|
||||
self.value %= other
|
||||
|
||||
def __divmod__(self, other):
|
||||
return divmod(self.value, other)
|
||||
|
||||
def __rdivmod__(self, other):
|
||||
return divmod(other, self.value)
|
||||
|
||||
def __pow__(self, other):
|
||||
return self.value ** other
|
||||
|
||||
def __rpow__(self, other):
|
||||
return other ** self.value
|
||||
|
||||
def __ipow__(self, other):
|
||||
self.value **= other
|
||||
|
||||
def __lshift__(self, other):
|
||||
return self.value << other
|
||||
|
||||
def __rlshift__(self, other):
|
||||
return other << self.value
|
||||
|
||||
def __ilshift__(self, other):
|
||||
self.value <<= other
|
||||
|
||||
def __rshift__(self, other):
|
||||
return self.value >> other
|
||||
|
||||
def __rrshift__(self, other):
|
||||
return other >> self.value
|
||||
|
||||
def __irshift__(self, other):
|
||||
self.value >>= other
|
||||
|
||||
def __and__(self, other):
|
||||
return self.value & other
|
||||
|
||||
def __rand__(self, other):
|
||||
return other & self.value
|
||||
|
||||
def __iand__(self, other):
|
||||
self.value &= other
|
||||
|
||||
def __xor__(self, other):
|
||||
return self.value ^ other
|
||||
|
||||
def __rxor__(self, other):
|
||||
return other ^ self.value
|
||||
|
||||
def __ixor__(self, other):
|
||||
self.value ^= other
|
||||
|
||||
def __or__(self, other):
|
||||
return self.value | other
|
||||
|
||||
def __ror__(self, other):
|
||||
return other | self.value
|
||||
|
||||
def __ior__(self, other):
|
||||
self.value |= other
|
||||
|
||||
def __neg__(self):
|
||||
return -self.value
|
||||
|
||||
def __pos__(self):
|
||||
return +self.value
|
||||
|
||||
def __abs__(self):
|
||||
return abs(self.value)
|
||||
|
||||
def __invert__(self):
|
||||
return ~self.value
|
||||
|
||||
|
||||
2
bonobo/util/term.py
Normal file
2
bonobo/util/term.py
Normal file
@ -0,0 +1,2 @@
|
||||
CLEAR_EOL = '\033[0K'
|
||||
MOVE_CURSOR_UP = lambda n: '\033[{}A'.format(n)
|
||||
@ -6,8 +6,7 @@ dependencies:
|
||||
- wheel=0.29.0=py35_0
|
||||
- pip:
|
||||
- psycopg2 >=2.6.1
|
||||
- blessings >=1.6,<1.7
|
||||
- colorama >=0.3,<0.4
|
||||
- psutil >=5.0,<5.1
|
||||
- requests >=2.12,<2.13
|
||||
- stevedore >=1.19,<1.20
|
||||
- toolz >=0.8,<0.9
|
||||
|
||||
@ -1,78 +0,0 @@
|
||||
----
|
||||
|
||||
Roadmap (in progress)
|
||||
:::::::::::::::::::::
|
||||
|
||||
Bonobo is young. This roadmap is alive, and will evolve. Its only purpose is to
|
||||
write down incoming things somewhere.
|
||||
|
||||
Version 0.2
|
||||
-----------
|
||||
|
||||
* Changelog
|
||||
* Migration guide
|
||||
* Update documentation
|
||||
* Threaded does not terminate anymore (fixed ?)
|
||||
* More tests
|
||||
|
||||
Bugs:
|
||||
|
||||
- KeyboardInterrupt does not work anymore. (fixed ?)
|
||||
- ThreadPool does not stop anymore. (fiexd ?)
|
||||
|
||||
Configuration
|
||||
.............
|
||||
|
||||
* Support for position arguments (options), required options are good candidates.
|
||||
|
||||
Context processors
|
||||
..................
|
||||
|
||||
* Be careful with order, especially with python 3.5. (done)
|
||||
* @contextual decorator is not clean enough. Once the behavior is right, find a
|
||||
way to use regular inheritance, without meta.
|
||||
* ValueHolder API not clean. Find a better way.
|
||||
|
||||
Random thoughts and things to do
|
||||
................................
|
||||
|
||||
* Class-tree for Graph and Nodes
|
||||
|
||||
* Class-tree for execution contexts:
|
||||
|
||||
* GraphExecutionContext
|
||||
* NodeExecutionContext
|
||||
* PluginExecutionContext
|
||||
|
||||
* Class-tree for ExecutionStrategies
|
||||
|
||||
* NaiveStrategy
|
||||
* PoolExecutionStrategy
|
||||
* ThreadPoolExecutionStrategy
|
||||
* ProcessPoolExecutionStrategy
|
||||
* ThreadExecutionStrategy
|
||||
* ProcessExecutionStrategy
|
||||
|
||||
* Class-tree for bags
|
||||
|
||||
* Bag
|
||||
* ErrorBag
|
||||
* InheritingBag
|
||||
|
||||
* Co-routines: for unordered, or even ordered but long io.
|
||||
|
||||
* "context processors": replace initialize/finalize by a generator that yields only once
|
||||
|
||||
|
||||
* "execute" function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def execute(graph: Graph, *, strategy: ExecutionStrategy, plugins: List[Plugin]) -> Execution:
|
||||
pass
|
||||
|
||||
* Handling console. Can we use a queue, and replace stdout / stderr ?
|
||||
|
||||
|
||||
|
||||
|
||||
41
setup.py
41
setup.py
@ -40,43 +40,36 @@ setup(
|
||||
name='bonobo',
|
||||
description='Bonobo',
|
||||
license='Apache License, Version 2.0',
|
||||
install_requires=[
|
||||
'blessings >=1.6,<1.7', 'psutil >=5.0,<5.1', 'requests >=2.12,<2.13',
|
||||
'stevedore >=1.19,<1.20', 'toolz >=0.8,<0.9'
|
||||
],
|
||||
install_requires=['colorama >=0.3,<0.4', 'psutil >=5.2,<5.3', 'requests >=2.12,<2.13', 'stevedore >=1.19,<1.20'],
|
||||
version=version,
|
||||
long_description=read('README.rst'),
|
||||
classifiers=read('classifiers.txt', tolines),
|
||||
packages=find_packages(exclude=['ez_setup', 'example', 'test']),
|
||||
include_package_data=True,
|
||||
data_files=[('share/jupyter/nbextensions/bonobo-jupyter', [
|
||||
'bonobo/ext/jupyter/static/extension.js',
|
||||
'bonobo/ext/jupyter/static/index.js',
|
||||
'bonobo/ext/jupyter/static/index.js.map'
|
||||
])],
|
||||
data_files=[
|
||||
(
|
||||
'share/jupyter/nbextensions/bonobo-jupyter', [
|
||||
'bonobo/ext/jupyter/static/extension.js', 'bonobo/ext/jupyter/static/index.js',
|
||||
'bonobo/ext/jupyter/static/index.js.map'
|
||||
]
|
||||
)
|
||||
],
|
||||
extras_require={
|
||||
'dev': [
|
||||
'coverage >=4.3,<4.4', 'mock >=2.0,<2.1', 'nose >=1.3,<1.4',
|
||||
'pylint >=1.6,<1.7', 'pytest >=3,<4', 'pytest-cov >=2.4,<2.5',
|
||||
'pytest-timeout >=1.2,<1.3', 'sphinx', 'sphinx_rtd_theme', 'yapf'
|
||||
'coverage >=4.3,<4.4', 'mock >=2.0,<2.1', 'nose >=1.3,<1.4', 'pylint >=1.6,<1.7', 'pytest >=3,<4',
|
||||
'pytest-cov >=2.4,<2.5', 'pytest-timeout >=1.2,<1.3', 'sphinx', 'sphinx_rtd_theme', 'yapf'
|
||||
],
|
||||
'jupyter': ['jupyter >=1.0,<1.1', 'ipywidgets >=6.0.0.beta5']
|
||||
},
|
||||
entry_points={
|
||||
'bonobo.commands': [
|
||||
'init = bonobo.commands.init:register',
|
||||
'run = bonobo.commands.run:register',
|
||||
'init = bonobo.commands.init:register', 'run = bonobo.commands.run:register',
|
||||
'version = bonobo.commands.version:register'
|
||||
],
|
||||
'console_scripts': [
|
||||
'bonobo = bonobo.commands:entrypoint',
|
||||
'bb = bonobo.commands:entrypoint'
|
||||
],
|
||||
'edgy.project.features':
|
||||
['bonobo = '
|
||||
'bonobo.ext.edgy.project.feature:BonoboFeature']
|
||||
'console_scripts': ['bonobo = bonobo.commands:entrypoint'],
|
||||
'edgy.project.features': ['bonobo = '
|
||||
'bonobo.ext.edgy.project.feature:BonoboFeature']
|
||||
},
|
||||
url='https://www.bonobo-project.org/',
|
||||
download_url=
|
||||
'https://github.com/python-bonobo/bonobo/tarball/{version}'.format(
|
||||
version=version), )
|
||||
download_url='https://github.com/python-bonobo/bonobo/tarball/{version}'.format(version=version),
|
||||
)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from bonobo import Graph, NaiveStrategy, Bag, contextual
|
||||
from bonobo.constants import BEGIN, END
|
||||
from bonobo.context.execution import GraphExecutionContext
|
||||
from bonobo.util.tokens import BEGIN, END
|
||||
|
||||
|
||||
def generate_integers():
|
||||
|
||||
@ -18,9 +18,9 @@ from queue import Empty
|
||||
|
||||
import pytest
|
||||
|
||||
from bonobo.core.errors import InactiveWritableError, InactiveReadableError
|
||||
from bonobo.constants import BEGIN, END
|
||||
from bonobo.core.inputs import Input
|
||||
from bonobo.util.tokens import BEGIN, END
|
||||
from bonobo.errors import InactiveWritableError, InactiveReadableError
|
||||
|
||||
|
||||
def test_input_runlevels():
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import pytest
|
||||
|
||||
from bonobo import Bag, CsvReader, CsvWriter
|
||||
from bonobo.constants import BEGIN, END
|
||||
from bonobo.context.execution import NodeExecutionContext
|
||||
from bonobo.util.testing import CapturingNodeExecutionContext
|
||||
from bonobo.util.tokens import BEGIN, END
|
||||
|
||||
|
||||
def test_write_csv_to_file(tmpdir):
|
||||
|
||||
@ -3,7 +3,7 @@ import pytest
|
||||
from bonobo import FileWriter, Bag, FileReader
|
||||
from bonobo.context.execution import NodeExecutionContext
|
||||
from bonobo.util.testing import CapturingNodeExecutionContext
|
||||
from bonobo.util.tokens import BEGIN, END
|
||||
from bonobo.constants import BEGIN, END
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import pytest
|
||||
|
||||
from bonobo import Bag, JsonWriter, JsonReader
|
||||
from bonobo.constants import BEGIN, END
|
||||
from bonobo.context.execution import NodeExecutionContext
|
||||
from bonobo.util.objects import ValueHolder
|
||||
from bonobo.util.testing import CapturingNodeExecutionContext
|
||||
from bonobo.util.tokens import BEGIN, END
|
||||
|
||||
|
||||
def test_write_json_to_file(tmpdir):
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from mock import Mock
|
||||
|
||||
from bonobo import Bag
|
||||
from bonobo.core.bags import INHERIT_INPUT
|
||||
from bonobo.constants import INHERIT_INPUT
|
||||
|
||||
args = ('foo', 'bar', )
|
||||
kwargs = dict(acme='corp')
|
||||
@ -1,7 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from bonobo.core.graphs import Graph
|
||||
from bonobo.util.tokens import BEGIN
|
||||
from bonobo import Graph, BEGIN
|
||||
|
||||
identity = lambda x: x
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from bonobo.util.tokens import Token
|
||||
from bonobo import Token
|
||||
|
||||
|
||||
def test_token_repr():
|
||||
Reference in New Issue
Block a user