Skip to content

Commit b786bc0

Browse files
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 <[email protected]>
1 parent 19d96f3 commit b786bc0

File tree

5 files changed

+166
-148
lines changed

5 files changed

+166
-148
lines changed

tests/test_api.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def setUpModule():
4141
from tuf.api.serialization import (
4242
DeserializationError
4343
)
44+
from tuf.api.serialization.util import metadata_to_dict
4445
from tuf.api.serialization.json import (
4546
JSONSerializer,
4647
JSONDeserializer,
@@ -120,9 +121,9 @@ def test_generic_read(self):
120121
self.assertTrue(
121122
isinstance(metadata_obj2.signed, inner_metadata_cls))
122123

123-
# ... and return the same object (compared by dict representation)
124-
self.assertDictEqual(
125-
metadata_obj.to_dict(), metadata_obj2.to_dict())
124+
# ... and are equal (compared by dict representation)
125+
self.assertDictEqual(metadata_to_dict(metadata_obj),
126+
metadata_to_dict(metadata_obj2))
126127

127128

128129
# Assert that it chokes correctly on an unknown metadata type
@@ -154,9 +155,8 @@ def test_read_write_read_compare(self):
154155
metadata_obj.to_file(path_2)
155156
metadata_obj_2 = Metadata.from_file(path_2)
156157

157-
self.assertDictEqual(
158-
metadata_obj.to_dict(),
159-
metadata_obj_2.to_dict())
158+
self.assertDictEqual(metadata_to_dict(metadata_obj),
159+
metadata_to_dict(metadata_obj_2))
160160

161161
os.remove(path_2)
162162

tuf/api/metadata.py

Lines changed: 3 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from datetime import datetime, timedelta
1010
from typing import Any, Dict, Optional
1111

12-
import json
1312
import tempfile
1413

1514
from securesystemslib.util import persist_temp_file
@@ -24,7 +23,6 @@
2423
import tuf.exceptions
2524

2625

27-
2826
# Types
2927
JsonDict = Dict[str, Any]
3028

@@ -57,49 +55,6 @@ def __init__(self, signed: 'Signed', signatures: list) -> None:
5755
self.signatures = signatures
5856

5957

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

14297

143-
# Serialization.
144-
def to_dict(self) -> JsonDict:
145-
"""Returns the JSON-serializable dictionary representation of self. """
146-
return {
147-
'signatures': self.signatures,
148-
'signed': self.signed.to_dict()
149-
}
150-
151-
152-
def to_json(self, compact: bool = False) -> None:
153-
"""Returns the optionally compacted JSON representation of self. """
154-
return json.dumps(
155-
self.to_dict(),
156-
indent=(None if compact else 1),
157-
separators=((',', ':') if compact else (',', ': ')),
158-
sort_keys=True)
159-
160-
16198
def to_file(self, filename: str, serializer: MetadataSerializer = None,
16299
storage_backend: StorageBackendInterface = None) -> None:
163100
"""Writes TUF metadata to file storage.
@@ -313,39 +250,6 @@ def __init__(
313250
self.version = version
314251

315252

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

359263

264+
360265
class Root(Signed):
361266
"""A container for the signed part of root metadata.
362267
@@ -406,18 +311,6 @@ def __init__(
406311
self.roles = roles
407312

408313

409-
# Serialization.
410-
def to_dict(self) -> JsonDict:
411-
"""Returns the JSON-serializable dictionary representation of self. """
412-
json_dict = super().to_dict()
413-
json_dict.update({
414-
'consistent_snapshot': self.consistent_snapshot,
415-
'keys': self.keys,
416-
'roles': self.roles
417-
})
418-
return json_dict
419-
420-
421314
# Update key for a role.
422315
def add_key(self, role: str, keyid: str, key_metadata: JsonDict) -> None:
423316
"""Adds new key for 'role' and updates the key store. """
@@ -439,7 +332,6 @@ def remove_key(self, role: str, keyid: str) -> None:
439332

440333

441334

442-
443335
class Timestamp(Signed):
444336
"""A container for the signed part of timestamp metadata.
445337
@@ -467,16 +359,6 @@ def __init__(
467359
self.meta = meta
468360

469361

470-
# Serialization.
471-
def to_dict(self) -> JsonDict:
472-
"""Returns the JSON-serializable dictionary representation of self. """
473-
json_dict = super().to_dict()
474-
json_dict.update({
475-
'meta': self.meta
476-
})
477-
return json_dict
478-
479-
480362
# Modification.
481363
def update(self, version: int, length: int, hashes: JsonDict) -> None:
482364
"""Assigns passed info about snapshot metadata to meta dict. """
@@ -487,6 +369,7 @@ def update(self, version: int, length: int, hashes: JsonDict) -> None:
487369
}
488370

489371

372+
490373
class Snapshot(Signed):
491374
"""A container for the signed part of snapshot metadata.
492375
@@ -520,15 +403,6 @@ def __init__(
520403
# TODO: Add class for meta
521404
self.meta = meta
522405

523-
# Serialization.
524-
def to_dict(self) -> JsonDict:
525-
"""Returns the JSON-serializable dictionary representation of self. """
526-
json_dict = super().to_dict()
527-
json_dict.update({
528-
'meta': self.meta
529-
})
530-
return json_dict
531-
532406

533407
# Modification.
534408
def update(
@@ -545,6 +419,7 @@ def update(
545419
self.meta[metadata_fn]['hashes'] = hashes
546420

547421

422+
548423
class Targets(Signed):
549424
"""A container for the signed part of targets metadata.
550425
@@ -612,16 +487,6 @@ def __init__(
612487
self.delegations = delegations
613488

614489

615-
# Serialization.
616-
def to_dict(self) -> JsonDict:
617-
"""Returns the JSON-serializable dictionary representation of self. """
618-
json_dict = super().to_dict()
619-
json_dict.update({
620-
'targets': self.targets,
621-
'delegations': self.delegations,
622-
})
623-
return json_dict
624-
625490
# Modification.
626491
def update(self, filename: str, fileinfo: JsonDict) -> None:
627492
"""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)