Browse Source

Fix deserialisation of metadata producing an object with the wrong type

master
JustAnotherArchivist 9 months ago
parent
commit
3dc1009e5b
1 changed files with 29 additions and 4 deletions
  1. +29
    -4
      codearchiver/core.py

+ 29
- 4
codearchiver/core.py View File

@@ -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)


Loading…
Cancel
Save