bump to 0.1.2: better validation logging, support multiple option types
This commit is contained in:
parent
b1e7cea974
commit
6e05e18422
|
@ -3,7 +3,7 @@
|
||||||
__title__ = 'bison'
|
__title__ = 'bison'
|
||||||
__description__ = 'Python application configuration'
|
__description__ = 'Python application configuration'
|
||||||
__url__ = 'https://github.com/edaniszewski/bison'
|
__url__ = 'https://github.com/edaniszewski/bison'
|
||||||
__version__ = '0.1.1'
|
__version__ = '0.1.2'
|
||||||
__author__ = 'Erick Daniszewski'
|
__author__ = 'Erick Daniszewski'
|
||||||
__author_email__ = 'edaniszewski@gmail.com'
|
__author_email__ = 'edaniszewski@gmail.com'
|
||||||
__license__ = 'MIT'
|
__license__ = 'MIT'
|
||||||
|
|
|
@ -114,7 +114,7 @@ class Scheme(object):
|
||||||
for arg in self.args:
|
for arg in self.args:
|
||||||
# the option exists in the config
|
# the option exists in the config
|
||||||
if arg.name in config:
|
if arg.name in config:
|
||||||
arg.validate(config[arg.name])
|
arg.validate(arg.name, config[arg.name])
|
||||||
|
|
||||||
# the option does not exist in the config
|
# the option does not exist in the config
|
||||||
else:
|
else:
|
||||||
|
@ -134,10 +134,12 @@ class _BaseOpt(object):
|
||||||
self.name = None
|
self.name = None
|
||||||
self.default = NoDefault
|
self.default = NoDefault
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, key, value):
|
||||||
"""Validate that the option constraints are met by the configuration.
|
"""Validate that the option constraints are met by the configuration.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
key: The key name for the option. This is used to identify the
|
||||||
|
field on error.
|
||||||
value: The value corresponding with the option.
|
value: The value corresponding with the option.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
@ -187,7 +189,7 @@ class Option(_BaseOpt):
|
||||||
found in the config, the default will be used (regardless of whether
|
found in the config, the default will be used (regardless of whether
|
||||||
it is required or optional). By default, this is the internal
|
it is required or optional). By default, this is the internal
|
||||||
`_NoDefault` type (this allows setting `None` as a default).
|
`_NoDefault` type (this allows setting `None` as a default).
|
||||||
field_type: The type that the option value should have.
|
field_type (type|list): The type that the option value should have.
|
||||||
choices (list|tuple): The valid options for the field.
|
choices (list|tuple): The valid options for the field.
|
||||||
bind_env (bool|str|None): Bind the option to an environment variable.
|
bind_env (bool|str|None): Bind the option to an environment variable.
|
||||||
"""
|
"""
|
||||||
|
@ -201,14 +203,22 @@ class Option(_BaseOpt):
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
self.bind_env = bind_env
|
self.bind_env = bind_env
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, key, value):
|
||||||
if (self.type is not None) and (type(value) != self.type):
|
if self.type is not None:
|
||||||
raise errors.SchemeValidationError(
|
if isinstance(self.type, (list, tuple)):
|
||||||
'{} is of type {}, but should be {}'.format(value, type(value), self.type)
|
if type(value) not in self.type:
|
||||||
)
|
raise errors.SchemeValidationError(
|
||||||
|
'{}={} : value is of type {}, but should be {}'.format(key, value, type(value), self.type)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if type(value) != self.type:
|
||||||
|
raise errors.SchemeValidationError(
|
||||||
|
'{}={} : value is of type {}, but should be {}'.format(key, value, type(value), self.type)
|
||||||
|
)
|
||||||
|
|
||||||
if (self.choices is not None) and (value not in self.choices):
|
if (self.choices is not None) and (value not in self.choices):
|
||||||
raise errors.SchemeValidationError(
|
raise errors.SchemeValidationError(
|
||||||
'{} is not in the valid options: {}'.format(value, self.choices)
|
'{}={} : value is not in the valid choice options: {}'.format(key, value, self.choices)
|
||||||
)
|
)
|
||||||
|
|
||||||
def parse_env(self, key=None, prefix=None, auto_env=False):
|
def parse_env(self, key=None, prefix=None, auto_env=False):
|
||||||
|
@ -335,9 +345,9 @@ class DictOption(_BaseOpt):
|
||||||
self.scheme = scheme
|
self.scheme = scheme
|
||||||
self.bind_env = bind_env
|
self.bind_env = bind_env
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, key, value):
|
||||||
if not isinstance(value, dict):
|
if not isinstance(value, dict):
|
||||||
raise errors.SchemeValidationError('{} is not a dictionary'.format(value))
|
raise errors.SchemeValidationError('{}={} : value is not a dictionary'.format(key, value))
|
||||||
|
|
||||||
if isinstance(self.scheme, Scheme):
|
if isinstance(self.scheme, Scheme):
|
||||||
self.scheme.validate(value)
|
self.scheme.validate(value)
|
||||||
|
@ -414,9 +424,9 @@ class ListOption(_BaseOpt):
|
||||||
self.member_scheme = member_scheme
|
self.member_scheme = member_scheme
|
||||||
self.bind_env = bind_env
|
self.bind_env = bind_env
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, key, value):
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
raise errors.SchemeValidationError('{} is not a list'.format(value))
|
raise errors.SchemeValidationError('{}={} : value is not a list'.format(key, value))
|
||||||
|
|
||||||
if self.member_scheme is not None and self.member_type is not None:
|
if self.member_scheme is not None and self.member_type is not None:
|
||||||
raise errors.SchemeValidationError(
|
raise errors.SchemeValidationError(
|
||||||
|
|
|
@ -9,7 +9,7 @@ def test_base_opt_validate():
|
||||||
"""Validate the base option, which should fail."""
|
"""Validate the base option, which should fail."""
|
||||||
opt = scheme._BaseOpt()
|
opt = scheme._BaseOpt()
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
opt.validate('test-data')
|
opt.validate('foo', 'test-data')
|
||||||
|
|
||||||
|
|
||||||
def test_base_opt_parse_env():
|
def test_base_opt_parse_env():
|
||||||
|
@ -69,13 +69,17 @@ class TestOption:
|
||||||
(tuple, ('a', 'b')),
|
(tuple, ('a', 'b')),
|
||||||
(dict, {}),
|
(dict, {}),
|
||||||
(dict, {'a': 'b'}),
|
(dict, {'a': 'b'}),
|
||||||
(dict, {1: 2})
|
(dict, {1: 2}),
|
||||||
|
([int, float], 1),
|
||||||
|
([int, float], 1.0),
|
||||||
|
((int, float), 1),
|
||||||
|
((int, float), 1.0),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_validate_type_ok(self, field_type, value):
|
def test_validate_type_ok(self, field_type, value):
|
||||||
"""Validate an Option, where type validation succeeds"""
|
"""Validate an Option, where type validation succeeds"""
|
||||||
opt = scheme.Option('test-option', field_type=field_type)
|
opt = scheme.Option('test-option', field_type=field_type)
|
||||||
opt.validate(value)
|
opt.validate('foo', value)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'field_type,value', [
|
'field_type,value', [
|
||||||
|
@ -108,13 +112,18 @@ class TestOption:
|
||||||
(list, 1),
|
(list, 1),
|
||||||
(list, 1.8),
|
(list, 1.8),
|
||||||
(list, True),
|
(list, True),
|
||||||
|
|
||||||
|
([int, float], 'foo'),
|
||||||
|
([int, float], None),
|
||||||
|
((int, float), 'foo'),
|
||||||
|
((int, float), None),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_validate_type_failure(self, field_type, value):
|
def test_validate_type_failure(self, field_type, value):
|
||||||
"""Validate an Option, where type validation fails"""
|
"""Validate an Option, where type validation fails"""
|
||||||
opt = scheme.Option('test-option', field_type=field_type)
|
opt = scheme.Option('test-option', field_type=field_type)
|
||||||
with pytest.raises(errors.SchemeValidationError):
|
with pytest.raises(errors.SchemeValidationError):
|
||||||
opt.validate(value)
|
opt.validate('foo', value)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'choices,value', [
|
'choices,value', [
|
||||||
|
@ -133,7 +142,7 @@ class TestOption:
|
||||||
def test_validate_choices_ok(self, choices, value):
|
def test_validate_choices_ok(self, choices, value):
|
||||||
"""Validate an Option, where choice validation succeeds"""
|
"""Validate an Option, where choice validation succeeds"""
|
||||||
opt = scheme.Option('test-option', choices=choices)
|
opt = scheme.Option('test-option', choices=choices)
|
||||||
opt.validate(value)
|
opt.validate('foo', value)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'choices,value', [
|
'choices,value', [
|
||||||
|
@ -148,7 +157,7 @@ class TestOption:
|
||||||
"""Validate an Option, where choice validation succeeds"""
|
"""Validate an Option, where choice validation succeeds"""
|
||||||
opt = scheme.Option('test-option', choices=choices)
|
opt = scheme.Option('test-option', choices=choices)
|
||||||
with pytest.raises(errors.SchemeValidationError):
|
with pytest.raises(errors.SchemeValidationError):
|
||||||
opt.validate(value)
|
opt.validate('foo', value)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'option,value,expected', [
|
'option,value,expected', [
|
||||||
|
@ -276,19 +285,19 @@ class TestDictOption:
|
||||||
"""Validate a DictOption where the given value is not a dict"""
|
"""Validate a DictOption where the given value is not a dict"""
|
||||||
opt = scheme.DictOption('test-opt', scheme.Scheme())
|
opt = scheme.DictOption('test-opt', scheme.Scheme())
|
||||||
with pytest.raises(errors.SchemeValidationError):
|
with pytest.raises(errors.SchemeValidationError):
|
||||||
opt.validate(value)
|
opt.validate('foo', value)
|
||||||
|
|
||||||
def test_validate_no_scheme(self):
|
def test_validate_no_scheme(self):
|
||||||
"""Validate a DictOption with no scheme"""
|
"""Validate a DictOption with no scheme"""
|
||||||
opt = scheme.DictOption('test-opt', None)
|
opt = scheme.DictOption('test-opt', None)
|
||||||
opt.validate({'foo': 'bar'})
|
opt.validate('foo', {'foo': 'bar'})
|
||||||
|
|
||||||
def test_validate_with_scheme(self):
|
def test_validate_with_scheme(self):
|
||||||
"""Validate a DictOption with a scheme"""
|
"""Validate a DictOption with a scheme"""
|
||||||
opt = scheme.DictOption('test-opt', scheme.Scheme(
|
opt = scheme.DictOption('test-opt', scheme.Scheme(
|
||||||
scheme.Option('foo', field_type=str)
|
scheme.Option('foo', field_type=str)
|
||||||
))
|
))
|
||||||
opt.validate({'foo': 'bar'})
|
opt.validate('foo', {'foo': 'bar'})
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'option,prefix,auto_env', [
|
'option,prefix,auto_env', [
|
||||||
|
@ -374,7 +383,7 @@ class TestListOption:
|
||||||
"""Validate when the value is not a list"""
|
"""Validate when the value is not a list"""
|
||||||
opt = scheme.ListOption('test-opt')
|
opt = scheme.ListOption('test-opt')
|
||||||
with pytest.raises(errors.SchemeValidationError):
|
with pytest.raises(errors.SchemeValidationError):
|
||||||
opt.validate(value)
|
opt.validate('foo', value)
|
||||||
|
|
||||||
def test_validate_member_type_scheme_conflict(self):
|
def test_validate_member_type_scheme_conflict(self):
|
||||||
"""Validate the ListOption when both member_type and member_scheme are defined."""
|
"""Validate the ListOption when both member_type and member_scheme are defined."""
|
||||||
|
@ -384,7 +393,7 @@ class TestListOption:
|
||||||
member_scheme=scheme.Scheme()
|
member_scheme=scheme.Scheme()
|
||||||
)
|
)
|
||||||
with pytest.raises(errors.SchemeValidationError):
|
with pytest.raises(errors.SchemeValidationError):
|
||||||
opt.validate([1, 2, 3])
|
opt.validate('foo', [1, 2, 3])
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'member_type,value', [
|
'member_type,value', [
|
||||||
|
@ -400,7 +409,7 @@ class TestListOption:
|
||||||
def test_validate_member_type_ok(self, member_type, value):
|
def test_validate_member_type_ok(self, member_type, value):
|
||||||
"""Validate the ListOption, where member_type validation succeeds."""
|
"""Validate the ListOption, where member_type validation succeeds."""
|
||||||
opt = scheme.ListOption('test-opt', member_type=member_type)
|
opt = scheme.ListOption('test-opt', member_type=member_type)
|
||||||
opt.validate(value)
|
opt.validate('foo', value)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'member_type,value', [
|
'member_type,value', [
|
||||||
|
@ -424,7 +433,7 @@ class TestListOption:
|
||||||
"""Validate the ListOption, where member_type validation fails."""
|
"""Validate the ListOption, where member_type validation fails."""
|
||||||
opt = scheme.ListOption('test-opt', member_type=member_type)
|
opt = scheme.ListOption('test-opt', member_type=member_type)
|
||||||
with pytest.raises(errors.SchemeValidationError):
|
with pytest.raises(errors.SchemeValidationError):
|
||||||
opt.validate(value)
|
opt.validate('foo', value)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'member_scheme,value', [
|
'member_scheme,value', [
|
||||||
|
@ -444,7 +453,7 @@ class TestListOption:
|
||||||
def test_validate_member_scheme_ok(self, member_scheme, value):
|
def test_validate_member_scheme_ok(self, member_scheme, value):
|
||||||
"""Validate the ListOption, where member_scheme validation succeeds."""
|
"""Validate the ListOption, where member_scheme validation succeeds."""
|
||||||
opt = scheme.ListOption('test-opt', member_scheme=member_scheme)
|
opt = scheme.ListOption('test-opt', member_scheme=member_scheme)
|
||||||
opt.validate(value)
|
opt.validate('foo', value)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'member_scheme,value', [
|
'member_scheme,value', [
|
||||||
|
@ -471,13 +480,13 @@ class TestListOption:
|
||||||
"""Validate the ListOption, where member_scheme validation fails."""
|
"""Validate the ListOption, where member_scheme validation fails."""
|
||||||
opt = scheme.ListOption('test-opt', member_scheme=member_scheme)
|
opt = scheme.ListOption('test-opt', member_scheme=member_scheme)
|
||||||
with pytest.raises(errors.SchemeValidationError):
|
with pytest.raises(errors.SchemeValidationError):
|
||||||
opt.validate(value)
|
opt.validate('foo', value)
|
||||||
|
|
||||||
def test_validate_member_scheme_not_a_scheme(self):
|
def test_validate_member_scheme_not_a_scheme(self):
|
||||||
"""Validate the ListOption, where the member_scheme is not a Scheme."""
|
"""Validate the ListOption, where the member_scheme is not a Scheme."""
|
||||||
opt = scheme.ListOption('test-opt', member_scheme='not-none-or-scheme')
|
opt = scheme.ListOption('test-opt', member_scheme='not-none-or-scheme')
|
||||||
with pytest.raises(errors.SchemeValidationError):
|
with pytest.raises(errors.SchemeValidationError):
|
||||||
opt.validate(['a', 'b', 'c'])
|
opt.validate('foo', ['a', 'b', 'c'])
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'option,prefix,auto_env', [
|
'option,prefix,auto_env', [
|
||||||
|
|
Loading…
Reference in New Issue