Merge branch 'master' into develop

This commit is contained in:
Romain Dorgueil
2018-08-11 15:04:35 +02:00
45 changed files with 257 additions and 205 deletions

View File

@ -7,7 +7,8 @@
import sys
assert sys.version_info >= (3, 5), "Python 3.5+ is required to use Bonobo."
if sys.version_info < (3, 5):
raise RuntimeError('Python 3.5+ is required to use Bonobo.')
from bonobo._api import (
run,
@ -58,15 +59,14 @@ __version__ = __version__
def _repr_html_():
"""This allows to easily display a version snippet in Jupyter."""
from bonobo.util.pkgs import bonobo_packages
from bonobo.commands.version import get_versions
return (
'<div style="padding: 8px;">'
' <div style="float: left; width: 20px; height: 20px;">{}</div>'
' <pre style="white-space: nowrap; padding-left: 8px">{}</pre>'
"</div>"
).format(__logo__, "<br/>".join(get_versions(all=True)))
'</div>'
).format(__logo__, '<br/>'.join(get_versions(all=True)))
del sys

View File

@ -187,7 +187,7 @@ def get_examples_path(*pathsegments):
import os
import pathlib
return str(pathlib.Path(os.path.dirname(__file__), "examples", *pathsegments))
return str(pathlib.Path(os.path.dirname(__file__), 'examples', *pathsegments))
@api.register

View File

@ -1 +1 @@
__version__ = "0.6.2"
__version__ = '0.6.3'

View File

@ -26,7 +26,9 @@ def entrypoint(args=None):
commands = {}
def register_extension(ext, commands=commands):
def register_extension(ext):
nonlocal commands
try:
parser = subparsers.add_parser(ext.name)
if isinstance(ext.plugin, type) and issubclass(ext.plugin, BaseCommand):

View File

@ -33,9 +33,9 @@ class InitCommand(BaseCommand):
self.logger.info("Generated {} using template {!r}.".format(filename, template_name))
def create_package(self, *, filename):
name, ext = os.path.splitext(filename)
if ext != "":
raise ValueError("Package names should not have an extension.")
_, ext = os.path.splitext(filename)
if ext != '':
raise ValueError('Package names should not have an extension.')
try:
import medikit.commands

View File

@ -133,7 +133,7 @@ class ContextCurrifier:
try:
# todo yield from ? how to ?
processor.send(self._stack_values.pop())
except StopIteration as exc:
except StopIteration:
# This is normal, and wanted.
pass
else:

View File

@ -21,32 +21,32 @@ class Service(Option):
identifier. For example, you can create a Configurable that has a "database" Service in its attribute, meaning that
you'll define which database to use, by name, when creating the instance of this class, then provide an
implementation when running the graph using a strategy.
Example::
import bonobo
class QueryExtractor(bonobo.Configurable):
database = bonobo.Service(default='sqlalchemy.engine.default')
graph = bonobo.Graph(
QueryExtractor(database='sqlalchemy.engine.secondary'),
*more_transformations,
)
if __name__ == '__main__':
engine = create_engine('... dsn ...')
bonobo.run(graph, services={
'sqlalchemy.engine.secondary': engine
})
The main goal is not to tie transformations to actual dependencies, so the same can be run in different contexts
(stages like preprod, prod, or tenants like client1, client2, or anything you want).
.. attribute:: name
Service name will be used to retrieve the implementation at runtime.
"""
def __init__(self, name, __doc__=None):
@ -66,8 +66,13 @@ class Service(Option):
class Container(dict):
def __new__(cls, *args, **kwargs):
if len(args) == 1:
assert not len(kwargs), "only one usage at a time, my dear."
if not (args[0]):
if len(kwargs):
raise ValueError(
'You can either use {} with one positional argument or with keyword arguments, not both.'.format(
cls.__name__
)
)
if not args[0]:
return super().__new__(cls)
if isinstance(args[0], cls):
return cls

View File

@ -4,11 +4,11 @@ from types import GeneratorType
from colorama import Back, Fore, Style
from django.core.management import BaseCommand
from django.core.management.base import OutputWrapper
from mondrian import term
import bonobo
from bonobo.plugins.console import ConsoleOutputPlugin
from bonobo.util.term import CLEAR_EOL
from mondrian import term
from .utils import create_or_update
@ -59,8 +59,9 @@ class ETLCommand(BaseCommand):
graph_coll = (graph_coll,)
for i, graph in enumerate(graph_coll):
assert isinstance(graph, bonobo.Graph), "Invalid graph provided."
print(term.lightwhite("{}. {}".format(i + 1, graph.name)))
if not isinstance(graph, bonobo.Graph):
raise ValueError('Expected a Graph instance, got {!r}.'.format(graph))
print(term.lightwhite('{}. {}'.format(i + 1, graph.name)))
result = bonobo.run(graph, services=services, strategy=strategy)
results.append(result)
print(term.lightblack(" ... return value: " + str(result)))
@ -75,6 +76,6 @@ class ETLCommand(BaseCommand):
self.stderr = OutputWrapper(ConsoleOutputPlugin._stderr, ending=CLEAR_EOL + "\n")
self.stderr.style_func = lambda x: Fore.LIGHTRED_EX + Back.RED + "!" + Style.RESET_ALL + " " + x
self.run(*args, **kwargs)
self.run(*args, **options)
self.stdout, self.stderr = _stdout_backup, _stderr_backup

View File

@ -8,6 +8,11 @@ from bonobo.structs.graphs import PartialGraph
def get_graph(graph=None, *, _limit=(), _print=()):
"""
Extracts a list of cafes with on euro in Paris, renames the name, address and zipcode fields,
reorders the fields and formats to json and csv files.
"""
graph = graph or bonobo.Graph()
producer = (

View File

@ -1,5 +1,5 @@
"""
Extracts a list of fablabs in the world, restrict to the ones in france, then format it both for a nice console output
Extracts a list of fablabs in the world, restricted to the ones in france, then format its both for a nice console output
and a flat txt file.
.. graphviz::

