bump to 0.1.2: better validation logging, support multiple option types

This commit is contained in:
Erick Daniszewski 2019-10-01 13:10:25 -04:00
parent b1e7cea974
commit 6e05e18422
No known key found for this signature in database
GPG Key ID: E0E005D7AF0CA9EC
3 changed files with 49 additions and 30 deletions

View File

@ -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'

View File

@ -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(

View File

@ -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', [