[config] Implements "Exclusive" context processor allowing to ask for an exclusive usage of a service while in a transformation.

This commit is contained in:
Romain Dorgueil
2017-05-25 11:14:49 +02:00
parent 8abd40cd73
commit 71c47a762b
9 changed files with 139 additions and 61 deletions

View File

@ -0,0 +1,88 @@
import pytest
from bonobo.config.configurables import Configurable
from bonobo.config.options import Option
class MyConfigurable(Configurable):
required_str = Option(str, required=True)
default_str = Option(str, default='foo')
integer = Option(int)
class MyHarderConfigurable(MyConfigurable):
also_required = Option(bool, required=True)
class MyBetterConfigurable(MyConfigurable):
required_str = Option(str, required=False, default='kaboom')
class MyConfigurableUsingPositionalOptions(MyConfigurable):
first = Option(str, required=True, positional=True)
second = Option(str, required=True, positional=True)
third = Option(str, required=False, positional=True)
def test_missing_required_option_error():
with pytest.raises(TypeError) as exc:
MyConfigurable()
assert exc.match('missing 1 required option:')
def test_missing_required_options_error():
with pytest.raises(TypeError) as exc:
MyHarderConfigurable()
assert exc.match('missing 2 required options:')
def test_extraneous_option_error():
with pytest.raises(TypeError) as exc:
MyConfigurable(required_str='foo', hello='world')
assert exc.match('got 1 unexpected option:')
def test_extraneous_options_error():
with pytest.raises(TypeError) as exc:
MyConfigurable(required_str='foo', hello='world', acme='corp')
assert exc.match('got 2 unexpected options:')
def test_defaults():
o = MyConfigurable(required_str='hello')
assert o.required_str == 'hello'
assert o.default_str == 'foo'
assert o.integer == None
def test_str_type_factory():
o = MyConfigurable(required_str=42)
assert o.required_str == '42'
assert o.default_str == 'foo'
assert o.integer == None
def test_int_type_factory():
o = MyConfigurable(required_str='yo', default_str='bar', integer='42')
assert o.required_str == 'yo'
assert o.default_str == 'bar'
assert o.integer == 42
def test_bool_type_factory():
o = MyHarderConfigurable(required_str='yes', also_required='True')
assert o.required_str == 'yes'
assert o.default_str == 'foo'
assert o.integer == None
assert o.also_required == True
def test_option_resolution_order():
o = MyBetterConfigurable()
assert o.required_str == 'kaboom'
assert o.default_str == 'foo'
assert o.integer == None
def test_option_positional():
o = MyConfigurableUsingPositionalOptions('1', '2', '3', required_str='hello')

View File

@ -0,0 +1,82 @@
import pytest
from bonobo.config import Configurable, Method, Option
from bonobo.errors import ConfigurationError
class MethodBasedConfigurable(Configurable):
handler = Method()
foo = Option(positional=True)
bar = Option()
def call(self, *args, **kwargs):
self.handler(*args, **kwargs)
def test_one_wrapper_only():
with pytest.raises(ConfigurationError):
class TwoMethods(Configurable):
h1 = Method()
h2 = Method()
def test_define_with_decorator():
calls = []
@MethodBasedConfigurable
def Concrete(self, *args, **kwargs):
calls.append((args, kwargs, ))
assert callable(Concrete.handler)
t = Concrete('foo', bar='baz')
assert callable(t.handler)
assert len(calls) == 0
t()
assert len(calls) == 1
def test_define_with_argument():
calls = []
def concrete_handler(*args, **kwargs):
calls.append((args, kwargs, ))
t = MethodBasedConfigurable('foo', bar='baz', handler=concrete_handler)
assert callable(t.handler)
assert len(calls) == 0
t()
assert len(calls) == 1
def test_define_with_inheritance():
calls = []
class Inheriting(MethodBasedConfigurable):
def handler(self, *args, **kwargs):
calls.append((args, kwargs, ))
t = Inheriting('foo', bar='baz')
assert callable(t.handler)
assert len(calls) == 0
t()
assert len(calls) == 1
def test_inheritance_then_decorate():
calls = []
class Inheriting(MethodBasedConfigurable):
pass
@Inheriting
def Concrete(self, *args, **kwargs):
calls.append((args, kwargs, ))
assert callable(Concrete.handler)
t = Concrete('foo', bar='baz')
assert callable(t.handler)
assert len(calls) == 0
t()
assert len(calls) == 1

View File

@ -0,0 +1,63 @@
from operator import attrgetter
from bonobo.config import Configurable
from bonobo.config.processors import ContextProcessor, resolve_processors, ContextCurrifier
class CP1(Configurable):
@ContextProcessor
def c(self):
yield
@ContextProcessor
def a(self):
yield 'this is A'
@ContextProcessor
def b(self, a):
yield a.upper()[:-1] + 'b'
def __call__(self, a, b):
return a, b
class CP2(CP1):
@ContextProcessor
def f(self):
pass
@ContextProcessor
def e(self):
pass
@ContextProcessor
def d(self):
pass
class CP3(CP2):
@ContextProcessor
def c(self):
pass
@ContextProcessor
def b(self):
pass
def get_all_processors_names(cls):
return list(map(attrgetter('__name__'), resolve_processors(cls)))
def test_inheritance_and_ordering():
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(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()

View File

@ -0,0 +1,96 @@
import threading
import time
import pytest
from bonobo.config import Configurable, Container, Exclusive, Service
from bonobo.config.services import validate_service_name
class PrinterInterface():
def print(self, *args):
raise NotImplementedError()
class ConcretePrinter(PrinterInterface):
def __init__(self, prefix):
self.prefix = prefix
def print(self, *args):
return ';'.join((self.prefix, *args))
SERVICES = Container(
printer0=ConcretePrinter(prefix='0'),
printer1=ConcretePrinter(prefix='1'),
)
class MyServiceDependantConfigurable(Configurable):
printer = Service(
PrinterInterface,
)
def __call__(self, printer: PrinterInterface, *args):
return printer.print(*args)
def test_service_name_validator():
assert validate_service_name('foo') == 'foo'
assert validate_service_name('foo.bar') == 'foo.bar'
assert validate_service_name('Foo') == 'Foo'
assert validate_service_name('Foo.Bar') == 'Foo.Bar'
assert validate_service_name('Foo.a0') == 'Foo.a0'
with pytest.raises(ValueError):
validate_service_name('foo.0')
with pytest.raises(ValueError):
validate_service_name('0.foo')
def test_service_dependency():
o = MyServiceDependantConfigurable(printer='printer0')
assert o(SERVICES.get('printer0'), 'foo', 'bar') == '0;foo;bar'
assert o(SERVICES.get('printer1'), 'bar', 'baz') == '1;bar;baz'
assert o(*SERVICES.args_for(o), 'foo', 'bar') == '0;foo;bar'
def test_service_dependency_unavailable():
o = MyServiceDependantConfigurable(printer='printer2')
with pytest.raises(KeyError):
SERVICES.args_for(o)
class VCR:
def __init__(self):
self.tape = []
def append(self, x):
return self.tape.append(x)
def test_exclusive():
vcr = VCR()
vcr.append('hello')
def record(prefix, vcr=vcr):
with Exclusive(vcr):
for i in range(5):
vcr.append(' '.join((prefix, str(i))))
time.sleep(0.05)
threads = [threading.Thread(target=record, args=(str(i), )) for i in range(5)]
for thread in threads:
thread.start()
time.sleep(0.01) # this is not good practice, how to test this without sleeping ?? XXX
for thread in threads:
thread.join()
assert vcr.tape == [
'hello', '0 0', '0 1', '0 2', '0 3', '0 4', '1 0', '1 1', '1 2', '1 3', '1 4', '2 0', '2 1', '2 2', '2 3',
'2 4', '3 0', '3 1', '3 2', '3 3', '3 4', '4 0', '4 1', '4 2', '4 3', '4 4'
]