View File

@ -2,16 +2,17 @@ import logging
import sys
from contextlib import contextmanager
from mondrian import term
from bonobo.util import deprecated
from bonobo.util.objects import Wrapper, get_name
from mondrian import term
@contextmanager
def recoverable(error_handler):
try:
yield
except Exception as exc: # pylint: disable=broad-except
except Exception: # pylint: disable=broad-except
error_handler(*sys.exc_info(), level=logging.ERROR)
@ -19,9 +20,9 @@ def recoverable(error_handler):
def unrecoverable(error_handler):
try:
yield
except Exception as exc: # pylint: disable=broad-except
except Exception: # pylint: disable=broad-except
error_handler(*sys.exc_info(), level=logging.ERROR)
raise # raise unrecoverableerror from exc ?
raise # raise unrecoverableerror from x ?
class Lifecycle:

View File

@ -190,7 +190,7 @@ class NodeExecutionContext(BaseContext, WithStatistics):
if self._stack:
try:
self._stack.teardown()
except Exception as exc:
except Exception:
self.fatal(sys.exc_info())
super().stop()
@ -207,7 +207,7 @@ class NodeExecutionContext(BaseContext, WithStatistics):
if self._input_type is not None:
raise RuntimeError("Cannot override input type, already have %r.", self._input_type)
if type(input_type) is not type:
if not isinstance(input_type, type):
raise UnrecoverableTypeError("Input types must be regular python types.")
if not issubclass(input_type, tuple):

View File

@ -28,7 +28,7 @@ DEFAULT_STRATEGY = "threadpool"
def create_strategy(name=None):
"""
Create a strategy, or just returns it if it's already one.
:param name:
:return: Strategy
"""

View File

@ -3,6 +3,8 @@ import html
import itertools
import pprint
from mondrian import term
from bonobo import settings
from bonobo.config import Configurable, Method, Option, use_context, use_no_input, use_raw_input
from bonobo.config.functools import transformation_factory
@ -10,7 +12,6 @@ from bonobo.config.processors import ContextProcessor, use_context_processor
from bonobo.constants import NOT_MODIFIED
from bonobo.util.objects import ValueHolder
from bonobo.util.term import CLEAR_EOL
from mondrian import term
__all__ = [
"FixedWindow",
@ -57,8 +58,6 @@ class Limit(Configurable):
def Tee(f):
from bonobo.constants import NOT_MODIFIED
@functools.wraps(f)
def wrapped(*args, **kwargs):
nonlocal f

View File

@ -11,9 +11,9 @@ class Filter(Configurable):
.. attribute:: filter
A callable used to filter lines.
If the callable returns a true-ish value, the input will be passed unmodified to the next items.
Otherwise, it'll be burnt.
"""

View File

@ -2,13 +2,13 @@ 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.plugins.console.ConsoleOutputPlugin, or bonobo.plugins.jupyter.JupyterOutputPlugin
that respectively permits an interactive output on an ANSI console and a rich output in a jupyter notebook. Note
that you most probably won't instanciate them by yourself at runtime, as it's the default behaviour of bonobo to use
them if your in a compatible context (aka an interactive terminal for the console plugin, or a jupyter notebook for
the notebook plugin.)
Warning: THE PLUGIN API IS PRE-ALPHA AND WILL EVOLVE BEFORE 1.0, DO NOT RELY ON IT BEING STABLE!
"""

View File

@ -7,7 +7,7 @@ from bonobo.errors import ValidationError
def to_bool(s):
if s is None:
return False
if type(s) is bool:
if isinstance(s, bool):
return s
if len(s):
if s.lower() in ("f", "false", "n", "no", "0"):
@ -31,7 +31,7 @@ class Setting:
def __init__(self, name, default=None, validator=None, formatter=None):
self.name = name
if default:
if default is not None:
self.default = default if callable(default) else lambda: default
else:
self.default = lambda: None

View File

@ -36,7 +36,6 @@ class GraphCursor:
"Expected something looking like a node, but got an Ellipsis (...). Did you forget to complete the graph?"
)
if len(nodes):
chain = self.graph.add_chain(*nodes, _input=self.last)
return GraphCursor(chain.graph, first=self.first, last=chain.output)

View File

@ -125,7 +125,7 @@ def BagType(typename, fields, *, verbose=False, module=None):
# message or automatically replace the field name with a valid name.
attrs = tuple(map(_uniquify(_make_valid_attr_name), fields))
if type(fields) is str:
if isinstance(fields, str):
raise TypeError("BagType does not support providing fields as a string.")
fields = list(map(str, fields))
typename = str(typename)
@ -133,6 +133,8 @@ def BagType(typename, fields, *, verbose=False, module=None):
for i, name in enumerate([typename] + fields):
if type(name) is not str:
raise TypeError("Type names and field names must be strings, got {name!r}".format(name=name))
if not isinstance(name, str):
raise TypeError('Type names and field names must be strings, got {name!r}'.format(name=name))
if not i:
if not name.isidentifier():
raise ValueError("Type names must be valid identifiers: {name!r}".format(name=name))

View File

@ -24,7 +24,7 @@ class ValueHolder:
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):

View File

@ -1,2 +1,2 @@
CLEAR_EOL = "\033[0K"
MOVE_CURSOR_UP = lambda n: "\033[{}A".format(n)
MOVE_CURSOR_UP = '\033[{}A'.format