Tuning ValueHolder as I could not find better option to generate the double-underscore methods.

This commit is contained in:
Romain Dorgueil
2017-05-21 19:22:45 +02:00
parent 2b3ef05fac
commit 4d9b579a60
12 changed files with 177 additions and 117 deletions

View File

@ -74,9 +74,11 @@ class Configurable(metaclass=ConfigurableMeta):
# transform positional arguments in keyword arguments if possible. # transform positional arguments in keyword arguments if possible.
position = 0 position = 0
for positional_option in self.__positional_options__: for positional_option in self.__positional_options__:
if len(args) <= position:
break
kwargs[positional_option] = args[position]
position += 1
if positional_option in missing: if positional_option in missing:
kwargs[positional_option] = args[position]
position += 1
missing.remove(positional_option) missing.remove(positional_option)
# complain if there are still missing options. # complain if there are still missing options.

View File

@ -1,11 +1,9 @@
import functools
import types import types
from collections import Iterable from collections import Iterable
from contextlib import contextmanager
from bonobo.util.compat import deprecated_alias, deprecated
from bonobo.config.options import Option from bonobo.config.options import Option
from bonobo.util.compat import deprecated_alias
from bonobo.util.iterators import ensure_tuple from bonobo.util.iterators import ensure_tuple
_CONTEXT_PROCESSORS_ATTR = '__processors__' _CONTEXT_PROCESSORS_ATTR = '__processors__'
@ -52,6 +50,14 @@ class ContextCurrifier:
self._stack = [] self._stack = []
self._stack_values = [] self._stack_values = []
def __iter__(self):
yield from self.wrapped
def __call__(self, *args, **kwargs):
if not callable(self.wrapped) and isinstance(self.wrapped, Iterable):
return self.__iter__()
return self.wrapped(*self.context, *args, **kwargs)
def setup(self, *context): def setup(self, *context):
if len(self._stack): if len(self._stack):
raise RuntimeError('Cannot setup context currification twice.') raise RuntimeError('Cannot setup context currification twice.')
@ -63,14 +69,6 @@ class ContextCurrifier:
self.context += ensure_tuple(_append_to_context) self.context += ensure_tuple(_append_to_context)
self._stack.append(_processed) self._stack.append(_processed)
def __iter__(self):
yield from self.wrapped
def __call__(self, *args, **kwargs):
if not callable(self.wrapped) and isinstance(self.wrapped, Iterable):
return self.__iter__()
return self.wrapped(*self.context, *args, **kwargs)
def teardown(self): def teardown(self):
while len(self._stack): while len(self._stack):
processor = self._stack.pop() processor = self._stack.pop()
@ -84,6 +82,23 @@ class ContextCurrifier:
# No error ? We should have had StopIteration ... # No error ? We should have had StopIteration ...
raise RuntimeError('Context processors should not yield more than once.') raise RuntimeError('Context processors should not yield more than once.')
@contextmanager
def as_contextmanager(self, *context):
"""
Convenience method to use it as a contextmanager, mostly for test purposes.
Example:
>>> with ContextCurrifier(node).as_contextmanager(context) as stack:
... stack()
:param context:
:return:
"""
self.setup(*context)
yield self
self.teardown()
def resolve_processors(mixed): def resolve_processors(mixed):
try: try:

View File

