From 0f59f4b749ea15ca3264350452b2dfac19328926 Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Wed, 19 Jan 2022 17:11:18 +0200 Subject: [PATCH 1/2] Drop support for python version 3.6 Python version 3.6 was supported until December 23-rd 2021 meaning its end of life has expired before more than 20 days. Dropping support for python version 3.6 will allow us to remove OrderedDicts. After a quick check I saw that Warehouse target python version 3.8.2: - their docker file: https://github.com/pypa/warehouse/blob/main/Dockerfile#L47 - https://github.com/pypa/warehouse/blob/main/.python-version - last pr updating pr version: https://github.com/pypa/warehouse/pull/7828 Pip supports python version 3.7+ as well. They dropped python 3.6 a couple of months ago: https://github.com/pypa/pip/pull/10641 This means it shouldn't cause headache to our users if we drop python version 3.6 too. Signed-off-by: Martin Vrachev --- .github/workflows/ci.yml | 2 +- requirements.txt | 2 +- setup.cfg | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f46283d76..5cc14fd116 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: # Run regular TUF tests on each OS/Python combination, plus special tests # (sslib master) and linters on Linux/Python3.x only. matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10"] os: [ubuntu-latest, macos-latest, windows-latest] toxenv: [py] include: diff --git a/requirements.txt b/requirements.txt index 84e209f3ed..8f7cc7f09b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,7 +26,7 @@ # 1. Use this script to create a pinned requirements file for each Python # version # ``` -# for v in 3.6 3.7 3.8 3.9; do +# for v in 3.7 3.8 3.9; do # mkvirtualenv tuf-env-${v} -p python${v}; # python3 -m pip install pip-tools; # pip-compile --no-header -o requirements-${v}.txt requirements.txt; diff --git a/setup.cfg b/setup.cfg index 8b715a7682..0d4202b679 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,7 +21,6 @@ classifiers = Operating System :: MacOS :: MacOS X Operating System :: Microsoft :: Windows Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 @@ -37,7 +36,7 @@ packages = find: scripts = tuf/scripts/repo.py tuf/scripts/client.py -python_requires = ~=3.6 +python_requires = ~=3.7 install_requires = requests>=2.19.1 securesystemslib>=0.20.0 From e3b267e2e0799673ac99ccfccd3631628013201c Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Wed, 19 Jan 2022 18:19:56 +0200 Subject: [PATCH 2/2] Remove OrderedDict in favor of python3.7+ dict After we drop support for python3.6 we can relly that dictionaries preserve the insertion order: https://docs.python.org/3.7/whatsnew/3.7.html This means we can replace the usage of OrderedDict with a standard dictionaries. Something we have to keep in mind is that even thought the insertion order is preserved the equality comparison for normal dicts is insensitive for normal dicts compared to OrderedDict For example: >>> OrderedDict([(1,1), (2,2)]) == OrderedDict([(2,2), (1,1)]) False >>> dict([(1,1), (2,2)]) == dict([(2,2), (1,1)]) True Signed-off-by: Martin Vrachev --- examples/repo_example/basic_repo.py | 34 ++++++++----------- .../repo_example/hashed_bin_delegation.py | 7 ++-- tests/repository_simulator.py | 13 ++++--- tuf/api/metadata.py | 9 +++-- 4 files changed, 27 insertions(+), 36 deletions(-) diff --git a/examples/repo_example/basic_repo.py b/examples/repo_example/basic_repo.py index 7201819e26..3a3fe91fd4 100644 --- a/examples/repo_example/basic_repo.py +++ b/examples/repo_example/basic_repo.py @@ -25,7 +25,6 @@ """ import os import tempfile -from collections import OrderedDict from datetime import datetime, timedelta from pathlib import Path from typing import Any, Dict @@ -103,7 +102,7 @@ def _in(days: float) -> datetime: signed=Targets( version=1, spec_version=SPEC_VERSION, expires=_in(7), targets={} ), - signatures=OrderedDict(), + signatures={}, ) # For the purpose of this example we use the top-level targets role to protect @@ -134,7 +133,7 @@ def _in(days: float) -> datetime: expires=_in(7), meta={"targets.json": MetaFile(version=1)}, ), - OrderedDict(), + {}, ) # Timestamp (freshness) @@ -156,7 +155,7 @@ def _in(days: float) -> datetime: expires=_in(1), snapshot_meta=MetaFile(version=1), ), - OrderedDict(), + {}, ) # Root (root of trust) @@ -195,7 +194,7 @@ def _in(days: float) -> datetime: }, consistent_snapshot=True, ), - signatures=OrderedDict(), + signatures={}, ) # NOTE: We only need the public part to populate root, so it is possible to use @@ -292,7 +291,7 @@ def _in(days: float) -> datetime: expires=_in(7), targets={target_path: target_file_info}, ), - signatures=OrderedDict(), + signatures={}, ) @@ -313,20 +312,15 @@ def _in(days: float) -> datetime: keys[delegatee_name] ) }, - roles=OrderedDict( - [ - ( - delegatee_name, - DelegatedRole( - name=delegatee_name, - keyids=[keys[delegatee_name]["keyid"]], - threshold=1, - terminating=True, - paths=["*.py"], - ), - ) - ] - ), + roles={ + delegatee_name: DelegatedRole( + name=delegatee_name, + keyids=[keys[delegatee_name]["keyid"]], + threshold=1, + terminating=True, + paths=["*.py"], + ), + }, ) # Remove target file info from top-level targets (delegatee is now responsible) diff --git a/examples/repo_example/hashed_bin_delegation.py b/examples/repo_example/hashed_bin_delegation.py index 644cf03c89..6041216fa3 100644 --- a/examples/repo_example/hashed_bin_delegation.py +++ b/examples/repo_example/hashed_bin_delegation.py @@ -19,7 +19,6 @@ import hashlib import os import tempfile -from collections import OrderedDict from datetime import datetime, timedelta from pathlib import Path from typing import Any, Dict, Iterator, List, Tuple @@ -160,10 +159,10 @@ def find_hash_bin(path: str) -> str: keys["bin-n"] ) }, - roles=OrderedDict(), + roles={}, ), ), - signatures=OrderedDict(), + signatures={}, ) # The hash bin generator yields an ordered list of incremental hash bin names @@ -190,7 +189,7 @@ def find_hash_bin(path: str) -> str: signed=Targets( version=1, spec_version=SPEC_VERSION, expires=_in(7), targets={} ), - signatures=OrderedDict(), + signatures={}, ) # Add target file diff --git a/tests/repository_simulator.py b/tests/repository_simulator.py index c8ddf8b13c..ba9cadceeb 100644 --- a/tests/repository_simulator.py +++ b/tests/repository_simulator.py @@ -47,7 +47,6 @@ import logging import os import tempfile -from collections import OrderedDict from dataclasses import dataclass, field from datetime import datetime, timedelta from typing import Dict, Iterator, List, Optional, Tuple @@ -167,15 +166,15 @@ def _initialize(self) -> None: """Setup a minimal valid repository.""" targets = Targets(1, SPEC_VER, self.safe_expiry, {}, None) - self.md_targets = Metadata(targets, OrderedDict()) + self.md_targets = Metadata(targets, {}) meta = {"targets.json": MetaFile(targets.version)} snapshot = Snapshot(1, SPEC_VER, self.safe_expiry, meta) - self.md_snapshot = Metadata(snapshot, OrderedDict()) + self.md_snapshot = Metadata(snapshot, {}) snapshot_meta = MetaFile(snapshot.version) timestamp = Timestamp(1, SPEC_VER, self.safe_expiry, snapshot_meta) - self.md_timestamp = Metadata(timestamp, OrderedDict()) + self.md_timestamp = Metadata(timestamp, {}) roles = {role_name: Role([], 1) for role_name in TOP_LEVEL_ROLE_NAMES} root = Root(1, SPEC_VER, self.safe_expiry, {}, roles, True) @@ -185,7 +184,7 @@ def _initialize(self) -> None: root.add_key(role, key) self.add_signer(role, signer) - self.md_root = Metadata(root, OrderedDict()) + self.md_root = Metadata(root, {}) self.publish_root() def publish_root(self) -> None: @@ -357,7 +356,7 @@ def add_delegation( # Create delegation if delegator.delegations is None: - delegator.delegations = Delegations({}, OrderedDict()) + delegator.delegations = Delegations({}, {}) # put delegation last by default delegator.delegations.roles[role.name] = role @@ -368,7 +367,7 @@ def add_delegation( # Add metadata for the role if role.name not in self.md_delegates: - self.md_delegates[role.name] = Metadata(targets, OrderedDict()) + self.md_delegates[role.name] = Metadata(targets, {}) def write(self) -> None: """Dump current repository metadata to self.dump_dir diff --git a/tuf/api/metadata.py b/tuf/api/metadata.py index 2ee2bc06a0..7850222412 100644 --- a/tuf/api/metadata.py +++ b/tuf/api/metadata.py @@ -32,7 +32,6 @@ import io import logging import tempfile -from collections import OrderedDict from datetime import datetime from typing import ( IO, @@ -113,7 +112,7 @@ class Metadata(Generic[T]): signing the canonical serialized representation of 'signed'. """ - def __init__(self, signed: T, signatures: "OrderedDict[str, Signature]"): + def __init__(self, signed: T, signatures: Dict[str, Signature]): self.signed: T = signed self.signatures = signatures @@ -150,7 +149,7 @@ def from_dict(cls, metadata: Dict[str, Any]) -> "Metadata[T]": raise ValueError(f'unrecognized metadata type "{_type}"') # Make sure signatures are unique - signatures: "OrderedDict[str, Signature]" = OrderedDict() + signatures: Dict[str, Signature] = {} for sig_dict in metadata.pop("signatures"): sig = Signature.from_dict(sig_dict) if sig.keyid in signatures: @@ -1211,7 +1210,7 @@ class Delegations: def __init__( self, keys: Dict[str, Key], - roles: "OrderedDict[str, DelegatedRole]", + roles: Dict[str, DelegatedRole], unrecognized_fields: Optional[Mapping[str, Any]] = None, ): self.keys = keys @@ -1233,7 +1232,7 @@ def from_dict(cls, delegations_dict: Dict[str, Any]) -> "Delegations": for keyid, key_dict in keys.items(): keys_res[keyid] = Key.from_dict(keyid, key_dict) roles = delegations_dict.pop("roles") - roles_res: "OrderedDict[str, DelegatedRole]" = OrderedDict() + roles_res: Dict[str, DelegatedRole] = {} for role_dict in roles: new_role = DelegatedRole.from_dict(role_dict) if new_role.name in roles_res: