Skip to content

Commit 6aa5727

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 53c8a24 commit 6aa5727

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, Mapping, Optional
7272

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

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

157157
# Helper properties for top level metadata
158158
@property
159-
def root(self) -> Metadata:
159+
def root(self) -> Metadata[Root]:
160160
"""Current root Metadata"""
161161
return self._trusted_set["root"]
162162

163163
@property
164-
def timestamp(self) -> Optional[Metadata]:
164+
def timestamp(self) -> Optional[Metadata[Timestamp]]:
165165
"""Current timestamp Metadata or None"""
166166
return self._trusted_set.get("timestamp")
167167

168168
@property
169-
def snapshot(self) -> Optional[Metadata]:
169+
def snapshot(self) -> Optional[Metadata[Snapshot]]:
170170
"""Current snapshot Metadata or None"""
171171
return self._trusted_set.get("snapshot")
172172

173173
@property
174-
def targets(self) -> Optional[Metadata]:
174+
def targets(self) -> Optional[Metadata[Targets]]:
175175
"""Current targets Metadata or None"""
176176
return self._trusted_set.get("targets")
177177

@@ -196,7 +196,7 @@ def update_root(self, data: bytes) -> None:
196196
logger.debug("Updating root")
197197

198198
try:
199-
new_root = Metadata.from_bytes(data)
199+
new_root = Metadata[Root].from_bytes(data)
200200
except DeserializationError as e:
201201
raise exceptions.RepositoryError("Failed to load root") from e
202202

@@ -261,7 +261,7 @@ def update_timestamp(self, data: bytes) -> None:
261261
raise RuntimeError("Cannot update timestamp after snapshot")
262262

263263
try:
264-
new_timestamp = Metadata.from_bytes(data)
264+
new_timestamp = Metadata[Timestamp].from_bytes(data)
265265
except DeserializationError as e:
266266
raise exceptions.RepositoryError("Failed to load timestamp") from e
267267

@@ -330,7 +330,7 @@ def update_snapshot(self, data: bytes) -> None:
330330
) from e
331331

332332
try:
333-
new_snapshot = Metadata.from_bytes(data)
333+
new_snapshot = Metadata[Snapshot].from_bytes(data)
334334
except DeserializationError as e:
335335
raise exceptions.RepositoryError("Failed to load snapshot") from e
336336

@@ -429,7 +429,7 @@ def update_delegated_targets(
429429
) from e
430430

431431
try:
432-
new_delegate = Metadata.from_bytes(data)
432+
new_delegate = Metadata[Targets].from_bytes(data)
433433
except DeserializationError as e:
434434
raise exceptions.RepositoryError("Failed to load snapshot") from e
435435

0 commit comments

Comments
 (0)