@ -46,7 +46,7 @@ class OpenDataSoftAPI(Configurable):
for row in records: for row in records:
yield {**row.get('fields', {}), 'geometry': row.get('geometry', {})} yield {**row.get('fields', {}), 'geometry': row.get('geometry', {})}
start.value += self.rows start += self.rows
__all__ = [ __all__ = [

View File

@ -3,10 +3,12 @@ from pprint import pprint as _pprint
from colorama import Fore, Style from colorama import Fore, Style
from bonobo.config import Configurable, Option
from bonobo.config.processors import ContextProcessor from bonobo.config.processors import ContextProcessor
from bonobo.structs.bags import Bag from bonobo.structs.bags import Bag
from bonobo.util.objects import ValueHolder from bonobo.util.objects import ValueHolder
from bonobo.util.term import CLEAR_EOL from bonobo.util.term import CLEAR_EOL
from bonobo.constants import NOT_MODIFIED
__all__ = [ __all__ = [
'identity', 'identity',
@ -23,19 +25,26 @@ def identity(x):
return x return x
def Limit(n=10): class Limit(Configurable):
from bonobo.constants import NOT_MODIFIED """
i = 0 Creates a Limit() node, that will only let go through the first n rows (defined by the `limit` option), unmodified.
def _limit(*args, **kwargs): .. attribute:: limit
nonlocal i, n
i += 1 Number of rows to let go through.
if i <= n:
"""
limit = Option(positional=True, default=10)
@ContextProcessor
def counter(self, context):
yield ValueHolder(0)
def call(self, counter, *args, **kwargs):
counter += 1
if counter <= self.limit:
yield NOT_MODIFIED yield NOT_MODIFIED
_limit.__name__ = 'Limit({})'.format(n)
return _limit
def Tee(f): def Tee(f):
from bonobo.constants import NOT_MODIFIED from bonobo.constants import NOT_MODIFIED
@ -57,7 +66,7 @@ def count(counter, *args, **kwargs):
def _count_counter(self, context): def _count_counter(self, context):
counter = ValueHolder(0) counter = ValueHolder(0)
yield counter yield counter
context.send(Bag(counter.value)) context.send(Bag(counter._value))
pprint = Tee(_pprint) pprint = Tee(_pprint)

View File

@ -46,8 +46,11 @@ class CsvReader(CsvHandler, FileReader):
def read(self, fs, file, headers): def read(self, fs, file, headers):
reader = csv.reader(file, delimiter=self.delimiter, quotechar=self.quotechar) reader = csv.reader(file, delimiter=self.delimiter, quotechar=self.quotechar)
headers.value = headers.value or next(reader)
field_count = len(headers.value) if not headers.get():
headers.set(next(reader))
field_count = len(headers)
if self.skip and self.skip > 0: if self.skip and self.skip > 0:
for _ in range(0, self.skip): for _ in range(0, self.skip):
@ -68,9 +71,9 @@ class CsvWriter(CsvHandler, FileWriter):
yield writer, headers yield writer, headers
def write(self, fs, file, lineno, writer, headers, row): def write(self, fs, file, lineno, writer, headers, row):
if not lineno.value: if not lineno:
headers.value = headers.value or row.keys() headers.set(headers.value or row.keys())
writer.writerow(headers.value) writer.writerow(headers.get())
writer.writerow(row[header] for header in headers.value) writer.writerow(row[header] for header in headers.get())
lineno.value += 1 lineno += 1
return NOT_MODIFIED return NOT_MODIFIED

View File

@ -86,7 +86,7 @@ class FileWriter(Writer):
@ContextProcessor @ContextProcessor
def lineno(self, context, fs, file): def lineno(self, context, fs, file):
lineno = ValueHolder(0, type=int) lineno = ValueHolder(0)
yield lineno yield lineno
def write(self, fs, file, lineno, row): def write(self, fs, file, lineno, row):
@ -94,7 +94,7 @@ class FileWriter(Writer):
Write a row on the next line of opened file in context. Write a row on the next line of opened file in context.
""" """
self._write_line(file, (self.eol if lineno.value else '') + row) self._write_line(file, (self.eol if lineno.value else '') + row)
lineno.value += 1 lineno += 1
return NOT_MODIFIED return NOT_MODIFIED
def _write_line(self, file, line): def _write_line(self, file, line):

View File

@ -65,8 +65,10 @@ class Bag:
if len(args) == 0 and len(kwargs) == 0: if len(args) == 0 and len(kwargs) == 0:
try: try:
iter(func_or_iter) iter(func_or_iter)
def generator(): def generator():
yield from func_or_iter yield from func_or_iter
return generator() return generator()
except TypeError as exc: except TypeError as exc:
raise TypeError('Could not apply bag to {}.'.format(func_or_iter)) from exc raise TypeError('Could not apply bag to {}.'.format(func_or_iter)) from exc

View File

@ -1,3 +1,7 @@
import functools
from functools import partial
def get_name(mixed): def get_name(mixed):
try: try:
return mixed.__name__ return mixed.__name__
@ -27,181 +31,194 @@ class ValueHolder:
""" """
def __init__(self, value, *, type=None): def __init__(self, value):
self.value = value self._value = value
self.type = type
def __repr__(self): @property
return repr(self.value) def value(self):
# XXX deprecated
return self._value
def __lt__(self, other): def get(self):
return self.value < other return self._value
def __le__(self, other): def set(self, new_value):
return self.value <= other self._value = new_value
def __bool__(self):
return bool(self._value)
def __eq__(self, other): def __eq__(self, other):
return self.value == other return self._value == other
def __ne__(self, other): def __ne__(self, other):
return self.value != other return self._value != other
def __repr__(self):
return repr(self._value)
def __lt__(self, other):
return self._value < other
def __le__(self, other):
return self._value <= other
def __gt__(self, other): def __gt__(self, other):
return self.value > other return self._value > other
def __ge__(self, other): def __ge__(self, other):
return self.value >= other return self._value >= other
def __add__(self, other): def __add__(self, other):
return self.value + other return self._value + other
def __radd__(self, other): def __radd__(self, other):
return other + self.value return other + self._value
def __iadd__(self, other): def __iadd__(self, other):
self.value += other self._value += other
return self return self
def __sub__(self, other): def __sub__(self, other):
return self.value - other return self._value - other
def __rsub__(self, other): def __rsub__(self, other):
return other - self.value return other - self._value
def __isub__(self, other): def __isub__(self, other):
self.value -= other self._value -= other
return self return self
def __mul__(self, other): def __mul__(self, other):
return self.value * other return self._value * other
def __rmul__(self, other): def __rmul__(self, other):
return other * self.value return other * self._value
def __imul__(self, other): def __imul__(self, other):
self.value *= other self._value *= other
return self return self
def __matmul__(self, other): def __matmul__(self, other):
return self.value @ other return self._value @ other
def __rmatmul__(self, other): def __rmatmul__(self, other):
return other @ self.value return other @ self._value
def __imatmul__(self, other): def __imatmul__(self, other):
self.value @= other self._value @= other
return self return self
def __truediv__(self, other): def __truediv__(self, other):
return self.value / other return self._value / other
def __rtruediv__(self, other): def __rtruediv__(self, other):
return other / self.value return other / self._value
def __itruediv__(self, other): def __itruediv__(self, other):
self.value /= other self._value /= other
return self return self
def __floordiv__(self, other): def __floordiv__(self, other):
return self.value // other return self._value // other
def __rfloordiv__(self, other): def __rfloordiv__(self, other):
return other // self.value return other // self._value
def __ifloordiv__(self, other): def __ifloordiv__(self, other):
self.value //= other self._value //= other
return self return self
def __mod__(self, other): def __mod__(self, other):
return self.value % other return self._value % other
def __rmod__(self, other): def __rmod__(self, other):
return other % self.value return other % self._value
def __imod__(self, other): def __imod__(self, other):
self.value %= other self._value %= other
return self return self
def __divmod__(self, other): def __divmod__(self, other):
return divmod(self.value, other) return divmod(self._value, other)
def __rdivmod__(self, other): def __rdivmod__(self, other):
return divmod(other, self.value) return divmod(other, self._value)
def __pow__(self, other): def __pow__(self, other):
return self.value**other return self._value**other
def __rpow__(self, other): def __rpow__(self, other):
return other**self.value return other**self._value
def __ipow__(self, other): def __ipow__(self, other):
self.value **= other self._value **= other
return self return self
def __lshift__(self, other): def __lshift__(self, other):
return self.value << other return self._value << other
def __rlshift__(self, other): def __rlshift__(self, other):
return other << self.value return other << self._value
def __ilshift__(self, other): def __ilshift__(self, other):
self.value <<= other self._value <<= other
return self return self
def __rshift__(self, other): def __rshift__(self, other):
return self.value >> other return self._value >> other
def __rrshift__(self, other): def __rrshift__(self, other):
return other >> self.value return other >> self._value
def __irshift__(self, other): def __irshift__(self, other):
self.value >>= other self._value >>= other
return self return self
def __and__(self, other): def __and__(self, other):
return self.value & other return self._value & other
def __rand__(self, other): def __rand__(self, other):
return other & self.value return other & self._value
def __iand__(self, other): def __iand__(self, other):
self.value &= other self._value &= other
return self return self
def __xor__(self, other): def __xor__(self, other):
return self.value ^ other return self._value ^ other
def __rxor__(self, other): def __rxor__(self, other):
return other ^ self.value return other ^ self._value
def __ixor__(self, other): def __ixor__(self, other):
self.value ^= other self._value ^= other
return self return self
def __or__(self, other): def __or__(self, other):
return self.value | other return self._value | other
def __ror__(self, other): def __ror__(self, other):
return other | self.value return other | self._value
def __ior__(self, other): def __ior__(self, other):
self.value |= other self._value |= other
return self return self
def __neg__(self): def __neg__(self):
return -self.value return -self._value
def __pos__(self): def __pos__(self):
return +self.value return +self._value
def __abs__(self): def __abs__(self):
return abs(self.value) return abs(self._value)
def __invert__(self): def __invert__(self):
return ~self.value return ~self._value
def __len__(self): def __len__(self):
return len(self.value) return len(self._value)
def get_attribute_or_create(obj, attr, default): def get_attribute_or_create(obj, attr, default):

View File

@ -5,7 +5,7 @@ from bonobo import Bag
from bonobo.constants import INHERIT_INPUT from bonobo.constants import INHERIT_INPUT
from bonobo.structs import Token from bonobo.structs import Token
args = ('foo', 'bar',) args = ('foo', 'bar', )
kwargs = dict(acme='corp') kwargs = dict(acme='corp')
@ -34,29 +34,29 @@ def test_inherit():
bag3 = bag.extend('c', c=3) bag3 = bag.extend('c', c=3)
bag4 = Bag('d', d=4) bag4 = Bag('d', d=4)
assert bag.args == ('a',) assert bag.args == ('a', )
assert bag.kwargs == {'a': 1} assert bag.kwargs == {'a': 1}
assert bag.flags is () assert bag.flags is ()
assert bag2.args == ('a', 'b',) assert bag2.args == ('a', 'b', )
assert bag2.kwargs == {'a': 1, 'b': 2} assert bag2.kwargs == {'a': 1, 'b': 2}
assert INHERIT_INPUT in bag2.flags assert INHERIT_INPUT in bag2.flags
assert bag3.args == ('a', 'c',) assert bag3.args == ('a', 'c', )
assert bag3.kwargs == {'a': 1, 'c': 3} assert bag3.kwargs == {'a': 1, 'c': 3}
assert bag3.flags is () assert bag3.flags is ()
assert bag4.args == ('d',) assert bag4.args == ('d', )
assert bag4.kwargs == {'d': 4} assert bag4.kwargs == {'d': 4}
assert bag4.flags is () assert bag4.flags is ()
bag4.set_parent(bag) bag4.set_parent(bag)
assert bag4.args == ('a', 'd',) assert bag4.args == ('a', 'd', )
assert bag4.kwargs == {'a': 1, 'd': 4} assert bag4.kwargs == {'a': 1, 'd': 4}
assert bag4.flags is () assert bag4.flags is ()
bag4.set_parent(bag3) bag4.set_parent(bag3)
assert bag4.args == ('a', 'c', 'd',) assert bag4.args == ('a', 'c', 'd', )
assert bag4.kwargs == {'a': 1, 'c': 3, 'd': 4} assert bag4.kwargs == {'a': 1, 'c': 3, 'd': 4}
assert bag4.flags is () assert bag4.flags is ()

View File

@ -1,7 +1,8 @@
from unittest.mock import MagicMock from unittest.mock import MagicMock
import bonobo
import pytest import pytest
import bonobo
from bonobo.config.processors import ContextCurrifier from bonobo.config.processors import ContextCurrifier
from bonobo.constants import NOT_MODIFIED from bonobo.constants import NOT_MODIFIED
@ -10,14 +11,12 @@ def test_count():
with pytest.raises(TypeError): with pytest.raises(TypeError):
bonobo.count() bonobo.count()
context = MagicMock() context = MagicMock()
currified = ContextCurrifier(bonobo.count) with ContextCurrifier(bonobo.count).as_contextmanager(context) as stack:
currified.setup(context) for i in range(42):
stack()
for i in range(42):
currified()
currified.teardown()
assert len(context.method_calls) == 1 assert len(context.method_calls) == 1
bag = context.send.call_args[0][0] bag = context.send.call_args[0][0]
@ -32,18 +31,31 @@ def test_identity():
def test_limit(): def test_limit():
limit = bonobo.Limit(2) context, results = MagicMock(), []
results = []
for i in range(42): with ContextCurrifier(bonobo.Limit(2)).as_contextmanager(context) as stack:
results += list(limit()) for i in range(42):
results += list(stack())
assert results == [NOT_MODIFIED] * 2 assert results == [NOT_MODIFIED] * 2
def test_limit_not_there(): def test_limit_not_there():
limit = bonobo.Limit(42) context, results = MagicMock(), []
results = []
for i in range(10): with ContextCurrifier(bonobo.Limit(42)).as_contextmanager(context) as stack:
results += list(limit()) for i in range(10):
results += list(stack())
assert results == [NOT_MODIFIED] * 10
def test_limit_default():
context, results = MagicMock(), []
with ContextCurrifier(bonobo.Limit()).as_contextmanager(context) as stack:
for i in range(20):
results += list(stack())
assert results == [NOT_MODIFIED] * 10 assert results == [NOT_MODIFIED] * 10

View File

@ -21,4 +21,3 @@ def test_deprecated_alias():
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
foo() foo()

View File

@ -35,6 +35,7 @@ def test_wrapper_name():
def test_valueholder(): def test_valueholder():
x = ValueHolder(42) x = ValueHolder(42)
assert x == 42 assert x == 42
x += 1 x += 1
assert x == 43 assert x == 43