diff options
| author | Elena ``of Valhalla'' Grandi <valhalla@trueelena.org> | 2020-10-07 08:45:39 +0200 | 
|---|---|---|
| committer | Elena ``of Valhalla'' Grandi <valhalla@trueelena.org> | 2020-10-07 08:45:39 +0200 | 
| commit | 9b54ed0ba513c4fe2e7d403b014e9819a8e84fa5 (patch) | |
| tree | e461a910e7a390b75439818dbbc81217cbe676f1 | |
| parent | 7e0061b4d99e8a23734bdd6d9a83942eb73c5793 (diff) | |
Validate also lists
| -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() | 
