diff --git a/codearchiver/core.py b/codearchiver/core.py index 0e4a58c..fc30a37 100644 --- a/codearchiver/core.py +++ b/codearchiver/core.py @@ -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)