Skip to content

Commit e1be085

Browse files
author
Lukas Puehringer
committed
Move to/from_dict metadata API methods to util
Add tuf.api.serialization.util module with functions to convert between TUF metadata class model and the corresponding dictionary representation. These functions replace the corresponding to/from_dict classmethods. Configure api/pylintrc to exempt '_type' from protected member access warning, because the underscore prefix here is only used to avoid name shadowing. Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
1 parent 240fb54 commit e1be085

File tree

5 files changed

+168
-137
lines changed

5 files changed

+168
-137
lines changed

tests/test_api.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
CanonicalJSONSerializer
3939
)
4040

41+
from tuf.api.serialization.util import metadata_to_dict
42+
4143
from securesystemslib.interface import (
4244
import_ed25519_publickey_from_file,
4345
import_ed25519_privatekey_from_file
@@ -47,6 +49,7 @@
4749
format_keyval_to_metadata
4850
)
4951

52+
5053
logger = logging.getLogger(__name__)
5154

5255

@@ -111,9 +114,9 @@ def test_generic_read(self):
111114
self.assertTrue(
112115
isinstance(metadata_obj2.signed, inner_metadata_cls))
113116

114-
# ... and return the same object (compared by dict representation)
115-
self.assertDictEqual(
116-
metadata_obj.to_dict(), metadata_obj2.to_dict())
117+
# ... and are equal (compared by dict representation)
118+
self.assertDictEqual(metadata_to_dict(metadata_obj),
119+
metadata_to_dict(metadata_obj2))
117120

118121

119122
# Assert that it chokes correctly on an unknown metadata type
@@ -145,9 +148,8 @@ def test_read_write_read_compare(self):
145148
metadata_obj.to_file(path_2)
146149
metadata_obj_2 = Metadata.from_file(path_2)
147150

148-
self.assertDictEqual(
149-
metadata_obj.to_dict(),
150-
metadata_obj_2.to_dict())
151+
self.assertDictEqual(metadata_to_dict(metadata_obj),
152+
metadata_to_dict(metadata_obj_2))
151153

152154
os.remove(path_2)
153155

tuf/api/metadata.py

Lines changed: 3 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import tuf.exceptions
2424

2525

26-
2726
# Types
2827
JsonDict = Dict[str, Any]
2928

@@ -56,49 +55,6 @@ def __init__(self, signed: 'Signed', signatures: list) -> None:
5655
self.signatures = signatures
5756

5857

