Merge branch 'master' into develop
This commit is contained in:
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1 +1 @@
|
||||
__version__ = "0.6.2"
|
||||
__version__ = '0.6.3'
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = (
|
||||
|
||||
@ -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::
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
"""
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
"""
|
||||
|
||||
@ -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!
|
||||
|
||||
"""
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
CLEAR_EOL = "\033[0K"
|
||||
MOVE_CURSOR_UP = lambda n: "\033[{}A".format(n)
|
||||
MOVE_CURSOR_UP = '\033[{}A'.format
|
||||
|
||||
Reference in New Issue
Block a user