From 7b34b43b08d61e6c9635910ad79fd1493df14f07 Mon Sep 17 00:00:00 2001 From: Romain Dorgueil Date: Sun, 25 Dec 2016 14:25:10 +0100 Subject: [PATCH] testing bags --- Projectfile | 1 + bonobo/core/__init__.py | 3 +- bonobo/core/bags.py | 65 +++++++++++++---- bonobo/core/contexts.py | 31 +++++--- setup.py | 5 +- tests/core/test_bags.py | 75 ++++++++++++++++++++ tests/core/test_contexts.py | 0 tests/core/{test_graph.py => test_graphs.py} | 0 tests/core/{test_io.py => test_inputs.py} | 0 9 files changed, 153 insertions(+), 27 deletions(-) create mode 100644 tests/core/test_bags.py create mode 100644 tests/core/test_contexts.py rename tests/core/{test_graph.py => test_graphs.py} (100%) rename tests/core/{test_io.py => test_inputs.py} (100%) diff --git a/Projectfile b/Projectfile index 62aa976..9a5fdee 100644 --- a/Projectfile +++ b/Projectfile @@ -20,6 +20,7 @@ enable_features = { } install_requires = [ + 'blessings >=1.6,<1.7', 'psutil >=5.0,<5.1', ] diff --git a/bonobo/core/__init__.py b/bonobo/core/__init__.py index d08428e..769e7cd 100644 --- a/bonobo/core/__init__.py +++ b/bonobo/core/__init__.py @@ -1,4 +1,4 @@ -from .bags import Bag, Inherit +from .bags import Bag from .graphs import Graph from .services import inject, service from .strategies.executor import ThreadPoolExecutorStrategy, ProcessPoolExecutorStrategy @@ -7,7 +7,6 @@ from .strategies.naive import NaiveStrategy __all__ = [ 'Bag', 'Graph', - 'Inherit', 'NaiveStrategy', 'ProcessPoolExecutorStrategy', 'ThreadPoolExecutorStrategy', diff --git a/bonobo/core/bags.py b/bonobo/core/bags.py index 597a023..8f86ab1 100644 --- a/bonobo/core/bags.py +++ b/bonobo/core/bags.py @@ -1,19 +1,58 @@ +from operator import attrgetter + +import itertools + +from bonobo.util.tokens import Token + +_get_args = attrgetter('args') + +InheritInputFlag = Token('InheritInputFlag') + + class Bag: - def __init__(self, *args, **kwargs): - self.args = args - self.kwargs = kwargs + def __init__(self, *args, _flags=None, _parent=None, **kwargs): + self._flags = _flags or () + self._parent = _parent + self._args = args + self._kwargs = kwargs + + @property + def args(self): + if self._parent is None: + return self._args + return ( + *self._parent.args, + *self._args, ) + + @property + def kwargs(self): + if self._parent is None: + return self._kwargs + return { + ** self._parent.kwargs, + ** self._kwargs, + } + + @property + def flags(self): + return self._flags def apply(self, f, *args, **kwargs): return f(*args, *self.args, **kwargs, **self.kwargs) + def extend(self, *args, **kwargs): + return type(self)(*args, _parent=self, **kwargs) + + def set_parent(self, parent): + self._parent = parent + + @classmethod + def inherit(cls, *args, **kwargs): + return cls(*args, _flags=(InheritInputFlag, ), **kwargs) + def __repr__(self): - return '<{} *{} **{}>'.format(type(self).__name__, self.args, self.kwargs) - - -class Inherit(Bag): - def override(self, input): - self.args = input.args + self.args - kwargs = dict(input.kwargs) - kwargs.update(self.kwargs) - self.kwargs = kwargs - return self + return '<{} ({})>'.format( + type(self).__name__, ', '.join( + itertools.chain( + map(repr, self.args), + ('{}={}'.format(k, repr(v)) for k, v in self.kwargs.items()), ))) diff --git a/bonobo/core/contexts.py b/bonobo/core/contexts.py index 076eaad..005a6b2 100644 --- a/bonobo/core/contexts.py +++ b/bonobo/core/contexts.py @@ -3,7 +3,7 @@ from functools import partial from queue import Empty from time import sleep -from bonobo.core.bags import Bag +from bonobo.core.bags import Bag, InheritInputFlag from bonobo.core.errors import InactiveReadableError from bonobo.core.inputs import Input from bonobo.core.stats import WithStatistics @@ -139,7 +139,6 @@ class ComponentExecutionContext(WithStatistics): def _call(self, bag_or_arg): # todo add timer - bag = bag_or_arg if hasattr(bag_or_arg, 'apply') else Bag(bag_or_arg) if getattr(self.component, '_with_context', False): return bag.apply(self.component, self) return bag.apply(self.component) @@ -149,17 +148,27 @@ class ComponentExecutionContext(WithStatistics): """Runs a transformation callable with given args/kwargs and flush the result into the right output channel.""" - input_row = self.get() + input_bag = self.get() - def _resolve(result): - nonlocal input_row - if result is NotModified: - return input_row - if hasattr(result, 'override'): - return result.override(input_row) - return result + def _resolve(output): + nonlocal input_bag - results = self._call(input_row) + # NotModified means to send the input unmodified to output. + if output is NotModified: + return input_bag + + # If it does not look like a bag, let's create one for easier manipulation + if hasattr(output, 'apply'): + # Already a bag? Check if we need to set parent. + if InheritInputFlag in output.flags: + output.set_parent(input_bag) + else: + # Not a bag? Let's encapsulate it. + output = Bag(result) + + return output + + results = self._call(input_bag) # self._exec_time += timer.duration # Put data onto output channels diff --git a/setup.py b/setup.py index 82195bc..1f24fd0 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,10 @@ setup( name='bonobo', description='Bonobo', license='Apache License, Version 2.0', - install_requires=['psutil >=5.0,<5.1', ], + install_requires=[ + 'psutil >=5.0,<5.1', + 'blessings >=1.6,<1.7', + ], version=version, long_description=read('README.rst'), classifiers=read('classifiers.txt', tolines), diff --git a/tests/core/test_bags.py b/tests/core/test_bags.py new file mode 100644 index 0000000..87bc2b0 --- /dev/null +++ b/tests/core/test_bags.py @@ -0,0 +1,75 @@ +from mock import Mock + +from bonobo import Bag +from bonobo.core.bags import InheritInputFlag + +args = ( + 'foo', + 'bar', ) +kwargs = dict(acme='corp') + + +def test_basic(): + my_callable1 = Mock() + my_callable2 = Mock() + bag = Bag(*args, **kwargs) + + assert not my_callable1.called + result1 = bag.apply(my_callable1) + assert my_callable1.called and result1 is my_callable1.return_value + + assert not my_callable2.called + result2 = bag.apply(my_callable2) + assert my_callable2.called and result2 is my_callable2.return_value + + assert result1 is not result2 + + my_callable1.assert_called_once_with(*args, **kwargs) + my_callable2.assert_called_once_with(*args, **kwargs) + + +def test_inherit(): + bag = Bag('a', a=1) + bag2 = Bag.inherit('b', b=2, _parent=bag) + bag3 = bag.extend('c', c=3) + bag4 = Bag('d', d=4) + + assert bag.args == ('a', ) + assert bag.kwargs == {'a': 1} + assert bag.flags is () + + assert bag2.args == ( + 'a', + 'b', ) + assert bag2.kwargs == {'a': 1, 'b': 2} + assert InheritInputFlag in bag2.flags + + assert bag3.args == ( + 'a', + 'c', ) + assert bag3.kwargs == {'a': 1, 'c': 3} + assert bag3.flags is () + + assert bag4.args == ('d', ) + assert bag4.kwargs == {'d': 4} + assert bag4.flags is () + + bag4.set_parent(bag) + assert bag4.args == ( + 'a', + 'd', ) + assert bag4.kwargs == {'a': 1, 'd': 4} + assert bag4.flags is () + + bag4.set_parent(bag3) + assert bag4.args == ( + 'a', + 'c', + 'd', ) + assert bag4.kwargs == {'a': 1, 'c': 3, 'd': 4} + assert bag4.flags is () + + +def test_repr(): + bag = Bag('a', a=1) + assert repr(bag) == "" diff --git a/tests/core/test_contexts.py b/tests/core/test_contexts.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/core/test_graph.py b/tests/core/test_graphs.py similarity index 100% rename from tests/core/test_graph.py rename to tests/core/test_graphs.py diff --git a/tests/core/test_io.py b/tests/core/test_inputs.py similarity index 100% rename from tests/core/test_io.py rename to tests/core/test_inputs.py