Skip to content

Commit 3028fb6

Browse files
author
Jussi Kukkonen
authored
Merge pull request #1489 from sechkova/enable-mypy-ngclient
Enable mypy for ngclient
2 parents 2dd88d9 + 6ada96c commit 3028fb6

File tree

7 files changed

+90
-56
lines changed

7 files changed

+90
-56
lines changed

requirements-test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ isort
1313
pylint
1414
mypy
1515
bandit
16+
types-requests

setup.cfg

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ warn_unreachable = True
1515
strict_equality = True
1616
disallow_untyped_defs = True
1717
disallow_untyped_calls = True
18-
files = tuf/api/, tuf/exceptions.py
18+
files =
19+
tuf/api/,
20+
tuf/ngclient,
21+
tuf/exceptions.py
1922

2023
[mypy-securesystemslib.*]
2124
ignore_missing_imports = True
25+
26+
[mypy-urllib3.*]
27+
ignore_missing_imports = True

tuf/api/metadata.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
from collections import OrderedDict
2525
from datetime import datetime, timedelta
2626
from typing import (
27+
IO,
2728
Any,
28-
BinaryIO,
2929
ClassVar,
3030
Dict,
3131
Generic,
@@ -98,7 +98,7 @@ def __init__(self, signed: T, signatures: "OrderedDict[str, Signature]"):
9898
self.signatures = signatures
9999

100100
@classmethod
101-
def from_dict(cls, metadata: Dict[str, Any]) -> "Metadata":
101+
def from_dict(cls, metadata: Dict[str, Any]) -> "Metadata[T]":
102102
"""Creates Metadata object from its dict representation.
103103
104104
Arguments:
@@ -753,7 +753,7 @@ class BaseFile:
753753

754754
@staticmethod
755755
def _verify_hashes(
756-
data: Union[bytes, BinaryIO], expected_hashes: Dict[str, str]
756+
data: Union[bytes, IO[bytes]], expected_hashes: Dict[str, str]
757757
) -> None:
758758
"""Verifies that the hash of 'data' matches 'expected_hashes'"""
759759
is_bytes = isinstance(data, bytes)
@@ -782,7 +782,7 @@ def _verify_hashes(
782782

783783
@staticmethod
784784
def _verify_length(
785-
data: Union[bytes, BinaryIO], expected_length: int
785+
data: Union[bytes, IO[bytes]], expected_length: int
786786
) -> None:
787787
"""Verifies that the length of 'data' matches 'expected_length'"""
788788
if isinstance(data, bytes):
@@ -867,7 +867,7 @@ def to_dict(self) -> Dict[str, Any]:
867867

868868
return res_dict
869869

870-
def verify_length_and_hashes(self, data: Union[bytes, BinaryIO]) -> None:
870+
def verify_length_and_hashes(self, data: Union[bytes, IO[bytes]]) -> None:
871871
"""Verifies that the length and hashes of "data" match expected values.
872872
873873
Args:
@@ -1182,7 +1182,7 @@ def to_dict(self) -> Dict[str, Any]:
11821182
**self.unrecognized_fields,
11831183
}
11841184

1185-
def verify_length_and_hashes(self, data: Union[bytes, BinaryIO]) -> None:
1185+
def verify_length_and_hashes(self, data: Union[bytes, IO[bytes]]) -> None:
11861186
"""Verifies that length and hashes of "data" match expected values.
11871187
11881188
Args:

tuf/exceptions.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
from urllib import parse
2626

27-
from typing import Any, Dict
27+
from typing import Any, Dict, Optional
2828

2929
import logging
3030
logger = logging.getLogger(__name__)
@@ -206,16 +206,19 @@ def __repr__(self) -> str:
206206
class SlowRetrievalError(DownloadError):
207207
""""Indicate that downloading a file took an unreasonably long time."""
208208

209-
def __init__(self, average_download_speed: int):
209+
def __init__(self, average_download_speed: Optional[int] = None):
210210
super(SlowRetrievalError, self).__init__()
211211

212212
self.__average_download_speed = average_download_speed #bytes/second
213213

214214
def __str__(self) -> str:
215-
return (
216-
'Download was too slow. Average speed: ' +
215+
msg = 'Download was too slow.'
216+
if self.__average_download_speed is not None:
217+
msg = ('Download was too slow. Average speed: ' +
217218
repr(self.__average_download_speed) + ' bytes per second.')
218219

220+
return msg
221+
219222
def __repr__(self) -> str:
220223
return self.__class__.__name__ + ' : ' + str(self)
221224

tuf/ngclient/_internal/requests_fetcher.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import logging
99
import time
10-
from typing import Iterator, Optional
10+
from typing import Dict, Iterator, Optional
1111
from urllib import parse
1212

1313
# Imports
@@ -31,7 +31,7 @@ class RequestsFetcher(FetcherInterface):
3131
session per scheme+hostname combination.
3232
"""
3333

34-
def __init__(self):
34+
def __init__(self) -> None:
3535
# http://docs.python-requests.org/en/master/user/advanced/#session-objects:
3636
#
3737
# "The Session object allows you to persist certain parameters across
@@ -46,7 +46,7 @@ def __init__(self):
4646
# improve efficiency, but avoiding sharing state between different
4747
# hosts-scheme combinations to minimize subtle security issues.
4848
# Some cookies may not be HTTP-safe.
49-
self._sessions = {}
49+
self._sessions: Dict[str, requests.Session] = {}
5050

5151
# Default settings
5252
self.socket_timeout: int = 4 # seconds
@@ -141,12 +141,12 @@ def _chunks(
141141
)
142142

143143
except urllib3.exceptions.ReadTimeoutError as e:
144-
raise exceptions.SlowRetrievalError(str(e))
144+
raise exceptions.SlowRetrievalError from e
145145

146146
finally:
147147
response.close()
148148

149-
def _get_session(self, url):
149+
def _get_session(self, url: str) -> requests.Session:
150150
"""Returns a different customized requests.Session per schema+hostname
151151
combination.
152152
"""

tuf/ngclient/_internal/trusted_metadata_set.py

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
from typing import Dict, Iterator, Optional
6767

6868
from tuf import exceptions
69-
from tuf.api.metadata import Metadata
69+
from tuf.api.metadata import Metadata, Root, Snapshot, Targets, Timestamp
7070
from tuf.api.serialization import DeserializationError
7171

7272
logger = logging.getLogger(__name__)
@@ -92,13 +92,13 @@ def __init__(self, root_data: bytes):
9292
RepositoryError: Metadata failed to load or verify. The actual
9393
error type and content will contain more details.
9494
"""
95-
self._trusted_set = {} # type: Dict[str: Metadata]
95+
self._trusted_set: Dict[str, Metadata] = {}
9696
self.reference_time = datetime.utcnow()
9797

9898
# Load and validate the local root metadata. Valid initial trusted root
9999
# metadata is required
100100
logger.debug("Updating initial trusted root")
101-
self.update_root(root_data)
101+
self._load_trusted_root(root_data)
102102

103103
def __getitem__(self, role: str) -> Metadata:
104104
"""Returns current Metadata for 'role'"""
@@ -114,27 +114,27 @@ def __iter__(self) -> Iterator[Metadata]:
114114

115115
# Helper properties for top level metadata
116116
@property
117-
def root(self) -> Optional[Metadata]:
118-
"""Current root Metadata or None"""
119-
return self._trusted_set.get("root")
117+
def root(self) -> Metadata[Root]:
118+
"""Current root Metadata"""
119+
return self._trusted_set["root"]
120120

121121
@property
122-
def timestamp(self) -> Optional[Metadata]:
122+
def timestamp(self) -> Optional[Metadata[Timestamp]]:
123123
"""Current timestamp Metadata or None"""
124124
return self._trusted_set.get("timestamp")
125125

126126
@property
127-
def snapshot(self) -> Optional[Metadata]:
127+
def snapshot(self) -> Optional[Metadata[Snapshot]]:
128128
"""Current snapshot Metadata or None"""
129129
return self._trusted_set.get("snapshot")
130130

131131
@property
132-
def targets(self) -> Optional[Metadata]:
132+
def targets(self) -> Optional[Metadata[Targets]]:
133133
"""Current targets Metadata or None"""
134134
return self._trusted_set.get("targets")
135135

136136
# Methods for updating metadata
137-
def update_root(self, data: bytes):
137+
def update_root(self, data: bytes) -> None:
138138
"""Verifies and loads 'data' as new root metadata.
139139
140140
Note that an expired intermediate root is considered valid: expiry is
@@ -152,7 +152,7 @@ def update_root(self, data: bytes):
152152
logger.debug("Updating root")
153153

154154
try:
155-
new_root = Metadata.from_bytes(data)
155+
new_root = Metadata[Root].from_bytes(data)
156156
except DeserializationError as e:
157157
raise exceptions.RepositoryError("Failed to load root") from e
158158

@@ -161,21 +161,21 @@ def update_root(self, data: bytes):
161161
f"Expected 'root', got '{new_root.signed.type}'"
162162
)
163163

164-
if self.root is not None:
165-
# We are not loading initial trusted root: verify the new one
166-
self.root.verify_delegate("root", new_root)
164+
# Verify that new root is signed by trusted root
165+
self.root.verify_delegate("root", new_root)
167166

168-
if new_root.signed.version != self.root.signed.version + 1:
169-
raise exceptions.ReplayedMetadataError(
170-
"root", new_root.signed.version, self.root.signed.version
171-
)
167+
if new_root.signed.version != self.root.signed.version + 1:
168+
raise exceptions.ReplayedMetadataError(
169+
"root", new_root.signed.version, self.root.signed.version
170+
)
172171

172+
# Verify that new root is signed by itself
173173
new_root.verify_delegate("root", new_root)
174174

175175
self._trusted_set["root"] = new_root
176176
logger.debug("Updated root")
177177

178-
def update_timestamp(self, data: bytes):
178+
def update_timestamp(self, data: bytes) -> None:
179179
"""Verifies and loads 'data' as new timestamp metadata.
180180
181181
Note that an expired intermediate timestamp is considered valid so it
@@ -199,7 +199,7 @@ def update_timestamp(self, data: bytes):
199199
# timestamp/snapshot can not yet be loaded at this point
200200

201201
try:
202-
new_timestamp = Metadata.from_bytes(data)
202+
new_timestamp = Metadata[Timestamp].from_bytes(data)
203203
except DeserializationError as e:
204204
raise exceptions.RepositoryError("Failed to load timestamp") from e
205205

@@ -237,7 +237,7 @@ def update_timestamp(self, data: bytes):
237237
self._trusted_set["timestamp"] = new_timestamp
238238
logger.debug("Updated timestamp")
239239

240-
def update_snapshot(self, data: bytes):
240+
def update_snapshot(self, data: bytes) -> None:
241241
"""Verifies and loads 'data' as new snapshot metadata.
242242
243243
Note that intermediate snapshot is considered valid even if it is
@@ -276,7 +276,7 @@ def update_snapshot(self, data: bytes):
276276
) from e
277277

278278
try:
279-
new_snapshot = Metadata.from_bytes(data)
279+
new_snapshot = Metadata[Snapshot].from_bytes(data)
280280
except DeserializationError as e:
281281
raise exceptions.RepositoryError("Failed to load snapshot") from e
282282

@@ -314,7 +314,11 @@ def update_snapshot(self, data: bytes):
314314
self._trusted_set["snapshot"] = new_snapshot
315315
logger.debug("Updated snapshot")
316316

317-
def _check_final_snapshot(self):
317+
def _check_final_snapshot(self) -> None:
318+
"""Check snapshot expiry and version before targets is updated"""
319+
320+
assert self.snapshot is not None # nosec
321+
assert self.timestamp is not None # nosec
318322
if self.snapshot.signed.is_expired(self.reference_time):
319323
raise exceptions.ExpiredMetadataError("snapshot.json is expired")
320324

@@ -328,7 +332,7 @@ def _check_final_snapshot(self):
328332
f"got {self.snapshot.signed.version}"
329333
)
330334

331-
def update_targets(self, data: bytes):
335+
def update_targets(self, data: bytes) -> None:
332336
"""Verifies and loads 'data' as new top-level targets metadata.
333337
334338
Args:
@@ -342,7 +346,7 @@ def update_targets(self, data: bytes):
342346

343347
def update_delegated_targets(
344348
self, data: bytes, role_name: str, delegator_name: str
345-
):
349+
) -> None:
346350
"""Verifies and loads 'data' as new metadata for target 'role_name'.
347351
348352
Args:
@@ -383,7 +387,7 @@ def update_delegated_targets(
383387
) from e
384388

385389
try:
386-
new_delegate = Metadata.from_bytes(data)
390+
new_delegate = Metadata[Targets].from_bytes(data)
387391
except DeserializationError as e:
388392
raise exceptions.RepositoryError("Failed to load snapshot") from e
389393

@@ -405,3 +409,24 @@ def update_delegated_targets(
405409

406410
self._trusted_set[role_name] = new_delegate
407411
logger.debug("Updated %s delegated by %s", role_name, delegator_name)
412+
413+
def _load_trusted_root(self, data: bytes) -> None:
414+
"""Verifies and loads 'data' as trusted root metadata.
415+
416+
Note that an expired initial root is considered valid: expiry is
417+
only checked for the final root in update_timestamp().
418+
"""
419+
try:
420+
new_root = Metadata[Root].from_bytes(data)
421+
except DeserializationError as e:
422+
raise exceptions.RepositoryError("Failed to load root") from e
423+
424+
if new_root.signed.type != "root":
425+
raise exceptions.RepositoryError(
426+
f"Expected 'root', got '{new_root.signed.type}'"
427+
)
428+
429+
new_root.verify_delegate("root", new_root)
430+
431+
self._trusted_set["root"] = new_root
432+
logger.debug("Loaded trusted root")

0 commit comments

Comments
 (0)