diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d274caf3..58391f2e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,12 +16,10 @@ updates: # Python dependencies that are only pinned to ensure test reproducibility patterns: - "bandit" - - "black" - "coverage" - - "isort" - "mypy" - - "pydocstyle" - "pylint" + - "ruff" - "tox" dependencies: # Python (developer) runtime dependencies. Also any new dependencies not diff --git a/examples/repository/_simplerepo.py b/examples/repository/_simplerepo.py index 17ce7a0f..1ed9cb55 100644 --- a/examples/repository/_simplerepo.py +++ b/examples/repository/_simplerepo.py @@ -112,7 +112,9 @@ def _get_verification_result( ) def open(self, role: str) -> Metadata: - """Return current Metadata for role from 'storage' (or create a new one)""" + """Return current Metadata for role from 'storage' + (or create a new one) + """ if role not in self.role_cache: signed_init = _signed_init.get(role, Targets) diff --git a/pyproject.toml b/pyproject.toml index 48c66757..7ce0498e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,11 +81,21 @@ dev-mode-dirs = ["."] line-length=80 [tool.ruff.lint] +select = [ + "D", # pydocstyle + "I", # isort + "F", # pyflakes + "N", # pep8-naming + "E" # pycodestyle +] ignore = ["D400","D415","D213","D205","D202","D107","D407","D413","D212","D104","D406","D105","D411","D401","D200","D203"] - -[tool.ruff.lint.per-file-ignores] +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["D", "E"] +"examples/*/*" = ["D"] "tuf/repository/__init__.py" = ["F401"] "tuf/api/exceptions.py" = ["F401"] +"tuf/repository/_repository.py" = ["N818"] +"verify_release" = ["F401", "D103", "E501"] # Pylint section diff --git a/tox.ini b/tox.ini index a28a51f7..88bee67d 100644 --- a/tox.ini +++ b/tox.ini @@ -47,12 +47,12 @@ deps = --editable {toxinidir} lint_dirs = tuf examples tests verify_release commands = - ruff check --diff {[testenv:lint]lint_dirs} + ruff check {[testenv:lint]lint_dirs} ruff format --diff {[testenv:lint]lint_dirs} pylint -j 0 --rcfile=pyproject.toml {[testenv:lint]lint_dirs} mypy {[testenv:lint]lint_dirs} - + bandit -r tuf [testenv:docs] diff --git a/tuf/api/exceptions.py b/tuf/api/exceptions.py index 9e01f742..8992e1e5 100644 --- a/tuf/api/exceptions.py +++ b/tuf/api/exceptions.py @@ -11,6 +11,7 @@ #### Repository errors #### # pylint: disable=unused-import +# ruff: disable unused-inport from securesystemslib.exceptions import StorageError @@ -23,7 +24,9 @@ class RepositoryError(Exception): class UnsignedMetadataError(RepositoryError): - """An error about metadata object with insufficient threshold of signatures.""" + """An error about metadata object with insufficient threshold of + signatures. + """ class BadVersionNumberError(RepositoryError): diff --git a/tuf/api/metadata.py b/tuf/api/metadata.py index dadc51a6..d679bba5 100644 --- a/tuf/api/metadata.py +++ b/tuf/api/metadata.py @@ -19,9 +19,10 @@ The above principle means that a ``Metadata`` object represents a single metadata file, and has a ``signed`` attribute that is an instance of one of the -four top level signed classes (``Root``, ``Timestamp``, ``Snapshot`` and ``Targets``). -To make Python type annotations useful ``Metadata`` can be type constrained: e.g. the -signed attribute of ``Metadata[Root]`` is known to be ``Root``. +four top level signed classes (``Root``, ``Timestamp``, ``Snapshot`` and +``Targets``). To make Python type annotations useful ``Metadata`` can be +type constrained: e.g. the signed attribute of ``Metadata[Root]`` +is known to be ``Root``. Currently Metadata API supports JSON as the file format. @@ -101,8 +102,8 @@ class Metadata(Generic[T]): Using a type constraint is not required but not doing so means T is not a specific type so static typing cannot happen. Note that the type constraint - ``[Root]`` is not validated at runtime (as pure annotations are not available - then). + ``[Root]`` is not validated at runtime (as pure annotations are not + available then). New Metadata instances can be created from scratch with:: @@ -227,6 +228,7 @@ def from_file( storage_backend: Object that implements ``securesystemslib.storage.StorageBackendInterface``. Default is ``FilesystemBackend`` (i.e. a local file). + Raises: StorageError: The file cannot be read. tuf.api.serialization.DeserializationError: @@ -357,9 +359,10 @@ def sign( """Create signature over ``signed`` and assigns it to ``signatures``. Args: - signer: A ``securesystemslib.signer.Signer`` object that provides a private - key and signing implementation to generate the signature. A standard - implementation is available in ``securesystemslib.signer.SSlibSigner``. + signer: A ``securesystemslib.signer.Signer`` object that provides a + private key and signing implementation to generate the signature. A + standard implementation is available in + ``securesystemslib.signer.SSlibSigner``. append: ``True`` if the signature should be appended to the list of signatures or replace any existing signatures. The default behavior is to replace signatures. @@ -403,7 +406,8 @@ def verify_delegate( threshold of keys for ``delegated_role``. .. deprecated:: 3.1.0 - Please use ``Root.verify_delegate()`` or ``Targets.verify_delegate()``. + Please use ``Root.verify_delegate()`` or + ``Targets.verify_delegate()``. """ if self.signed.type not in ["root", "targets"]: @@ -522,7 +526,9 @@ def to_dict(self) -> Dict[str, Any]: @classmethod @abc.abstractmethod def from_dict(cls, signed_dict: Dict[str, Any]) -> "Signed": - """Deserialization helper, creates object from json/dict representation.""" + """Deserialization helper, creates object from json/dict + representation. + """ raise NotImplementedError @classmethod @@ -533,7 +539,8 @@ def _common_fields_from_dict( representation, and returns an ordered list to be passed as leading positional arguments to a subclass constructor. - See ``{Root, Timestamp, Snapshot, Targets}.from_dict`` methods for usage. + See ``{Root, Timestamp, Snapshot, Targets}.from_dict`` + methods for usage. """ _type = signed_dict.pop("_type") @@ -551,7 +558,8 @@ def _common_fields_from_dict( return version, spec_version, expires def _common_fields_to_dict(self) -> Dict[str, Any]: - """Return a dict representation of common fields of ``Signed`` instances. + """Return a dict representation of common fields of + ``Signed`` instances. See ``{Root, Timestamp, Snapshot, Targets}.to_dict`` methods for usage. @@ -700,19 +708,27 @@ def __bool__(self) -> bool: @property def verified(self) -> bool: - """True if threshold of signatures is met in both underlying VerificationResults.""" + """True if threshold of signatures is met in both underlying + VerificationResults. + """ return self.first.verified and self.second.verified @property def signed(self) -> Dict[str, Key]: - """Dictionary of all signing keys that have signed, from both VerificationResults""" - # return a union of all signed (in python<3.9 this requires dict unpacking) + """Dictionary of all signing keys that have signed, from both + VerificationResults. + return a union of all signed (in python<3.9 this requires + dict unpacking) + """ return {**self.first.signed, **self.second.signed} @property def unsigned(self) -> Dict[str, Key]: - """Dictionary of all signing keys that have not signed, from both VerificationResults""" - # return a union of all unsigned (in python<3.9 this requires dict unpacking) + """Dictionary of all signing keys that have not signed, from both + VerificationResults. + return a union of all unsigned (in python<3.9 this requires + dict unpacking) + """ return {**self.first.unsigned, **self.second.unsigned} @@ -828,8 +844,8 @@ class Root(Signed, _DelegatorMixin): roles: Dictionary of role names to Roles. Defines which keys are required to sign the metadata for a specific role. Default is a dictionary of top level roles without keys and threshold of 1. - consistent_snapshot: ``True`` if repository supports consistent snapshots. - Default is True. + consistent_snapshot: ``True`` if repository supports consistent + snapshots. Default is True. unrecognized_fields: Dictionary of all attributes that are not managed by TUF Metadata API @@ -1191,6 +1207,7 @@ def from_data( data: Metadata bytes that the metafile represents. hash_algorithms: Hash algorithms to create the hashes with. If not specified, the securesystemslib default hash algorithm is used. + Raises: ValueError: The hash algorithms list contains an unsupported algorithm. @@ -1234,7 +1251,8 @@ class Timestamp(Signed): """A container for the signed part of timestamp metadata. TUF file format uses a dictionary to contain the snapshot information: - this is not the case with ``Timestamp.snapshot_meta`` which is a ``MetaFile``. + this is not the case with ``Timestamp.snapshot_meta`` which is a + ``MetaFile``. *All parameters named below are not just constructor arguments but also instance attributes.* @@ -1366,12 +1384,13 @@ class DelegatedRole(Role): A delegation can happen in two ways: - - ``paths`` is set: delegates targets matching any path pattern in ``paths`` - - ``path_hash_prefixes`` is set: delegates targets whose target path hash - starts with any of the prefixes in ``path_hash_prefixes`` + - ``paths`` is set: delegates targets matching any path pattern in + ``paths`` + - ``path_hash_prefixes`` is set: delegates targets whose target path + hash starts with any of the prefixes in ``path_hash_prefixes`` - ``paths`` and ``path_hash_prefixes`` are mutually exclusive: both cannot be - set, at least one of them must be set. + ``paths`` and ``path_hash_prefixes`` are mutually exclusive: + both cannot be set, at least one of them must be set. *All parameters named below are not just constructor arguments but also instance attributes.* @@ -1491,10 +1510,10 @@ def is_delegated_path(self, target_filepath: str) -> bool: """Determine whether the given ``target_filepath`` is in one of the paths that ``DelegatedRole`` is trusted to provide. - The ``target_filepath`` and the ``DelegatedRole`` paths are expected to be - in their canonical forms, so e.g. "a/b" instead of "a//b" . Only "/" is - supported as target path separator. Leading separators are not handled - as special cases (see `TUF specification on targetpath + The ``target_filepath`` and the ``DelegatedRole`` paths are expected to + be in their canonical forms, so e.g. "a/b" instead of "a//b" . Only "/" + is supported as target path separator. Leading separators are not + handled as special cases (see `TUF specification on targetpath `_). Args: @@ -1613,7 +1632,8 @@ def to_dict(self) -> Dict[str, Any]: } def get_role_for_target(self, target_filepath: str) -> str: - """Calculate the name of the delegated role responsible for ``target_filepath``. + """Calculate the name of the delegated role responsible for + ``target_filepath``. The target at path ``target_filepath`` is assigned to a bin by casting the left-most ``bit_length`` of bits of the file path hash digest to @@ -1897,6 +1917,7 @@ def from_file( local_path: Local path to target file content. hash_algorithms: Hash algorithms to calculate hashes with. If not specified the securesystemslib default hash algorithm is used. + Raises: FileNotFoundError: The file doesn't exist. ValueError: The hash algorithms list contains an unsupported diff --git a/tuf/ngclient/_internal/requests_fetcher.py b/tuf/ngclient/_internal/requests_fetcher.py index 4abd3914..c39c1fb2 100644 --- a/tuf/ngclient/_internal/requests_fetcher.py +++ b/tuf/ngclient/_internal/requests_fetcher.py @@ -1,7 +1,8 @@ # Copyright 2021, New York University and the TUF contributors # SPDX-License-Identifier: MIT OR Apache-2.0 -"""Provides an implementation of ``FetcherInterface`` using the Requests HTTP library. +"""Provides an implementation of ``FetcherInterface`` using the Requests HTTP +library. """ # requests_fetcher is public but comes from _internal for now (because @@ -117,7 +118,8 @@ def _chunks(self, response: "requests.Response") -> Iterator[bytes]: response.close() def _get_session(self, url: str) -> requests.Session: - """Return a different customized requests.Session per schema+hostname combination. + """Return a different customized requests.Session per schema+hostname + combination. Raises: exceptions.DownloadError: When there is a problem parsing the url. diff --git a/tuf/ngclient/_internal/trusted_metadata_set.py b/tuf/ngclient/_internal/trusted_metadata_set.py index c99a365d..b9a8c358 100644 --- a/tuf/ngclient/_internal/trusted_metadata_set.py +++ b/tuf/ngclient/_internal/trusted_metadata_set.py @@ -73,9 +73,10 @@ class TrustedMetadataSet(abc.Mapping): """Internal class to keep track of trusted metadata in ``Updater``. - ``TrustedMetadataSet`` ensures that the collection of metadata in it is valid - and trusted through the whole client update workflow. It provides easy ways - to update the metadata with the caller making decisions on what is updated. + ``TrustedMetadataSet`` ensures that the collection of metadata in it is + valid and trusted through the whole client update workflow. It provides + easy ways to update the metadata with the caller making decisions on + what is updated. """ def __init__(self, root_data: bytes): @@ -107,7 +108,9 @@ def __len__(self) -> int: return len(self._trusted_set) def __iter__(self) -> Iterator[Metadata]: - """Return iterator over ``Metadata`` objects in ``TrustedMetadataSet``.""" + """Return iterator over ``Metadata`` objects in + ``TrustedMetadataSet``. + """ return iter(self._trusted_set.values()) # Helper properties for top level metadata diff --git a/tuf/repository/__init__.py b/tuf/repository/__init__.py index 57b29f11..a0ce8d55 100644 --- a/tuf/repository/__init__.py +++ b/tuf/repository/__init__.py @@ -10,4 +10,5 @@ The repository module is not considered part of the stable python-tuf API yet. """ +# ruff: disable unused-inport from tuf.repository._repository import AbortEdit, Repository diff --git a/tuf/repository/_repository.py b/tuf/repository/_repository.py index 3a0198ff..86e9d564 100644 --- a/tuf/repository/_repository.py +++ b/tuf/repository/_repository.py @@ -22,6 +22,7 @@ logger = logging.getLogger(__name__) +# ruff: ignore N818 `Exception name should have Error suffix` class AbortEdit(Exception): """Raise to exit the edit() contextmanager without saving changes""" diff --git a/verify_release b/verify_release index bab8f032..006c5dd9 100755 --- a/verify_release +++ b/verify_release @@ -19,7 +19,7 @@ from tempfile import TemporaryDirectory from typing import Optional try: - import build as _ # type: ignore + import build as _ import requests except ImportError: print("Error: verify_release requires modules 'requests' and 'build':")