[config] Refactoring of configurables, allowing partially configured objects.

Configurables did not allow more than one "method" option, and mixed
scenarios (options+methods+...) were sometimes flaky, forcing the user
to know what order was the right one. Now, all options work the same,
sharing the same "order" namespace.

Backward incompatible change: Options are now required by default,
unless a default is provided.

Also adds a few candies for debugging/testing, found in the
bonobo.util.inspect module.
This commit is contained in:
Romain Dorgueil
2017-07-05 11:15:03 +02:00
parent 67b4227436
commit 5062221e78
19 changed files with 573 additions and 120 deletions

View File

@ -2,12 +2,17 @@ import pytest
from bonobo.config.configurables import Configurable
from bonobo.config.options import Option
from bonobo.util.inspect import inspect_node
class NoOptConfigurable(Configurable):
pass
class MyConfigurable(Configurable):
required_str = Option(str, required=True)
required_str = Option(str)
default_str = Option(str, default='foo')
integer = Option(int)
integer = Option(int, required=False)
class MyHarderConfigurable(MyConfigurable):
@ -25,14 +30,20 @@ class MyConfigurableUsingPositionalOptions(MyConfigurable):
def test_missing_required_option_error():
with inspect_node(MyConfigurable()) as ni:
assert ni.partial
with pytest.raises(TypeError) as exc:
MyConfigurable()
MyConfigurable(_final=True)
assert exc.match('missing 1 required option:')
def test_missing_required_options_error():
with inspect_node(MyHarderConfigurable()) as ni:
assert ni.partial
with pytest.raises(TypeError) as exc:
MyHarderConfigurable()
MyHarderConfigurable(_final=True)
assert exc.match('missing 2 required options:')
@ -50,6 +61,10 @@ def test_extraneous_options_error():
def test_defaults():
o = MyConfigurable(required_str='hello')
with inspect_node(o) as ni:
assert not ni.partial
assert o.required_str == 'hello'
assert o.default_str == 'foo'
assert o.integer == None
@ -57,6 +72,10 @@ def test_defaults():
def test_str_type_factory():
o = MyConfigurable(required_str=42)
with inspect_node(o) as ni:
assert not ni.partial
assert o.required_str == '42'
assert o.default_str == 'foo'
assert o.integer == None
@ -64,6 +83,10 @@ def test_str_type_factory():
def test_int_type_factory():
o = MyConfigurable(required_str='yo', default_str='bar', integer='42')
with inspect_node(o) as ni:
assert not ni.partial
assert o.required_str == 'yo'
assert o.default_str == 'bar'
assert o.integer == 42
@ -71,6 +94,10 @@ def test_int_type_factory():
def test_bool_type_factory():
o = MyHarderConfigurable(required_str='yes', also_required='True')
with inspect_node(o) as ni:
assert not ni.partial
assert o.required_str == 'yes'
assert o.default_str == 'foo'
assert o.integer == None
@ -79,6 +106,10 @@ def test_bool_type_factory():
def test_option_resolution_order():
o = MyBetterConfigurable()
with inspect_node(o) as ni:
assert not ni.partial
assert o.required_str == 'kaboom'
assert o.default_str == 'foo'
assert o.integer == None
@ -86,3 +117,21 @@ def test_option_resolution_order():
def test_option_positional():
o = MyConfigurableUsingPositionalOptions('1', '2', '3', required_str='hello')
with inspect_node(o) as ni:
assert not ni.partial
assert o.first == '1'
assert o.second == '2'
assert o.third == '3'
assert o.required_str == 'hello'
assert o.default_str == 'foo'
assert o.integer is None
def test_no_opt_configurable():
o = NoOptConfigurable()
with inspect_node(o) as ni:
assert not ni.partial