Skip to content

Commit d9a928e

Browse files
author
Jussi Kukkonen
authored
Merge pull request #1395 from jku/initial-mypy-integration
Build: Initial mypy integration
2 parents f496c83 + b643e5b commit d9a928e

File tree

5 files changed

+66
-33
lines changed

5 files changed

+66
-33
lines changed

requirements-test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ coverage
1111
black
1212
isort
1313
pylint
14+
mypy
1415
bandit

setup.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@ ignore =
66
requirements-dev.txt
77
.travis.yml
88
.coveragerc
9+
10+
[mypy]
11+
warn_unused_configs = True
12+
files = tuf/api/
13+
14+
[mypy-securesystemslib.*]
15+
ignore_missing_imports = True

tox.ini

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,18 @@ commands =
3939
python -m coverage report -m
4040

4141
[testenv:lint]
42+
changedir = {toxinidir}
4243
commands =
4344
# Use different configs for new (tuf/api/*) and legacy code
4445
# TODO: configure black and isort args in pyproject.toml (see #1161)
45-
black --check --diff --line-length 80 {toxinidir}/tuf/api
46-
isort --check --diff --line-length 80 --profile black -p tuf {toxinidir}/tuf/api
47-
pylint {toxinidir}/tuf/api --rcfile={toxinidir}/tuf/api/pylintrc
46+
black --check --diff --line-length 80 tuf/api
47+
isort --check --diff --line-length 80 --profile black -p tuf tuf/api
48+
pylint -j 0 tuf/api --rcfile=tuf/api/pylintrc
4849

4950
# NOTE: Contrary to what the pylint docs suggest, ignoring full paths does
5051
# work, unfortunately each subdirectory has to be ignored explicitly.
51-
pylint {toxinidir}/tuf --ignore={toxinidir}/tuf/api,{toxinidir}/tuf/api/serialization
52+
pylint -j 0 tuf --ignore=tuf/api,tuf/api/serialization
5253

53-
bandit -r {toxinidir}/tuf
54+
mypy
55+
56+
bandit -r tuf

tuf/api/metadata.py

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
available in the class model.
1616
1717
"""
18+
import abc
1819
import tempfile
1920
from datetime import datetime, timedelta
20-
from typing import Any, Dict, List, Mapping, Optional
21+
from typing import Any, ClassVar, Dict, List, Mapping, Optional, Tuple, Type
2122

2223
from securesystemslib.keys import verify_signature
2324
from securesystemslib.signer import Signature, Signer
@@ -78,7 +79,7 @@ def from_dict(cls, metadata: Dict[str, Any]) -> "Metadata":
7879
_type = metadata["signed"]["_type"]
7980

8081
if _type == "targets":
81-
inner_cls = Targets
82+
inner_cls: Type[Signed] = Targets
8283
elif _type == "snapshot":
8384
inner_cls = Snapshot
8485
elif _type == "timestamp":
@@ -304,7 +305,7 @@ def verify(
304305
)
305306

306307

307-
class Signed:
308+
class Signed(metaclass=abc.ABCMeta):
308309
"""A base class for the signed part of TUF metadata.
309310
310311
Objects with base class Signed are usually included in a Metadata object
@@ -321,7 +322,7 @@ class Signed:
321322
"""
322323

323324
# Signed implementations are expected to override this
324-
_signed_type = None
325+
_signed_type: ClassVar[str] = "signed"
325326

326327
# _type and type are identical: 1st replicates file format, 2nd passes lint
327328
@property
@@ -351,8 +352,21 @@ def __init__(
351352
self.version = version
352353
self.unrecognized_fields: Mapping[str, Any] = unrecognized_fields or {}
353354

355+
@abc.abstractmethod
356+
def to_dict(self) -> Dict[str, Any]:
357+
"""Serialization helper that returns dict representation of self"""
358+
raise NotImplementedError
359+
360+
@classmethod
361+
@abc.abstractmethod
362+
def from_dict(cls, signed_dict: Dict[str, Any]) -> "Signed":
363+
"""Deserialization helper, creates object from dict representation"""
364+
raise NotImplementedError
365+
354366
@classmethod
355-
def _common_fields_from_dict(cls, signed_dict: Dict[str, Any]) -> List[Any]:
367+
def _common_fields_from_dict(
368+
cls, signed_dict: Dict[str, Any]
369+
) -> Tuple[int, str, datetime]:
356370
"""Returns common fields of 'Signed' instances from the passed dict
357371
representation, and returns an ordered list to be passed as leading
358372
positional arguments to a subclass constructor.
@@ -371,7 +385,7 @@ def _common_fields_from_dict(cls, signed_dict: Dict[str, Any]) -> List[Any]:
371385
# what the constructor expects and what we store. The inverse operation
372386
# is implemented in '_common_fields_to_dict'.
373387
expires = formats.expiry_string_to_datetime(expires_str)
374-
return [version, spec_version, expires]
388+
return version, spec_version, expires
375389

