Skip to content

Commit c254fd0

Browse files
committed
Demystify from/to_dict methods in Signed baseclass
Prior to this commit the (abstract) 'Signed' base class implemented from/to_dict methods, to be used by any subclass in addition to or instead of a custom from/to_dict method. The design led to some confusion, especially in 'Signed.from_dict' factories, which instantiated subclass objects when called on a subclass, which didn't implement its own 'from_dict' method. This commit demystifies the design, by implementing from/to_dict on all 'Signed' subclasses, and moving common from/to_dict tasks to helper functions in the 'Signed' class. The newly gained clarity and explicitness comes at the cost of slightly more lines of code. Signed-off-by: Lukas Puehringer <[email protected]>
1 parent f421cdc commit c254fd0

File tree

1 file changed

+54
-24
lines changed

1 file changed

+54
-24
lines changed

tuf/api/metadata.py

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -301,31 +301,31 @@ def __init__(
301301
raise ValueError(f'version must be < 0, got {version}')
302302
self.version = version
303303

304+
@staticmethod
305+
def _common_fields_from_dict(signed_dict: Mapping[str, Any]) -> list:
306+
"""Returns common fields of 'Signed' instances from the passed dict
307+
representation, and returns an ordered list to be passed as leading
308+
positional arguments to a subclass constructor.
304309
305-
# Deserialization (factories).
306-
@classmethod
307-
def from_dict(cls, signed_dict: Mapping[str, Any]) -> 'Signed':
308-
"""Creates Signed object from its dict representation. """
310+
See '{Root, Timestamp, Snapshot, Targets}.from_dict' methods for usage.
311+
312+
"""
313+
_type = signed_dict.pop('_type')
314+
version = signed_dict.pop('version')
315+
spec_version = signed_dict.pop('spec_version')
316+
expires_str = signed_dict.pop('expires')
309317
# Convert 'expires' TUF metadata string to a datetime object, which is
310318
# what the constructor expects and what we store. The inverse operation
311-
# is implemented in 'to_dict'.
312-
signed_dict['expires'] = tuf.formats.expiry_string_to_datetime(
313-
signed_dict['expires'])
314-
# NOTE: We write the converted 'expires' back into 'signed_dict' above
315-
# so that we can pass it to the constructor as '**signed_dict' below,
316-
# along with other fields that belong to Signed subclasses.
317-
# Any 'from_dict'(-like) conversions of fields that correspond to a
318-
# subclass should be performed in the 'from_dict' method of that
319-
# subclass and also be written back into 'signed_dict' before calling
320-
# super().from_dict.
321-
322-
# NOTE: cls might be a subclass of Signed, if 'from_dict' was called on
323-
# that subclass (see e.g. Metadata.from_dict).
324-
return cls(**signed_dict)
319+
# is implemented in '_common_fields_to_dict'.
320+
expires = tuf.formats.expiry_string_to_datetime(expires_str)
321+
return [_type, version, spec_version, expires]
325322

323+
def _common_fields_to_dict(self) -> Dict[str, Any]:
324+
"""Returns dict representation of common fields of 'Signed' instances.
326325
327-
def to_dict(self) -> Dict[str, Any]:
328-
"""Returns the dict representation of self. """
326+
See '{Root, Timestamp, Snapshot, Targets}.to_dict' methods for usage.
327+
328+
"""
329329
return {
330330
'_type': self._type,
331331
'version': self.version,
@@ -393,10 +393,18 @@ def __init__(
393393
self.keys = keys
394394
self.roles = roles
395395

396+
@classmethod
397+
def from_dict(cls, root_dict: Mapping[str, Any]) -> 'Root':
398+
"""Creates Root object from its dict representation. """
399+
common_args = super()._common_fields_from_dict(root_dict)
400+
consistent_snapshot = root_dict.pop('consistent_snapshot')
401+
keys = root_dict.pop('keys')
402+
roles = root_dict.pop('roles')
403+
return cls(*common_args, consistent_snapshot, keys, roles)
396404

397405
def to_dict(self) -> Dict[str, Any]:
398406
"""Returns the dict representation of self. """
399-
root_dict = super().to_dict()
407+
root_dict = super()._common_fields_to_dict()
400408
root_dict.update({
401409
'consistent_snapshot': self.consistent_snapshot,
402410
'keys': self.keys,
@@ -453,9 +461,16 @@ def __init__(
453461
# TODO: Add class for meta
454462
self.meta = meta
455463

464+
@classmethod
465+
def from_dict(cls, timestamp_dict: Mapping[str, Any]) -> 'Timestamp':
466+
"""Creates Timestamp object from its dict representation. """
467+
common_args = super()._common_fields_from_dict(timestamp_dict)
468+
meta = timestamp_dict.pop('meta')
469+
return cls(*common_args, meta)
470+
456471
def to_dict(self) -> Dict[str, Any]:
457472
"""Returns the dict representation of self. """
458-
timestamp_dict = super().to_dict()
473+
timestamp_dict = super()._common_fields_to_dict()
459474
timestamp_dict.update({
460475
'meta': self.meta
461476
})
@@ -505,9 +520,16 @@ def __init__(
505520
# TODO: Add class for meta
506521
self.meta = meta
507522

523+
@classmethod
524+
def from_dict(cls, snapshot_dict: Mapping[str, Any]) -> 'Snapshot':
525+
"""Creates Snapshot object from its dict representation. """
526+
common_args = super()._common_fields_from_dict(snapshot_dict)
527+
meta = snapshot_dict.pop('meta')
528+
return cls(*common_args, meta)
529+
508530
def to_dict(self) -> Dict[str, Any]:
509531
"""Returns the dict representation of self. """
510-
snapshot_dict = super().to_dict()
532+
snapshot_dict = super()._common_fields_to_dict()
511533
snapshot_dict.update({
512534
'meta': self.meta
513535
})
@@ -595,9 +617,17 @@ def __init__(
595617
self.targets = targets
596618
self.delegations = delegations
597619

620+
@classmethod
621+
def from_dict(cls, targets_dict: Mapping[str, Any]) -> 'Targets':
622+
"""Creates Targets object from its dict representation. """
623+
common_args = super()._common_fields_from_dict(targets_dict)
624+
targets = targets_dict.pop('targets')
625+
delegations = targets_dict.pop('delegations')
626+
return cls(*common_args, targets, delegations)
627+
598628
def to_dict(self) -> Dict[str, Any]:
599629
"""Returns the dict representation of self. """
600-
targets_dict = super().to_dict()
630+
targets_dict = super()._common_fields_to_dict()
601631
targets_dict.update({
602632
'targets': self.targets,
603633
'delegations': self.delegations,

0 commit comments

Comments
 (0)