59-
# Deserialization (factories).
60-
@classmethod
61-
def from_dict(cls, metadata: JsonDict) -> 'Metadata':
62-
"""Creates Metadata object from its JSON/dict representation.
63-
64-
Calls 'from_dict' for any complex metadata attribute represented by a
65-
class also that has a 'from_dict' factory method. (Currently this is
66-
only the signed attribute.)
67-
68-
Arguments:
69-
metadata: TUF metadata in JSON/dict representation, as e.g.
70-
returned by 'json.loads'.
71-
72-
Raises:
73-
KeyError: The metadata dict format is invalid.
74-
ValueError: The metadata has an unrecognized signed._type field.
75-
76-
Returns:
77-
A TUF Metadata object.
78-
79-
"""
80-
# Dispatch to contained metadata class on metadata _type field.
81-
_type = metadata['signed']['_type']
82-
83-
if _type == 'targets':
84-
inner_cls = Targets
85-
elif _type == 'snapshot':
86-
inner_cls = Snapshot
87-
elif _type == 'timestamp':
88-
inner_cls = Timestamp
89-
elif _type == 'root':
90-
inner_cls = Root
91-
else:
92-
raise ValueError(f'unrecognized metadata type "{_type}"')
93-
94-
# NOTE: If Signature becomes a class, we should iterate over
95-
# metadata['signatures'], call Signature.from_dict for each item, and
96-
# pass a list of Signature objects to the Metadata constructor intead.
97-
return cls(
98-
signed=inner_cls.from_dict(metadata['signed']),
99-
signatures=metadata['signatures'])
100-
101-
10258
@classmethod
10359
def from_file(
10460
cls, filename: str, deserializer: MetadataDeserializer = None,
@@ -139,14 +95,6 @@ def from_file(
13995
return deserializer.deserialize(raw_data)
14096

14197

142-
# Serialization.
143-
def to_dict(self) -> JsonDict:
144-
"""Returns the JSON-serializable dictionary representation of self. """
145-
return {
146-
'signatures': self.signatures,
147-
'signed': self.signed.to_dict()
148-
}
149-
15098
def to_file(self, filename: str, serializer: MetadataSerializer = None,
15199
storage_backend: StorageBackendInterface = None) -> None:
152100
"""Writes TUF metadata to file storage.
@@ -302,39 +250,6 @@ def __init__(
302250
self.version = version
303251

304252

305-
# Deserialization (factories).
306-
@classmethod
307-
def from_dict(cls, signed_dict: JsonDict) -> 'Signed':
308-
"""Creates Signed object from its JSON/dict representation. """
309-
310-
# Convert 'expires' TUF metadata string to a datetime object, which is
311-
# what the constructor expects and what we store. The inverse operation
312-
# is implemented in 'to_dict'.
313-
signed_dict['expires'] = tuf.formats.expiry_string_to_datetime(
314-
signed_dict['expires'])
315-
# NOTE: We write the converted 'expires' back into 'signed_dict' above
316-
# so that we can pass it to the constructor as '**signed_dict' below,
317-
# along with other fields that belong to Signed subclasses.
318-
# Any 'from_dict'(-like) conversions of fields that correspond to a
319-
# subclass should be performed in the 'from_dict' method of that
320-
# subclass and also be written back into 'signed_dict' before calling
321-
# super().from_dict.
322-
323-
# NOTE: cls might be a subclass of Signed, if 'from_dict' was called on
324-
# that subclass (see e.g. Metadata.from_dict).
325-
return cls(**signed_dict)
326-
327-
328-
def to_dict(self) -> JsonDict:
329-
"""Returns the JSON-serializable dictionary representation of self. """
330-
return {
331-
'_type': self._type,
332-
'version': self.version,
333-
'spec_version': self.spec_version,
334-
'expires': self.expires.isoformat() + 'Z'
335-
}
336-
337-
338253
# Modification.
339254
def bump_expiration(self, delta: timedelta = timedelta(days=1)) -> None:
340255
"""Increments the expires attribute by the passed timedelta. """
@@ -346,6 +261,7 @@ def bump_version(self) -> None:
346261
self.version += 1
347262

348263

264+
349265
class Root(Signed):
350266
"""A container for the signed part of root metadata.
351267
@@ -395,18 +311,6 @@ def __init__(
395311
self.roles = roles
396312

397313

398-
# Serialization.
399-
def to_dict(self) -> JsonDict:
400-
"""Returns the JSON-serializable dictionary representation of self. """
401-
json_dict = super().to_dict()
402-
json_dict.update({
403-
'consistent_snapshot': self.consistent_snapshot,
404-
'keys': self.keys,
405-
'roles': self.roles
406-
})
407-
return json_dict
408-
409-
410314
# Update key for a role.
411315
def add_key(self, role: str, keyid: str, key_metadata: JsonDict) -> None:
412316
"""Adds new key for 'role' and updates the key store. """
@@ -428,7 +332,6 @@ def remove_key(self, role: str, keyid: str) -> None:
428332

429333

430334

431-
432335
class Timestamp(Signed):
433336
"""A container for the signed part of timestamp metadata.
434337
@@ -456,16 +359,6 @@ def __init__(
456359
self.meta = meta
457360

458361

459-
# Serialization.
460-
def to_dict(self) -> JsonDict:
461-
"""Returns the JSON-serializable dictionary representation of self. """
462-
json_dict = super().to_dict()
463-
json_dict.update({
464-
'meta': self.meta
465-
})
466-
return json_dict
467-
468-
469362
# Modification.
470363
def update(self, version: int, length: int, hashes: JsonDict) -> None:
471364
"""Assigns passed info about snapshot metadata to meta dict. """
@@ -476,6 +369,7 @@ def update(self, version: int, length: int, hashes: JsonDict) -> None:
476369
}
477370

478371

372+
479373
class Snapshot(Signed):
480374
"""A container for the signed part of snapshot metadata.
481375
@@ -509,15 +403,6 @@ def __init__(
509403
# TODO: Add class for meta
510404
self.meta = meta
511405

512-
# Serialization.
513-
def to_dict(self) -> JsonDict:
514-
"""Returns the JSON-serializable dictionary representation of self. """
515-
json_dict = super().to_dict()
516-
json_dict.update({
517-
'meta': self.meta
518-
})
519-
return json_dict
520-
521406

522407
# Modification.
523408
def update(
@@ -534,6 +419,7 @@ def update(
534419
self.meta[metadata_fn]['hashes'] = hashes
535420

536421

422+
537423
class Targets(Signed):
538424
"""A container for the signed part of targets metadata.
539425
@@ -601,16 +487,6 @@ def __init__(
601487
self.delegations = delegations
602488

603489

604-
# Serialization.
605-
def to_dict(self) -> JsonDict:
606-
"""Returns the JSON-serializable dictionary representation of self. """
607-
json_dict = super().to_dict()
608-
json_dict.update({
609-
'targets': self.targets,
610-
'delegations': self.delegations,
611-
})
612-
return json_dict
613-
614490
# Modification.
615491
def update(self, filename: str, fileinfo: JsonDict) -> None:
616492
"""Assigns passed target file info to meta dict. """

tuf/api/pylintrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@ good-names=e
88
indent-string=" "
99
max-line-length=79
1010

11+
[CLASSES]
12+
exclude-protected:_type
13+
1114
[DESIGN]
1215
min-public-methods=0

tuf/api/serialization/json.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
MetadataDeserializer,
2020
SignedSerializer,
2121
SerializationError,
22-
DeserializationError)
22+
DeserializationError,
23+
util)
2324

2425

2526
class JSONDeserializer(MetadataDeserializer):
@@ -29,7 +30,7 @@ def deserialize(self, raw_data: bytes) -> Metadata:
2930
"""Deserialize utf-8 encoded JSON bytes into Metadata object. """
3031
try:
3132
_dict = json.loads(raw_data.decode("utf-8"))
32-
return Metadata.from_dict(_dict)
33+
return util.metadata_from_dict(_dict)
3334

3435
except Exception as e: # pylint: disable=broad-except
3536
six.raise_from(DeserializationError, e)
@@ -51,7 +52,7 @@ def serialize(self, metadata_obj: Metadata) -> bytes:
5152
try:
5253
indent = (None if self.compact else 1)
5354
separators=((',', ':') if self.compact else (',', ': '))
54-
return json.dumps(metadata_obj.to_dict(),
55+
return json.dumps(util.metadata_to_dict(metadata_obj),
5556
indent=indent,
5657
separators=separators,
5758
sort_keys=True).encode("utf-8")
@@ -66,7 +67,7 @@ class CanonicalJSONSerializer(SignedSerializer):
6667
def serialize(self, signed_obj: Signed) -> bytes:
6768
"""Serialize Signed object into utf-8 encoded Canonical JSON bytes. """
6869
try:
69-
signed_dict = signed_obj.to_dict()
70+
signed_dict = util.signed_to_dict(signed_obj)
7071
return encode_canonical(signed_dict).encode("utf-8")
7172

7273
except Exception as e: # pylint: disable=broad-except

0 commit comments

Comments
 (0)