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'
__description__ = 'Python application configuration'
__url__ = 'https://github.com/edaniszewski/bison'
__version__ = '0.1.1'
__version__ = '0.1.2'
__author__ = 'Erick Daniszewski'
__author_email__ = 'edaniszewski@gmail.com'
__license__ = 'MIT'

View File

@ -114,7 +114,7 @@ class Scheme(object):
for arg in self.args:
# the option exists in the 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
else:
@ -134,10 +134,12 @@ class _BaseOpt(object):
self.name = None
self.default = NoDefault
def validate(self, value):
def validate(self, key, value):
"""Validate that the option constraints are met by the configuration.
Args:
key: The key name for the option. This is used to identify the
field on error.
value: The value corresponding with the option.
Raises:
@ -187,7 +189,7 @@ class Option(_BaseOpt):
found in the config, the default will be used (regardless of whether
it is required or optional). By default, this is the internal
`_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.
bind_env (bool|str|None): Bind the option to an environment variable.
"""
@ -201,14 +203,22 @@ class Option(_BaseOpt):
self.choices = choices
self.bind_env = bind_env
def validate(self, value):
if (self.type is not None) and (type(value) != self.type):
def validate(self, key, value):
if self.type is not None:
if isinstance(self.type, (list, tuple)):
if type(value) not in self.type:
raise errors.SchemeValidationError(
'{} is of type {}, but should be {}'.format(value, type(value), self.type)
'{}={} : 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):
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):
@ -335,9 +345,9 @@ class DictOption(_BaseOpt):
self.scheme = scheme
self.bind_env = bind_env
def validate(self, value):
def validate(self, key, value):
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):
self.scheme.validate(value)
@ -414,9 +424,9 @@ class ListOption(_BaseOpt):
self.member_scheme = member_scheme
self.bind_env = bind_env
def validate(self, value):
def validate(self, key, value):
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:
raise errors.SchemeValidationError(

View File

@ -9,7 +9,7 @@ def test_base_opt_validate():
"""Validate the base option, which should fail."""
opt = scheme._BaseOpt()
with pytest.raises(NotImplementedError):
opt.validate('test-data')
opt.validate('foo', 'test-data')
def test_base_opt_parse_env():
@ -69,13 +69,17 @@ class TestOption:
(tuple, ('a', 'b')),
(dict, {}),
(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):
"""Validate an Option, where type validation succeeds"""
opt = scheme.Option('test-option', field_type=field_type)
opt.validate(value)
opt.validate('foo', value)
@pytest.mark.parametrize(
'field_type,value', [
@ -108,13 +112,18 @@ class TestOption:
(list, 1),
(list, 1.8),
(list, True),
([int, float], 'foo'),
([int, float], None),
((int, float), 'foo'),
((int, float), None),
]
)
def test_validate_type_failure(self, field_type, value):
"""Validate an Option, where type validation fails"""
opt = scheme.Option('test-option', field_type=field_type)
with pytest.raises(errors.SchemeValidationError):
opt.validate(value)
opt.validate('foo', value)
@pytest.mark.parametrize(
'choices,value', [
@ -133,7 +142,7 @@ class TestOption:
def test_validate_choices_ok(self, choices, value):
"""Validate an Option, where choice validation succeeds"""
opt = scheme.Option('test-option', choices=choices)
opt.validate(value)
opt.validate('foo', value)
@pytest.mark.parametrize(
'choices,value', [
@ -148,7 +157,7 @@ class TestOption:
"""Validate an Option, where choice validation succeeds"""
opt = scheme.Option('test-option', choices=choices)
with pytest.raises(errors.SchemeValidationError):
opt.validate(value)
opt.validate('foo', value)
@pytest.mark.parametrize(
'option,value,expected', [
@ -276,19 +285,19 @@ class TestDictOption:
"""Validate a DictOption where the given value is not a dict"""
opt = scheme.DictOption('test-opt', scheme.Scheme())
with pytest.raises(errors.SchemeValidationError):
opt.validate(value)
opt.validate('foo', value)
def test_validate_no_scheme(self):
"""Validate a DictOption with no scheme"""
opt = scheme.DictOption('test-opt', None)
opt.validate({'foo': 'bar'})
opt.validate('foo', {'foo': 'bar'})
def test_validate_with_scheme(self):
"""Validate a DictOption with a scheme"""
opt = scheme.DictOption('test-opt', scheme.Scheme(
scheme.Option('foo', field_type=str)
))
opt.validate({'foo': 'bar'})
opt.validate('foo', {'foo': 'bar'})
@pytest.mark.parametrize(
'option,prefix,auto_env', [
@ -374,7 +383,7 @@ class TestListOption:
"""Validate when the value is not a list"""
opt = scheme.ListOption('test-opt')
with pytest.raises(errors.SchemeValidationError):
opt.validate(value)
opt.validate('foo', value)
def test_validate_member_type_scheme_conflict(self):
"""Validate the ListOption when both member_type and member_scheme are defined."""
@ -384,7 +393,7 @@ class TestListOption:
member_scheme=scheme.Scheme()
)
with pytest.raises(errors.SchemeValidationError):
opt.validate([1, 2, 3])
opt.validate('foo', [1, 2, 3])
@pytest.mark.parametrize(
'member_type,value', [
@ -400,7 +409,7 @@ class TestListOption:
def test_validate_member_type_ok(self, member_type, value):
"""Validate the ListOption, where member_type validation succeeds."""
opt = scheme.ListOption('test-opt', member_type=member_type)
opt.validate(value)
opt.validate('foo', value)
@pytest.mark.parametrize(
'member_type,value', [
@ -424,7 +433,7 @@ class TestListOption:
"""Validate the ListOption, where member_type validation fails."""
opt = scheme.ListOption('test-opt', member_type=member_type)
with pytest.raises(errors.SchemeValidationError):
opt.validate(value)
opt.validate('foo', value)
@pytest.mark.parametrize(
'member_scheme,value', [
@ -444,7 +453,7 @@ class TestListOption:
def test_validate_member_scheme_ok(self, member_scheme, value):
"""Validate the ListOption, where member_scheme validation succeeds."""
opt = scheme.ListOption('test-opt', member_scheme=member_scheme)
opt.validate(value)
opt.validate('foo', value)
@pytest.mark.parametrize(
'member_scheme,value', [
@ -471,13 +480,13 @@ class TestListOption:
"""Validate the ListOption, where member_scheme validation fails."""
opt = scheme.ListOption('test-opt', member_scheme=member_scheme)
with pytest.raises(errors.SchemeValidationError):
opt.validate(value)
opt.validate('foo', value)
def test_validate_member_scheme_not_a_scheme(self):
"""Validate the ListOption, where the member_scheme is not a Scheme."""
opt = scheme.ListOption('test-opt', member_scheme='not-none-or-scheme')
with pytest.raises(errors.SchemeValidationError):
opt.validate(['a', 'b', 'c'])
opt.validate('foo', ['a', 'b', 'c'])
@pytest.mark.parametrize(
'option,prefix,auto_env', [