Merge branch '47_context_processors' into 0.3
This commit is contained in:
@ -3,7 +3,7 @@ from pprint import pprint as _pprint
|
|||||||
|
|
||||||
from colorama import Fore, Style
|
from colorama import Fore, Style
|
||||||
|
|
||||||
from bonobo.config.processors import contextual
|
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
|
||||||
@ -49,12 +49,11 @@ def Tee(f):
|
|||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
@contextual
|
|
||||||
def count(counter, *args, **kwargs):
|
def count(counter, *args, **kwargs):
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
|
|
||||||
@count.add_context_processor
|
@ContextProcessor.decorate(count)
|
||||||
def _count_counter(self, context):
|
def _count_counter(self, context):
|
||||||
counter = ValueHolder(0)
|
counter = ValueHolder(0)
|
||||||
yield counter
|
yield counter
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from bonobo.config.processors import ContextProcessor
|
||||||
from bonobo.config.options import Option
|
from bonobo.config.options import Option
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -15,16 +16,24 @@ class ConfigurableMeta(type):
|
|||||||
super().__init__(what, bases, dict)
|
super().__init__(what, bases, dict)
|
||||||
cls.__options__ = {}
|
cls.__options__ = {}
|
||||||
cls.__positional_options__ = []
|
cls.__positional_options__ = []
|
||||||
|
cls.__processors__ = []
|
||||||
|
|
||||||
for typ in cls.__mro__:
|
for typ in cls.__mro__:
|
||||||
for name, value in typ.__dict__.items():
|
for name, value in typ.__dict__.items():
|
||||||
if isinstance(value, Option):
|
if isinstance(value, Option):
|
||||||
if not value.name:
|
if isinstance(value, ContextProcessor):
|
||||||
value.name = name
|
cls.__processors__.append(value)
|
||||||
if not name in cls.__options__:
|
else:
|
||||||
cls.__options__[name] = value
|
if not value.name:
|
||||||
if value.positional:
|
value.name = name
|
||||||
cls.__positional_options__.append(name)
|
if not name in cls.__options__:
|
||||||
|
cls.__options__[name] = value
|
||||||
|
if value.positional:
|
||||||
|
cls.__positional_options__.append(name)
|
||||||
|
|
||||||
|
# This can be done before, more efficiently. Not so bad neither as this is only done at type() creation time
|
||||||
|
# (aka class Xxx(...) time) and there should not be hundreds of processors. Still not very elegant.
|
||||||
|
cls.__processors__ = sorted(cls.__processors__, key=lambda v: v._creation_counter)
|
||||||
|
|
||||||
|
|
||||||
class Configurable(metaclass=ConfigurableMeta):
|
class Configurable(metaclass=ConfigurableMeta):
|
||||||
|
|||||||
@ -2,24 +2,23 @@ import functools
|
|||||||
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
from bonobo.util.compat import deprecated_alias
|
from bonobo.util.compat import deprecated_alias, deprecated
|
||||||
|
|
||||||
|
from bonobo.config.options import Option
|
||||||
|
from bonobo.util.iterators import ensure_tuple
|
||||||
|
|
||||||
_CONTEXT_PROCESSORS_ATTR = '__processors__'
|
_CONTEXT_PROCESSORS_ATTR = '__processors__'
|
||||||
|
|
||||||
|
|
||||||
class ContextProcessor:
|
class ContextProcessor(Option):
|
||||||
_creation_counter = 0
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __name__(self):
|
def __name__(self):
|
||||||
return self.func.__name__
|
return self.func.__name__
|
||||||
|
|
||||||
def __init__(self, func):
|
def __init__(self, func):
|
||||||
self.func = func
|
self.func = func
|
||||||
|
super(ContextProcessor, self).__init__(required=False, default=self.__name__)
|
||||||
# This hack is necessary for python3.5
|
self.name = self.__name__
|
||||||
self._creation_counter = ContextProcessor._creation_counter
|
|
||||||
ContextProcessor._creation_counter += 1
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return repr(self.func).replace('<function', '<{}'.format(type(self).__name__))
|
return repr(self.func).replace('<function', '<{}'.format(type(self).__name__))
|
||||||
@ -27,11 +26,63 @@ class ContextProcessor:
|
|||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
return self.func(*args, **kwargs)
|
return self.func(*args, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def decorate(cls, cls_or_func):
|
||||||
|
try:
|
||||||
|
cls_or_func.__processors__
|
||||||
|
except AttributeError:
|
||||||
|
cls_or_func.__processors__ = []
|
||||||
|
|
||||||
|
def decorator(processor, cls_or_func=cls_or_func):
|
||||||
|
cls_or_func.__processors__.append(cls(processor))
|
||||||
|
return cls_or_func
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class ContextCurrifier:
|
||||||
|
"""
|
||||||
|
This is a helper to resolve processors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, wrapped, *initial_context):
|
||||||
|
self.wrapped = wrapped
|
||||||
|
self.context = tuple(initial_context)
|
||||||
|
self._stack = []
|
||||||
|
|
||||||
|
def setup(self, *context):
|
||||||
|
if len(self._stack):
|
||||||
|
raise RuntimeError('Cannot setup context currification twice.')
|
||||||
|
for processor in resolve_processors(self.wrapped):
|
||||||
|
_processed = processor(self.wrapped, *context, *self.context)
|
||||||
|
_append_to_context = next(_processed)
|
||||||
|
if _append_to_context is not None:
|
||||||
|
self.context += ensure_tuple(_append_to_context)
|
||||||
|
self._stack.append(_processed)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return self.wrapped(*self.context, *args, **kwargs)
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
while len(self._stack):
|
||||||
|
processor = self._stack.pop()
|
||||||
|
try:
|
||||||
|
# todo yield from ? how to ?
|
||||||
|
next(processor)
|
||||||
|
except StopIteration as exc:
|
||||||
|
# This is normal, and wanted.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# No error ? We should have had StopIteration ...
|
||||||
|
raise RuntimeError('Context processors should not yield more than once.')
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated
|
||||||
def add_context_processor(cls_or_func, context_processor):
|
def add_context_processor(cls_or_func, context_processor):
|
||||||
getattr(cls_or_func, _CONTEXT_PROCESSORS_ATTR).append(context_processor)
|
getattr(cls_or_func, _CONTEXT_PROCESSORS_ATTR).append(context_processor)
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated
|
||||||
def contextual(cls_or_func):
|
def contextual(cls_or_func):
|
||||||
"""
|
"""
|
||||||
Make sure an element has the context processors collection.
|
Make sure an element has the context processors collection.
|
||||||
@ -62,11 +113,15 @@ def contextual(cls_or_func):
|
|||||||
|
|
||||||
|
|
||||||
def resolve_processors(mixed):
|
def resolve_processors(mixed):
|
||||||
if isinstance(mixed, types.FunctionType):
|
try:
|
||||||
yield from getattr(mixed, _CONTEXT_PROCESSORS_ATTR, ())
|
yield from mixed.__processors__
|
||||||
|
except AttributeError:
|
||||||
|
# old code, deprecated usage
|
||||||
|
if isinstance(mixed, types.FunctionType):
|
||||||
|
yield from getattr(mixed, _CONTEXT_PROCESSORS_ATTR, ())
|
||||||
|
|
||||||
for cls in reversed((mixed if isinstance(mixed, type) else type(mixed)).__mro__):
|
for cls in reversed((mixed if isinstance(mixed, type) else type(mixed)).__mro__):
|
||||||
yield from cls.__dict__.get(_CONTEXT_PROCESSORS_ATTR, ())
|
yield from cls.__dict__.get(_CONTEXT_PROCESSORS_ATTR, ())
|
||||||
|
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import traceback
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from bonobo.config import Container
|
from bonobo.config import Container
|
||||||
from bonobo.config.processors import resolve_processors
|
from bonobo.config.processors import resolve_processors, ContextCurrifier
|
||||||
from bonobo.util.errors import print_error
|
from bonobo.util.errors import print_error
|
||||||
from bonobo.util.iterators import ensure_tuple
|
from bonobo.util.iterators import ensure_tuple
|
||||||
from bonobo.util.objects import Wrapper
|
from bonobo.util.objects import Wrapper
|
||||||
@ -36,31 +36,19 @@ class LoopingExecutionContext(Wrapper):
|
|||||||
else:
|
else:
|
||||||
self.services = None
|
self.services = None
|
||||||
|
|
||||||
self._started, self._stopped, self._context, self._stack = False, False, None, []
|
self._started, self._stopped, self._stack = False, False, None
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
assert self.state == (False,
|
assert self.state == (False,
|
||||||
False), ('{}.start() can only be called on a new node.').format(type(self).__name__)
|
False), ('{}.start() can only be called on a new node.').format(type(self).__name__)
|
||||||
assert self._context is None
|
|
||||||
self._started = True
|
self._started = True
|
||||||
|
self._stack = ContextCurrifier(self.wrapped, *self._get_initial_context())
|
||||||
|
|
||||||
if self.parent:
|
try:
|
||||||
self._context = self.parent.services.args_for(self.wrapped)
|
self._stack.setup(self)
|
||||||
elif self.services:
|
except Exception as exc: # pylint: disable=broad-except
|
||||||
self._context = self.services.args_for(self.wrapped)
|
self.handle_error(exc, traceback.format_exc())
|
||||||
else:
|
raise
|
||||||
self._context = ()
|
|
||||||
|
|
||||||
for processor in resolve_processors(self.wrapped):
|
|
||||||
try:
|
|
||||||
_processed = processor(self.wrapped, self, *self._context)
|
|
||||||
_append_to_context = next(_processed)
|
|
||||||
if _append_to_context is not None:
|
|
||||||
self._context += ensure_tuple(_append_to_context)
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
self.handle_error(exc, traceback.format_exc())
|
|
||||||
raise
|
|
||||||
self._stack.append(_processed)
|
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
"""Generic loop. A bit boring. """
|
"""Generic loop. A bit boring. """
|
||||||
@ -78,21 +66,18 @@ class LoopingExecutionContext(Wrapper):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._stopped = True
|
self._stopped = True
|
||||||
if self._context is not None:
|
try:
|
||||||
while len(self._stack):
|
self._stack.teardown()
|
||||||
processor = self._stack.pop()
|
except Exception as exc: # pylint: disable=broad-except
|
||||||
try:
|
self.handle_error(exc, traceback.format_exc())
|
||||||
# todo yield from ? how to ?
|
raise
|
||||||
next(processor)
|
|
||||||
except StopIteration as exc:
|
|
||||||
# This is normal, and wanted.
|
|
||||||
pass
|
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
|
||||||
self.handle_error(exc, traceback.format_exc())
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
# No error ? We should have had StopIteration ...
|
|
||||||
raise RuntimeError('Context processors should not yield more than once.')
|
|
||||||
|
|
||||||
def handle_error(self, exc, trace):
|
def handle_error(self, exc, trace):
|
||||||
return print_error(exc, trace, context=self.wrapped)
|
return print_error(exc, trace, context=self.wrapped)
|
||||||
|
|
||||||
|
def _get_initial_context(self):
|
||||||
|
if self.parent:
|
||||||
|
return self.parent.services.args_for(self.wrapped)
|
||||||
|
if self.services:
|
||||||
|
return self.services.args_for(self.wrapped)
|
||||||
|
return ()
|
||||||
|
|||||||
@ -93,7 +93,7 @@ class NodeExecutionContext(WithStatistics, LoopingExecutionContext):
|
|||||||
input_bag = self.get()
|
input_bag = self.get()
|
||||||
|
|
||||||
# todo add timer
|
# todo add timer
|
||||||
self.handle_results(input_bag, input_bag.apply(self.wrapped, *self._context))
|
self.handle_results(input_bag, input_bag.apply(self._stack))
|
||||||
|
|
||||||
def push(self, bag):
|
def push(self, bag):
|
||||||
# MAKE THIS PUBLIC API FOR CONTEXT PROCESSORS !!!
|
# MAKE THIS PUBLIC API FOR CONTEXT PROCESSORS !!!
|
||||||
|
|||||||
@ -13,7 +13,6 @@ def path_str(path):
|
|||||||
return path if path.startswith('/') else '/' + path
|
return path if path.startswith('/') else '/' + path
|
||||||
|
|
||||||
|
|
||||||
@contextual
|
|
||||||
class OpenDataSoftAPI(Configurable):
|
class OpenDataSoftAPI(Configurable):
|
||||||
dataset = Option(str, required=True)
|
dataset = Option(str, required=True)
|
||||||
endpoint = Option(str, default='{scheme}://{netloc}{path}')
|
endpoint = Option(str, default='{scheme}://{netloc}{path}')
|
||||||
|
|||||||
@ -27,7 +27,6 @@ class CsvHandler(FileHandler):
|
|||||||
headers = Option(tuple)
|
headers = Option(tuple)
|
||||||
|
|
||||||
|
|
||||||
@contextual
|
|
||||||
class CsvReader(CsvHandler, FileReader):
|
class CsvReader(CsvHandler, FileReader):
|
||||||
"""
|
"""
|
||||||
Reads a CSV and yield the values as dicts.
|
Reads a CSV and yield the values as dicts.
|
||||||
@ -60,7 +59,6 @@ class CsvReader(CsvHandler, FileReader):
|
|||||||
yield dict(zip(headers.value, row))
|
yield dict(zip(headers.value, row))
|
||||||
|
|
||||||
|
|
||||||
@contextual
|
|
||||||
class CsvWriter(CsvHandler, FileWriter):
|
class CsvWriter(CsvHandler, FileWriter):
|
||||||
@ContextProcessor
|
@ContextProcessor
|
||||||
def writer(self, context, fs, file, lineno):
|
def writer(self, context, fs, file, lineno):
|
||||||
|
|||||||
@ -9,7 +9,6 @@ __all__ = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@contextual
|
|
||||||
class FileHandler(Configurable):
|
class FileHandler(Configurable):
|
||||||
"""Abstract component factory for file-related components.
|
"""Abstract component factory for file-related components.
|
||||||
|
|
||||||
@ -75,7 +74,6 @@ class FileReader(Reader):
|
|||||||
yield line.rstrip(self.eol)
|
yield line.rstrip(self.eol)
|
||||||
|
|
||||||
|
|
||||||
@contextual
|
|
||||||
class FileWriter(Writer):
|
class FileWriter(Writer):
|
||||||
"""Component factory for file or file-like writers.
|
"""Component factory for file or file-like writers.
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,6 @@ class JsonReader(JsonHandler, FileReader):
|
|||||||
yield line
|
yield line
|
||||||
|
|
||||||
|
|
||||||
@contextual
|
|
||||||
class JsonWriter(JsonHandler, FileWriter):
|
class JsonWriter(JsonHandler, FileWriter):
|
||||||
@ContextProcessor
|
@ContextProcessor
|
||||||
def envelope(self, context, fs, file, lineno):
|
def envelope(self, context, fs, file, lineno):
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
from bonobo.basics import noop
|
|
||||||
|
|
||||||
|
|
||||||
def _create_lifecycle_functions(noun, verb):
|
|
||||||
getter = lambda c: getattr(c, verb, noop)
|
|
||||||
getter.__name__ = 'get_' + noun
|
|
||||||
|
|
||||||
def setter(f):
|
|
||||||
nonlocal noun, verb
|
|
||||||
assert callable(f), 'You must provide a callable to decorate with {}.'.format(noun)
|
|
||||||
|
|
||||||
def wrapper(c):
|
|
||||||
nonlocal verb, f
|
|
||||||
setattr(f, verb, c)
|
|
||||||
return f
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
setter.__name__ = 'set_' + noun
|
|
||||||
|
|
||||||
return getter, setter
|
|
||||||
|
|
||||||
|
|
||||||
get_initializer, set_initializer = _create_lifecycle_functions('initializer', 'initialize')
|
|
||||||
get_finalizer, set_finalizer = _create_lifecycle_functions('finalizer', 'finalize')
|
|
||||||
|
|
||||||
|
|
||||||
class Contextual:
|
|
||||||
_with_context = True
|
|
||||||
|
|
||||||
|
|
||||||
def with_context(cls_or_func):
|
|
||||||
cls_or_func._with_context = True
|
|
||||||
return cls_or_func
|
|
||||||
38
docs/contribute/release.rst
Normal file
38
docs/contribute/release.rst
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
Releases
|
||||||
|
========
|
||||||
|
|
||||||
|
WORK IN PROGRESS, THIS DOC IS UNFINISHED AND JUST RAW NOTES TO HELP ME RELEASING STUFF.
|
||||||
|
|
||||||
|
How to make a patch release?
|
||||||
|
::::::::::::::::::::::::::::
|
||||||
|
|
||||||
|
For now, reference at http://rdc.li/r
|
||||||
|
|
||||||
|
Additional checklist:
|
||||||
|
|
||||||
|
* make format
|
||||||
|
|
||||||
|
How to make a minor or major release?
|
||||||
|
:::::::::::::::::::::::::::::::::::::
|
||||||
|
|
||||||
|
Releases above patch level are more complex, because we did not find a way not to hardcode the version number in a bunch
|
||||||
|
of files, and because a few dependant services (source control, continuous integration, code coverage, documentation
|
||||||
|
builder ...) also depends on version numbers.
|
||||||
|
|
||||||
|
Checklist:
|
||||||
|
|
||||||
|
* Files
|
||||||
|
* Github
|
||||||
|
|
||||||
|
|
||||||
|
Recipes
|
||||||
|
:::::::
|
||||||
|
|
||||||
|
Get current minor::
|
||||||
|
|
||||||
|
git semver | python -c 'import sys; print(".".join(sys.stdin.read().strip().split(".")[0:2]))'
|
||||||
|
|
||||||
|
Open git with all files containing current minor::
|
||||||
|
|
||||||
|
ack `git semver | python -c 'import sys; print("\\\\.".join(sys.stdin.read().strip().split(".")[0:2]))'` | vim -
|
||||||
|
|
||||||
62
tests/test_basics.py
Normal file
62
tests/test_basics.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import bonobo
|
||||||
|
import pytest
|
||||||
|
from bonobo.config.processors import ContextCurrifier
|
||||||
|
from bonobo.constants import NOT_MODIFIED
|
||||||
|
|
||||||
|
|
||||||
|
def test_count():
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
bonobo.count()
|
||||||
|
|
||||||
|
context = MagicMock()
|
||||||
|
|
||||||
|
currified = ContextCurrifier(bonobo.count)
|
||||||
|
currified.setup(context)
|
||||||
|
|
||||||
|
for i in range(42):
|
||||||
|
currified()
|
||||||
|
currified.teardown()
|
||||||
|
|
||||||
|
assert len(context.method_calls) == 1
|
||||||
|
bag = context.send.call_args[0][0]
|
||||||
|
assert isinstance(bag, bonobo.Bag)
|
||||||
|
assert 0 == len(bag.kwargs)
|
||||||
|
assert 1 == len(bag.args)
|
||||||
|
assert bag.args[0] == 42
|
||||||
|
|
||||||
|
|
||||||
|
def test_identity():
|
||||||
|
assert bonobo.identity(42) == 42
|
||||||
|
|
||||||
|
|
||||||
|
def test_limit():
|
||||||
|
limit = bonobo.Limit(2)
|
||||||
|
results = []
|
||||||
|
for i in range(42):
|
||||||
|
results += list(limit())
|
||||||
|
assert results == [NOT_MODIFIED] * 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_limit_not_there():
|
||||||
|
limit = bonobo.Limit(42)
|
||||||
|
results = []
|
||||||
|
for i in range(10):
|
||||||
|
results += list(limit())
|
||||||
|
assert results == [NOT_MODIFIED] * 10
|
||||||
|
|
||||||
|
|
||||||
|
def test_tee():
|
||||||
|
inner = MagicMock(side_effect=bonobo.identity)
|
||||||
|
tee = bonobo.Tee(inner)
|
||||||
|
results = []
|
||||||
|
for i in range(10):
|
||||||
|
results.append(tee('foo'))
|
||||||
|
|
||||||
|
assert results == [NOT_MODIFIED] * 10
|
||||||
|
assert len(inner.mock_calls) == 10
|
||||||
|
|
||||||
|
|
||||||
|
def test_noop():
|
||||||
|
assert bonobo.noop(1, 2, 3, 4, foo='bar') == NOT_MODIFIED
|
||||||
@ -2,7 +2,7 @@ import pytest
|
|||||||
|
|
||||||
import bonobo
|
import bonobo
|
||||||
from bonobo.execution import GraphExecutionContext
|
from bonobo.execution import GraphExecutionContext
|
||||||
from mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.timeout(2)
|
@pytest.mark.timeout(2)
|
||||||
|
|||||||
@ -1,24 +1,26 @@
|
|||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
from bonobo.config.processors import ContextProcessor, contextual, resolve_processors
|
from bonobo.config import Configurable
|
||||||
|
from bonobo.config.processors import ContextProcessor, resolve_processors, ContextCurrifier
|
||||||
|
|
||||||
|
|
||||||
@contextual
|
class CP1(Configurable):
|
||||||
class CP1:
|
|
||||||
@ContextProcessor
|
@ContextProcessor
|
||||||
def c(self):
|
def c(self):
|
||||||
pass
|
yield
|
||||||
|
|
||||||
@ContextProcessor
|
@ContextProcessor
|
||||||
def a(self):
|
def a(self):
|
||||||
pass
|
yield 'this is A'
|
||||||
|
|
||||||
@ContextProcessor
|
@ContextProcessor
|
||||||
def b(self):
|
def b(self, a):
|
||||||
pass
|
yield a.upper()[:-1] + 'b'
|
||||||
|
|
||||||
|
def __call__(self, a, b):
|
||||||
|
return a, b
|
||||||
|
|
||||||
|
|
||||||
@contextual
|
|
||||||
class CP2(CP1):
|
class CP2(CP1):
|
||||||
@ContextProcessor
|
@ContextProcessor
|
||||||
def f(self):
|
def f(self):
|
||||||
@ -33,7 +35,6 @@ class CP2(CP1):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@contextual
|
|
||||||
class CP3(CP2):
|
class CP3(CP2):
|
||||||
@ContextProcessor
|
@ContextProcessor
|
||||||
def c(self):
|
def c(self):
|
||||||
@ -52,3 +53,11 @@ def test_inheritance_and_ordering():
|
|||||||
assert get_all_processors_names(CP1) == ['c', 'a', 'b']
|
assert get_all_processors_names(CP1) == ['c', 'a', 'b']
|
||||||
assert get_all_processors_names(CP2) == ['c', 'a', 'b', 'f', 'e', 'd']
|
assert get_all_processors_names(CP2) == ['c', 'a', 'b', 'f', 'e', 'd']
|
||||||
assert get_all_processors_names(CP3) == ['c', 'a', 'b', 'f', 'e', 'd', 'c', 'b']
|
assert get_all_processors_names(CP3) == ['c', 'a', 'b', 'f', 'e', 'd', 'c', 'b']
|
||||||
|
|
||||||
|
|
||||||
|
def test_setup_teardown():
|
||||||
|
o = CP1()
|
||||||
|
stack = ContextCurrifier(o)
|
||||||
|
stack.setup()
|
||||||
|
assert o(*stack.context) == ('this is A', 'THIS IS b')
|
||||||
|
stack.teardown()
|
||||||
@ -1,4 +1,4 @@
|
|||||||
from bonobo.config.processors import contextual
|
from bonobo.config.processors import ContextProcessor
|
||||||
from bonobo.constants import BEGIN, END
|
from bonobo.constants import BEGIN, END
|
||||||
from bonobo.execution.graph import GraphExecutionContext
|
from bonobo.execution.graph import GraphExecutionContext
|
||||||
from bonobo.strategies import NaiveStrategy
|
from bonobo.strategies import NaiveStrategy
|
||||||
@ -13,12 +13,11 @@ def square(i: int) -> int:
|
|||||||
return i**2
|
return i**2
|
||||||
|
|
||||||
|
|
||||||
@contextual
|
|
||||||
def push_result(results, i: int):
|
def push_result(results, i: int):
|
||||||
results.append(i)
|
results.append(i)
|
||||||
|
|
||||||
|
|
||||||
@push_result.__processors__.append
|
@ContextProcessor.decorate(push_result)
|
||||||
def results(f, context):
|
def results(f, context):
|
||||||
results = []
|
results = []
|
||||||
yield results
|
yield results
|
||||||
|
|||||||
Reference in New Issue
Block a user