|
|
@@ -108,6 +108,8 @@ class Metadata(list[tuple[str, str]]): |
|
|
|
# The easiest way to achieve that is by mapping class objects to the corresponding cache. |
|
|
|
_allFieldsCache: dict[typing.Type['Metadata'], tuple[MetadataField]] = {} |
|
|
|
|
|
|
|
_subclassesByNameCache: dict[str, typing.Type['Metadata']] = {} |
|
|
|
|
|
|
|
def append(self, *args): |
|
|
|
if len(args) == 1: |
|
|
|
args = args[0] |
|
|
@@ -126,6 +128,12 @@ class Metadata(list[tuple[str, str]]): |
|
|
|
cls._allFieldsCache[cls] = tuple(fields) |
|
|
|
return cls._allFieldsCache[cls] |
|
|
|
|
|
|
|
@classmethod |
|
|
|
def _get_type_version_string(cls): |
|
|
|
if 'version' not in cls.__dict__: |
|
|
|
return None |
|
|
|
return f'{cls.__module__}.{cls.__qualname__}/{cls.version}' |
|
|
|
|
|
|
|
def validate(self): |
|
|
|
'''Check that all keys and values conform to the specification''' |
|
|
|
|
|
|
@@ -201,6 +209,25 @@ class Metadata(list[tuple[str, str]]): |
|
|
|
cm = contextlib.nullcontext(f) |
|
|
|
with cm as fp: |
|
|
|
o = cls((key, value[:-1]) for key, value in map(functools.partial(str.split, sep = ': ', maxsplit = 1), fp)) |
|
|
|
|
|
|
|
# Extract the type and recreate with the correct Metadata subclass if necessary |
|
|
|
#TODO Implement a cleaner way of doing this than parsing it out of the 'Metadata version' field |
|
|
|
metaVersion = next((value for key, value in o if key == 'Metadata version'), None) |
|
|
|
if not metaVersion: |
|
|
|
raise MetadataValidationError('missing metadata version') |
|
|
|
#TODO Support for different metadata versions in case I need to bump it for backwards-incompatible changes since older files may still need to be read |
|
|
|
metaTypeVersionString = metaVersion.rsplit(' ', 1)[-1] |
|
|
|
if metaTypeVersionString not in cls._subclassesByNameCache: |
|
|
|
q = collections.deque() |
|
|
|
q.append(Metadata) |
|
|
|
while q: |
|
|
|
c = q.popleft() |
|
|
|
if (cts := c._get_type_version_string()): |
|
|
|
cls._subclassesByNameCache[cts] = c |
|
|
|
q.extend(c.__subclasses__()) |
|
|
|
if (metaType := cls._subclassesByNameCache.get(metaTypeVersionString)) is not cls: |
|
|
|
o = metaType(o) |
|
|
|
|
|
|
|
if validate: |
|
|
|
o.validate() |
|
|
|
return o |
|
|
@@ -408,10 +435,8 @@ class Module(metaclass = ModuleMeta): |
|
|
|
idx.append('Module', type(self).name) |
|
|
|
metadataVersions = [] |
|
|
|
for cls in reversed(type(self).MetadataClass.mro()): |
|
|
|
version = cls.__dict__.get('version') |
|
|
|
if version is None: |
|
|
|
continue |
|
|
|
metadataVersions.append(f'{cls.__module__}.{cls.__qualname__}/{version}') |
|
|
|
if (f := getattr(cls, '_get_type_version_string', None)) and (version := f()): |
|
|
|
metadataVersions.append(version) |
|
|
|
idx.append('Metadata version', ' '.join(metadataVersions)) |
|
|
|
idx.append('ID', self._id) |
|
|
|
idx.append('Input URL', self._url) |
|
|
|