Skip to content

Commit e3bb771

Browse files
sechkovaJussi Kukkonen
andcommitted
Make Metadata class Generic
'Generic' is used to help type checkers and IDEs correctly understand the 'signed' attribute type which is discovered runtime. This is entirely a typing feature and has no runtime effect on the existing code. Co-authored-by: Jussi Kukkonen <[email protected]> Signed-off-by: Teodora Sechkova <[email protected]>
1 parent 29a4d41 commit e3bb771

File tree

2 files changed

+19
-15
lines changed

2 files changed

+19
-15
lines changed

tuf/api/metadata.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@
2626
Any,
2727
ClassVar,
2828
Dict,
29+
Generic,
2930
List,
3031
Mapping,
3132
Optional,
3233
Tuple,
3334
Type,
35+
TypeVar,
3436
Union,
37+
cast,
3538
)
3639

3740
from securesystemslib import exceptions as sslib_exceptions
@@ -56,8 +59,10 @@
5659
# files to have the same major version (the first number) as ours.
5760
SPECIFICATION_VERSION = ["1", "0", "19"]
5861

62+
T = TypeVar("T", "Root", "Timestamp", "Snapshot", "Targets")
5963

60-
class Metadata:
64+
65+
class Metadata(Generic[T]):
6166
"""A container for signed TUF metadata.
6267
6368
Provides methods to convert to and from dictionary, read and write to and
@@ -70,10 +75,8 @@ class Metadata:
7075
signing the canonical serialized representation of 'signed'.
7176
"""
7277

73-
def __init__(
74-
self, signed: "Signed", signatures: "OrderedDict[str, Signature]"
75-
):
76-
self.signed = signed
78+
def __init__(self, signed: T, signatures: "OrderedDict[str, Signature]"):
79+
self.signed: T = signed
7780
self.signatures = signatures
7881

7982
@classmethod
@@ -119,7 +122,8 @@ def from_dict(cls, metadata: Dict[str, Any]) -> "Metadata":
119122
signatures[sig.keyid] = sig
120123

121124
return cls(
122-
signed=inner_cls.from_dict(metadata.pop("signed")),
125+
# 'cast' is used only to help typing, it has no runtime effect
126+
signed=cast(T, inner_cls.from_dict(metadata.pop("signed"))),
123127
signatures=signatures,
124128
)
125129

tuf/ngclient/_internal/trusted_metadata_set.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
from typing import Dict, Iterator, Optional
7272

7373
from tuf import exceptions
74-
from tuf.api.metadata import Metadata
74+
from tuf.api.metadata import Metadata, Root, Snapshot, Targets, Timestamp
7575
from tuf.api.serialization import DeserializationError
7676

7777
logger = logging.getLogger(__name__)
@@ -120,22 +120,22 @@ def __iter__(self) -> Iterator[Metadata]:
120120

121121
# Helper properties for top level metadata
122122
@property
123-
def root(self) -> Metadata:
123+
def root(self) -> Metadata[Root]:
124124
"""Current root Metadata"""
125125
return self._trusted_set["root"]
126126

127127
@property
128-
def timestamp(self) -> Optional[Metadata]:
128+
def timestamp(self) -> Optional[Metadata[Timestamp]]:
129129
"""Current timestamp Metadata or None"""
130130
return self._trusted_set.get("timestamp")
131131

132132
@property
133-
def snapshot(self) -> Optional[Metadata]:
133+
def snapshot(self) -> Optional[Metadata[Snapshot]]:
134134
"""Current snapshot Metadata or None"""
135135
return self._trusted_set.get("snapshot")
136136

137137
@property
138-
def targets(self) -> Optional[Metadata]:
138+
def targets(self) -> Optional[Metadata[Targets]]:
139139
"""Current targets Metadata or None"""
140140
return self._trusted_set.get("targets")
141141

@@ -160,7 +160,7 @@ def update_root(self, data: bytes) -> None:
160160
logger.debug("Updating root")
161161

162162
try:
163-
new_root = Metadata.from_bytes(data)
163+
new_root = Metadata[Root].from_bytes(data)
164164
except DeserializationError as e:
165165
raise exceptions.RepositoryError("Failed to load root") from e
166166

@@ -219,7 +219,7 @@ def update_timestamp(self, data: bytes) -> None:
219219
raise RuntimeError("Cannot update timestamp after snapshot")
220220

221221
try:
222-
new_timestamp = Metadata.from_bytes(data)
222+
new_timestamp = Metadata[Timestamp].from_bytes(data)
223223
except DeserializationError as e:
224224
raise exceptions.RepositoryError("Failed to load timestamp") from e
225225

@@ -285,7 +285,7 @@ def update_snapshot(self, data: bytes) -> None:
285285
) from e
286286

287287
try:
288-
new_snapshot = Metadata.from_bytes(data)
288+
new_snapshot = Metadata[Snapshot].from_bytes(data)
289289
except DeserializationError as e:
290290
raise exceptions.RepositoryError("Failed to load snapshot") from e
291291

@@ -381,7 +381,7 @@ def update_delegated_targets(
381381
) from e
382382

383383
try:
384-
new_delegate = Metadata.from_bytes(data)
384+
new_delegate = Metadata[Targets].from_bytes(data)
385385
except DeserializationError as e:
386386
raise exceptions.RepositoryError("Failed to load snapshot") from e
387387

0 commit comments

Comments
 (0)