diff options
-rw-r--r-- | lesana/collection.py | 24 | ||||
-rw-r--r-- | lesana/types.py | 34 | ||||
-rw-r--r-- | tests/data/wrong/items/07f079d281f542f88b97aa1c30d72e12.yaml | 11 | ||||
-rw-r--r-- | tests/test_types.py | 70 |
4 files changed, 92 insertions, 47 deletions
diff --git a/lesana/collection.py b/lesana/collection.py index a449de1..ecb3eca 100644 --- a/lesana/collection.py +++ b/lesana/collection.py @@ -94,31 +94,16 @@ class Entry(object): valid = True for name, field in self.collection.fields.items(): value = self.data.get(name, None) - t = field.name try: self.data[name] = field.load(value) except types.LesanaValueError as e: + valid = False errors.append( { 'field': name, 'error': e, } ) - - if t != 'list' and not value: - # empty fields are always fine except for lists - continue - elif t == 'list': - if not hasattr(value, '__iter__'): - valid = False - errors.append( - { - 'field': name, - 'error': 'Invalid value for list field: {}'.format( - value - ), - } - ) return valid, errors def render(self, template, searchpath='.'): @@ -181,7 +166,10 @@ class Collection(object): fields = {} for field in self.settings.get('fields', []): try: - fields[field['name']] = type_loaders[field['type']](field) + fields[field['name']] = type_loaders[field['type']]( + field, + type_loaders, + ) except KeyError: # unknown fields are treated as if they were # (unvalidated) generic YAML to support working with @@ -191,7 +179,7 @@ class Collection(object): field['type'], field['name'], ) - fields[field['name']] = types.LesanaYAML(field) + fields[field['name']] = types.LesanaYAML(field, type_loaders) return fields def _index_file(self, fname, cache): diff --git a/lesana/types.py b/lesana/types.py index c4061f2..ccdaf20 100644 --- a/lesana/types.py +++ b/lesana/types.py @@ -6,6 +6,7 @@ significantly in a future release. """ import datetime import decimal +import logging import dateutil.parser @@ -14,7 +15,7 @@ class LesanaType: """ Base class for lesana field types. """ - def __init__(self, field): + def __init__(self, field, types): self.field = field def load(self, data): @@ -99,7 +100,7 @@ class LesanaDecimal(LesanaType): return decimal.Decimal(data) except decimal.InvalidOperation: raise LesanaValueError( - "Invalid value for float field: {}".format(data) + "Invalid value for decimal field: {}".format(data) ) def empty(self): @@ -224,15 +225,38 @@ class LesanaYAML(LesanaType): return None -class LesanaList(LesanaYAML): +class LesanaList(LesanaType): """ A list of other values """ name = 'list' - # Temporary definition so that tests aren't broken in the current - # commit + def __init__(self, field, types): + super().__init__(field, types) + try: + self.sub_type = types[field['list']](field, types) + except KeyError: + logging.warning( + "Unknown field type %s in field %s", + field['type'], + field['name'], + ) + self.sub_type = types['yaml'](field, types) + + def load(self, data): + if data is None: + # empty for this type means an empty list + return [] + try: + return [self.sub_type.load(x) for x in data] + except TypeError: + raise LesanaValueError( + "Invalid value for list field: {}".format(data) + ) + + def empty(self): + return [] class LesanaValueError(ValueError): diff --git a/tests/data/wrong/items/07f079d281f542f88b97aa1c30d72e12.yaml b/tests/data/wrong/items/07f079d281f542f88b97aa1c30d72e12.yaml deleted file mode 100644 index 79fe93f..0000000 --- a/tests/data/wrong/items/07f079d281f542f88b97aa1c30d72e12.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: 'bad things' -description: | - An entry with bad things (empty instead of an empty list) -position: '' -# number (integer): Enter an integer here -number: 0 -# float (float): Enter a floating point number here -float: 0.0 -# price (decimal): prices are never float! -price: 0.00 -things: diff --git a/tests/test_types.py b/tests/test_types.py index 363d9a4..9e0c931 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -19,7 +19,7 @@ class testTypes(unittest.TestCase): } def test_base(self): - checker = types.LesanaType(self._get_field_def('base')) + checker = types.LesanaType(self._get_field_def('base'), {}) # The base class does not implement empty nor load with self.assertRaises(NotImplementedError): @@ -29,7 +29,7 @@ class testTypes(unittest.TestCase): checker.load("") def test_string(self): - checker = types.LesanaString(self._get_field_def('string')) + checker = types.LesanaString(self._get_field_def('string'), {}) s = checker.empty() self.assertEqual(s, "") @@ -41,7 +41,7 @@ class testTypes(unittest.TestCase): self.assertEqual(s, None) def test_text(self): - checker = types.LesanaText(self._get_field_def('text')) + checker = types.LesanaText(self._get_field_def('text'), {}) s = checker.empty() self.assertEqual(s, "") @@ -53,7 +53,7 @@ class testTypes(unittest.TestCase): self.assertEqual(s, None) def test_int(self): - checker = types.LesanaInt(self._get_field_def('integer')) + checker = types.LesanaInt(self._get_field_def('integer'), {}) v = checker.empty() self.assertEqual(v, 0) @@ -72,7 +72,7 @@ class testTypes(unittest.TestCase): self.assertEqual(v, None) def test_float(self): - checker = types.LesanaFloat(self._get_field_def('float')) + checker = types.LesanaFloat(self._get_field_def('float'), {}) v = checker.empty() self.assertEqual(v, 0.0) @@ -94,7 +94,7 @@ class testTypes(unittest.TestCase): self.assertEqual(v, None) def test_decimal(self): - checker = types.LesanaDecimal(self._get_field_def('decimal')) + checker = types.LesanaDecimal(self._get_field_def('decimal'), {}) v = checker.empty() self.assertEqual(v, decimal.Decimal(0)) @@ -116,7 +116,7 @@ class testTypes(unittest.TestCase): self.assertEqual(v, None) def test_timestamp(self): - checker = types.LesanaTimestamp(self._get_field_def('timestamp')) + checker = types.LesanaTimestamp(self._get_field_def('timestamp'), {}) v = checker.empty() self.assertEqual(v, None) @@ -142,7 +142,7 @@ class testTypes(unittest.TestCase): self.assertEqual(v, None) def test_datetime(self): - checker = types.LesanaDatetime(self._get_field_def('datetime')) + checker = types.LesanaDatetime(self._get_field_def('datetime'), {}) v = checker.empty() self.assertEqual(v, None) @@ -171,7 +171,7 @@ class testTypes(unittest.TestCase): self.assertEqual(v, None) def test_date(self): - checker = types.LesanaDate(self._get_field_def('date')) + checker = types.LesanaDate(self._get_field_def('date'), {}) v = checker.empty() self.assertEqual(v, None) @@ -200,7 +200,7 @@ class testTypes(unittest.TestCase): self.assertEqual(v, None) def test_boolean(self): - checker = types.LesanaBoolean(self._get_field_def('boolean')) + checker = types.LesanaBoolean(self._get_field_def('boolean'), {}) v = checker.empty() self.assertEqual(v, None) @@ -216,7 +216,7 @@ class testTypes(unittest.TestCase): self.assertEqual(v, None) def test_file(self): - checker = types.LesanaFile(self._get_field_def('file')) + checker = types.LesanaFile(self._get_field_def('file'), {}) v = checker.empty() self.assertEqual(v, "") @@ -230,7 +230,7 @@ class testTypes(unittest.TestCase): # TODO: check for invalid file paths def test_url(self): - checker = types.LesanaURL(self._get_field_def('url')) + checker = types.LesanaURL(self._get_field_def('url'), {}) v = checker.empty() self.assertEqual(v, "") @@ -244,7 +244,7 @@ class testTypes(unittest.TestCase): # TODO: check for invalid URLs def test_yaml(self): - checker = types.LesanaYAML(self._get_field_def('yaml')) + checker = types.LesanaYAML(self._get_field_def('yaml'), {}) v = checker.empty() self.assertEqual(v, None) @@ -259,6 +259,50 @@ class testTypes(unittest.TestCase): v = checker.load(None) self.assertEqual(v, None) + def test_list(self): + field_def = self._get_field_def('yaml') + # we use one type that is easy to check for correct validation + field_def['list'] = 'int' + checker = types.LesanaList(field_def, {'int': types.LesanaInt}) + + v = checker.empty() + self.assertEqual(v, []) + + some_data = [1, 2, 3] + v = checker.load(some_data) + self.assertEqual(v, some_data) + + v = checker.load(None) + self.assertEqual(v, []) + + for d in (['hello'], 1): + with self.assertRaises(types.LesanaValueError): + checker.load(d) + + def test_list_unknown_subtype(self): + field_def = self._get_field_def('yaml') + # we use one type that is easy to check for correct validation + field_def['list'] = 'int' + checker = types.LesanaList(field_def, {'yaml': types.LesanaYAML}) + + v = checker.empty() + self.assertEqual(v, []) + + some_data = [1, 2, 3] + v = checker.load(some_data) + self.assertEqual(v, some_data) + + some_data = ["hello"] + v = checker.load(some_data) + self.assertEqual(v, some_data) + + v = checker.load(None) + self.assertEqual(v, []) + + for d in (1, 1.0): + with self.assertRaises(types.LesanaValueError): + checker.load(d) + if __name__ == '__main__': unittest.main() |