From b445258f28708bf5c709e3db7dc2c0363b41a99f Mon Sep 17 00:00:00 2001 From: Elena ``of Valhalla'' Grandi Date: Sat, 3 Oct 2020 16:27:42 +0200 Subject: Load one type checker per field --- lesana/collection.py | 48 ++++++++++++++++++++++++++---------------- lesana/types.py | 11 ++++++++++ tests/data/wrong/settings.yaml | 3 +++ tests/test_collection.py | 24 +++++++++++++++++---- 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/lesana/collection.py b/lesana/collection.py index 4b3e964..5b7b809 100644 --- a/lesana/collection.py +++ b/lesana/collection.py @@ -92,22 +92,15 @@ class Entry(object): def validate(self): errors = [] valid = True - for field in self.collection.settings['fields']: - value = self.data.get(field['name'], None) - t = field['type'] + for name, field in self.collection.fields.items(): + value = self.data.get(name, None) + t = field.name try: - self.data[field['name']] = self.collection.types[t].load(value) - except KeyError: - errors.append( - { - 'field': field['name'], - 'error': "No such type {}".format(t), - } - ) + self.data[name] = field.load(value) except types.LesanaValueError as e: errors.append( { - 'field': field['name'], + 'field': name, 'error': e, } ) @@ -120,7 +113,7 @@ class Entry(object): valid = False errors.append( { - 'field': field['name'], + 'field': name, 'error': 'Invalid value for list field: {}'.format( value ), @@ -150,7 +143,6 @@ class Collection(object): def __init__(self, directory=None, itemdir='items'): self.basedir = directory or os.getcwd() self.itemdir = os.path.join(self.basedir, itemdir) - self.types = self._load_types() try: with open(os.path.join(self.basedir, 'settings.yaml')) as fp: self.settings = ruamel.yaml.load( @@ -158,6 +150,7 @@ class Collection(object): ) except FileNotFoundError: self.settings = ruamel.yaml.safe_load("{}") + self.fields = self.load_field_types() os.makedirs(os.path.join(self.basedir, '.lesana'), exist_ok=True) if 'lang' in self.settings: try: @@ -176,11 +169,30 @@ class Collection(object): self.safe = False self.entry_class = Entry - def _load_types(self): + def _get_subsubclasses(self, cls): + for c in cls.__subclasses__(): + yield c + yield from self._get_subsubclasses(c) + + def load_field_types(self): type_loaders = {} - for t in types.LesanaType.__subclasses__(): - type_loaders[t.name] = t() - return type_loaders + for t in self._get_subsubclasses(types.LesanaType): + type_loaders[t.name] = t + fields = {} + for field in self.settings.get('fields', []): + try: + fields[field['name']] = type_loaders[field['type']]() + except KeyError: + # unknown fields are treated as if they were + # (unvalidated) generic YAML to support working with + # collections based on lesana derivatives + logging.warning( + "Unknown field type %s in field %s", + field['type'], + field['name'], + ) + fields[field['name']] = types.LesanaYAML() + return fields def _index_file(self, fname, cache): with open(os.path.join(self.itemdir, fname)) as fp: diff --git a/lesana/types.py b/lesana/types.py index 565d1b2..a252830 100644 --- a/lesana/types.py +++ b/lesana/types.py @@ -221,6 +221,17 @@ class LesanaYAML(LesanaType): return None +class LesanaList(LesanaYAML): + """ + A list of other values + """ + + name = 'list' + + # Temporary definition so that tests aren't broken in the current + # commit + + class LesanaValueError(ValueError): """ Raised in case of validation errors. diff --git a/tests/data/wrong/settings.yaml b/tests/data/wrong/settings.yaml index 83a542b..bf79572 100644 --- a/tests/data/wrong/settings.yaml +++ b/tests/data/wrong/settings.yaml @@ -23,3 +23,6 @@ fields: type: list list: string index: field + - name: cloud + type: cloud + help: 'There is no cloud type' diff --git a/tests/test_collection.py b/tests/test_collection.py index 832f421..f7ddf6d 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -305,6 +305,22 @@ class testComplexCollection(unittest.TestCase): self.assertIn('with_default', entry.yaml_data) self.assertIn('amount: 0', entry.yaml_data) + def test_load_field_loaders(self): + # Check that all fields have been loaded, with the right types + to_test = ( + ('name', lesana.types.LesanaString), + ('description', lesana.types.LesanaText), + ('position', lesana.types.LesanaString), + ('something', lesana.types.LesanaYAML), + ('tags', lesana.types.LesanaList), + ('keywords', lesana.types.LesanaList), + ('exists', lesana.types.LesanaBoolean), + ('with_default', lesana.types.LesanaString), + ('amount', lesana.types.LesanaInt), + ) + for f in to_test: + self.assertIsInstance(self.collection.fields[f[0]], f[1]) + class testCollectionWithErrors(unittest.TestCase): def setUp(self): @@ -320,8 +336,8 @@ class testCollectionWithErrors(unittest.TestCase): # check that the log contains a warning. with self.assertLogs(level=logging.WARNING) as cm: self.collection = lesana.Collection(self.tmpdir) - self.assertEqual(len(cm.output), 1) - self.assertIn("Invalid language", cm.output[0]) + self.assertEqual(len(cm.output), 2) + self.assertIn("Invalid language", cm.output[1]) # The collection will default to english, but should still work. self.collection.update_cache() self.assertIsNotNone(self.collection.settings) @@ -334,7 +350,7 @@ class testCollectionWithErrors(unittest.TestCase): self.assertIsNotNone(self.collection.settings) self.assertIsNotNone(self.collection.stemmer) # Fields with no "index" entry are not indexed - self.assertEqual(len(self.collection.settings['fields']), 7) + self.assertEqual(len(self.collection.settings['fields']), 8) self.assertEqual(len(self.collection.indexed_fields), 3) def test_init(self): @@ -343,7 +359,7 @@ class testCollectionWithErrors(unittest.TestCase): self.collection.settings['name'], "Lesana collection with certain errors", ) - self.assertEqual(len(self.collection.settings['fields']), 7) + self.assertEqual(len(self.collection.settings['fields']), 8) self.assertIsNotNone(self.collection.stemmer) self.assertEqual(len(self.collection.indexed_fields), 3) -- cgit v1.2.3