|
|
@@ -84,31 +84,45 @@ class IndexField: |
|
|
|
class Index(list[tuple[str, str]]): |
|
|
|
'''An index (key-value mapping, possibly with repeated keys) of a file produced by a module''' |
|
|
|
|
|
|
|
fields: list[IndexField] = [] |
|
|
|
fields: tuple[IndexField] = () |
|
|
|
'''The fields for this index''' |
|
|
|
|
|
|
|
_allFieldsCache: typing.Optional[tuple[IndexField]] = None |
|
|
|
|
|
|
|
def append(self, *args): |
|
|
|
if len(args) == 1: |
|
|
|
args = args[0] |
|
|
|
return super().append(args) |
|
|
|
|
|
|
|
# This should be a @classmethod, too, but that's deprecated since Python 3.11. |
|
|
|
@property |
|
|
|
def _allFields(self): |
|
|
|
'''All fields known by this index, own ones and all from superclasses''' |
|
|
|
|
|
|
|
if type(self)._allFieldsCache is None: |
|
|
|
fields = [] |
|
|
|
for cls in reversed(type(self).mro()): |
|
|
|
fields.extend(getattr(cls, 'fields', [])) |
|
|
|
type(self)._allFieldsCache = tuple(fields) |
|
|
|
return type(self)._allFieldsCache |
|
|
|
|
|
|
|
def validate(self): |
|
|
|
'''Check that all keys and values in the index conform to the specification''' |
|
|
|
|
|
|
|
keyCounts = collections.Counter(key for key, _ in self) |
|
|
|
keys = set(keyCounts) |
|
|
|
|
|
|
|
permittedKeys = set(field.key for field in type(self).fields) |
|
|
|
permittedKeys = set(field.key for field in self._allFields) |
|
|
|
unrecognisedKeys = keys - permittedKeys |
|
|
|
if unrecognisedKeys: |
|
|
|
raise IndexValidationError(f'Unrecognised key(s): {", ".join(sorted(unrecognisedKeys))}') |
|
|
|
|
|
|
|
requiredKeys = set(field.key for field in type(self).fields if field.required) |
|
|
|
requiredKeys = set(field.key for field in self._allFields if field.required) |
|
|
|
missingRequiredKeys = requiredKeys - keys |
|
|
|
if missingRequiredKeys: |
|
|
|
raise IndexValidationError(f'Missing required key(s): {", ".join(sorted(missingRequiredKeys))}') |
|
|
|
|
|
|
|
repeatableKeys = set(field.key for field in type(self).fields if field.repeatable) |
|
|
|
repeatableKeys = set(field.key for field in self._allFields if field.repeatable) |
|
|
|
repeatedKeys = set(key for key, count in keyCounts.items() if count > 1) |
|
|
|
repeatedUnrepeatableKeys = repeatedKeys - repeatableKeys |
|
|
|
if repeatedUnrepeatableKeys: |
|
|
|