376390
def _common_fields_to_dict(self) -> Dict[str, Any]:
377391
"""Returns dict representation of common fields of 'Signed' instances.
@@ -550,20 +564,20 @@ def __init__(
550564
self.roles = roles
551565

552566
@classmethod
553-
def from_dict(cls, root_dict: Dict[str, Any]) -> "Root":
567+
def from_dict(cls, signed_dict: Dict[str, Any]) -> "Root":
554568
"""Creates Root object from its dict representation."""
555-
common_args = cls._common_fields_from_dict(root_dict)
556-
consistent_snapshot = root_dict.pop("consistent_snapshot", None)
557-
keys = root_dict.pop("keys")
558-
roles = root_dict.pop("roles")
569+
common_args = cls._common_fields_from_dict(signed_dict)
570+
consistent_snapshot = signed_dict.pop("consistent_snapshot", None)
571+
keys = signed_dict.pop("keys")
572+
roles = signed_dict.pop("roles")
559573

560574
for keyid, key_dict in keys.items():
561575
keys[keyid] = Key.from_dict(key_dict)
562576
for role_name, role_dict in roles.items():
563577
roles[role_name] = Role.from_dict(role_dict)
564578

565-
# All fields left in the root_dict are unrecognized.
566-
return cls(*common_args, keys, roles, consistent_snapshot, root_dict)
579+
# All fields left in the signed_dict are unrecognized.
580+
return cls(*common_args, keys, roles, consistent_snapshot, signed_dict)
567581

568582
def to_dict(self) -> Dict[str, Any]:
569583
"""Returns the dict representation of self."""
@@ -646,7 +660,10 @@ def from_dict(cls, meta_dict: Dict[str, Any]) -> "MetaFile":
646660

647661
def to_dict(self) -> Dict[str, Any]:
648662
"""Returns the dictionary representation of self."""
649-
res_dict = {"version": self.version, **self.unrecognized_fields}
663+
res_dict: Dict[str, Any] = {
664+
"version": self.version,
665+
**self.unrecognized_fields,
666+
}
650667

651668
if self.length is not None:
652669
res_dict["length"] = self.length
@@ -683,13 +700,13 @@ def __init__(
683700
self.meta = meta
684701

685702
@classmethod
686-
def from_dict(cls, timestamp_dict: Dict[str, Any]) -> "Timestamp":
703+
def from_dict(cls, signed_dict: Dict[str, Any]) -> "Timestamp":
687704
"""Creates Timestamp object from its dict representation."""
688-
common_args = cls._common_fields_from_dict(timestamp_dict)
689-
meta_dict = timestamp_dict.pop("meta")
705+
common_args = cls._common_fields_from_dict(signed_dict)
706+
meta_dict = signed_dict.pop("meta")
690707
meta = {"snapshot.json": MetaFile.from_dict(meta_dict["snapshot.json"])}
691708
# All fields left in the timestamp_dict are unrecognized.
692-
return cls(*common_args, meta, timestamp_dict)
709+
return cls(*common_args, meta, signed_dict)
693710

694711
def to_dict(self) -> Dict[str, Any]:
695712
"""Returns the dict representation of self."""
@@ -733,15 +750,15 @@ def __init__(
733750
self.meta = meta
734751

735752
@classmethod
736-
def from_dict(cls, snapshot_dict: Dict[str, Any]) -> "Snapshot":
753+
def from_dict(cls, signed_dict: Dict[str, Any]) -> "Snapshot":
737754
"""Creates Snapshot object from its dict representation."""
738-
common_args = cls._common_fields_from_dict(snapshot_dict)
739-
meta_dicts = snapshot_dict.pop("meta")
755+
common_args = cls._common_fields_from_dict(signed_dict)
756+
meta_dicts = signed_dict.pop("meta")
740757
meta = {}
741758
for meta_path, meta_dict in meta_dicts.items():
742759
meta[meta_path] = MetaFile.from_dict(meta_dict)
743760
# All fields left in the snapshot_dict are unrecognized.
744-
return cls(*common_args, meta, snapshot_dict)
761+
return cls(*common_args, meta, signed_dict)
745762

746763
def to_dict(self) -> Dict[str, Any]:
747764
"""Returns the dict representation of self."""
@@ -801,7 +818,7 @@ def __init__(
801818
self.path_hash_prefixes = path_hash_prefixes
802819

803820
@classmethod
804-
def from_dict(cls, role_dict: Mapping[str, Any]) -> "Role":
821+
def from_dict(cls, role_dict: Dict[str, Any]) -> "DelegatedRole":
805822
"""Creates DelegatedRole object from its dict representation."""
806823
name = role_dict.pop("name")
807824
keyids = role_dict.pop("keyids")
@@ -971,12 +988,12 @@ def __init__(
971988
self.delegations = delegations
972989

973990
@classmethod
974-
def from_dict(cls, targets_dict: Dict[str, Any]) -> "Targets":
991+
def from_dict(cls, signed_dict: Dict[str, Any]) -> "Targets":
975992
"""Creates Targets object from its dict representation."""
976-
common_args = cls._common_fields_from_dict(targets_dict)
977-
targets = targets_dict.pop("targets")
993+
common_args = cls._common_fields_from_dict(signed_dict)
994+
targets = signed_dict.pop("targets")
978995
try:
979-
delegations_dict = targets_dict.pop("delegations")
996+
delegations_dict = signed_dict.pop("delegations")
980997
except KeyError:
981998
delegations = None
982999
else:
@@ -985,7 +1002,7 @@ def from_dict(cls, targets_dict: Dict[str, Any]) -> "Targets":
9851002
for target_path, target_info in targets.items():
9861003
res_targets[target_path] = TargetFile.from_dict(target_info)
9871004
# All fields left in the targets_dict are unrecognized.
988-
return cls(*common_args, res_targets, delegations, targets_dict)
1005+
return cls(*common_args, res_targets, delegations, signed_dict)
9891006

9901007
def to_dict(self) -> Dict[str, Any]:
9911008
"""Returns the dict representation of self."""

tuf/api/serialization/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
1616
"""
1717
import abc
18+
from typing import TYPE_CHECKING
19+
20+
if TYPE_CHECKING:
21+
# pylint: disable=cyclic-import
22+
from tuf.api.metadata import Metadata, Signed
1823

1924

2025
# TODO: Should these be in tuf.exceptions or inherit from tuf.exceptions.Error?

0 commit comments

Comments
 (0)