diff --git a/pyproject.toml b/pyproject.toml index 8885dacd0..ebc5e7e88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ dev = [ "mypy>=1.1,<3.0", # NOTE(ww): ruff is under active development, so we pin conservatively here # and let Dependabot periodically perform this update. - "ruff<0.15.23", + "ruff<0.16.1", "types-pyOpenSSL", "mkdocs-material[imaging]", "mkdocstrings-python", @@ -130,7 +130,14 @@ exclude_dirs = ["./test"] [tool.ruff.lint] extend-select = ["I", "UP"] ignore = [ + "TRY004", # invalid type does not always lead to TypeError in this code base "UP007", # https://github.com/pydantic/pydantic/issues/4146 "UP011", "UP015", ] + +[tool.ruff.lint.per-file-ignores] +"test/**" = [ + "BLE001", # blind exception handling is fine in tests + "S110", # try-except-pass is fine in tests +] \ No newline at end of file diff --git a/sigstore/_cli.py b/sigstore/_cli.py index 7573b6436..d07257711 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -718,13 +718,13 @@ def _sign_file_threaded( predicate=predicate, ) result = signer.sign_dsse(statement_builder.build()) - except ExpiredIdentity as exp_identity: + except ExpiredIdentity: _logger.error("Signature failed: identity token has expired") - raise exp_identity + raise - except ExpiredCertificate as exp_certificate: + except ExpiredCertificate: _logger.error("Signature failed: Fulcio signing certificate has expired") - raise exp_certificate + raise _logger.info( f"Transparency log entry created at index: {result.log_entry._inner.log_index}" @@ -800,7 +800,7 @@ def _sign_common( for job in futures.as_completed(jobs): job.result() - for file, outputs in output_map.items(): + for outputs in output_map.values(): if outputs.signature is not None: print(f"Signature written to {outputs.signature}") if outputs.certificate is not None: @@ -973,12 +973,15 @@ def _collect_verification_state( ) # Fail if digest input is not used with `--bundle` or both `--certificate` and `--signature`. - if any(isinstance(x, Hashed) for x in args.files_or_digest): - if not args.bundle and not (args.certificate and args.signature): - _invalid_arguments( - args, - "verifying a digest input (sha256:*) needs either --bundle or both --certificate and --signature", - ) + if ( + any(isinstance(x, Hashed) for x in args.files_or_digest) + and not args.bundle + and not (args.certificate and args.signature) + ): + _invalid_arguments( + args, + "verifying a digest input (sha256:*) needs either --bundle or both --certificate and --signature", + ) # Fail if `--certificate` or `--signature` is used with `--offline`. if args.offline and (args.certificate or args.signature): diff --git a/sigstore/_internal/fulcio/client.py b/sigstore/_internal/fulcio/client.py index 75da5114f..63cffca8d 100644 --- a/sigstore/_internal/fulcio/client.py +++ b/sigstore/_internal/fulcio/client.py @@ -67,8 +67,6 @@ class FulcioClientError(Exception): Raised on any error in the Fulcio client. """ - pass - class _Endpoint(ABC): def __init__(self, url: str, session: requests.Session) -> None: diff --git a/sigstore/_internal/merkle.py b/sigstore/_internal/merkle.py index 1eab29807..3e65476cd 100644 --- a/sigstore/_internal/merkle.py +++ b/sigstore/_internal/merkle.py @@ -48,7 +48,7 @@ def _decomp_inclusion_proof(index: int, size: int) -> tuple[int, int]: """ inner = (index ^ (size - 1)).bit_length() - border = bin(index >> inner).count("1") + border = (index >> inner).bit_count() return inner, border diff --git a/sigstore/_internal/oidc/oauth.py b/sigstore/_internal/oidc/oauth.py index ebabaddb6..f4c441de7 100644 --- a/sigstore/_internal/oidc/oauth.py +++ b/sigstore/_internal/oidc/oauth.py @@ -156,7 +156,7 @@ def do_GET(self) -> None: _logger.debug(f"{self.path} unavailable (teardown)") self.send_response(404) self.end_headers() - return None + return r = urllib.parse.urlsplit(self.path) diff --git a/sigstore/_internal/rekor/__init__.py b/sigstore/_internal/rekor/__init__.py index 50bdad768..3f1c4895c 100644 --- a/sigstore/_internal/rekor/__init__.py +++ b/sigstore/_internal/rekor/__init__.py @@ -53,7 +53,7 @@ def __init__(self, http_error: requests.HTTPError): try: error = rekor_types.Error.model_validate_json(http_error.response.text) super().__init__(f"{error.code}: {error.message}") - except Exception: + except Exception: # noqa: BLE001 super().__init__( f"Rekor returned an unknown error with HTTP {http_error.response.status_code}" ) @@ -76,7 +76,6 @@ def create_entry( """ Submit the request to Rekor. """ - pass @classmethod @abstractmethod @@ -86,7 +85,6 @@ def _build_hashed_rekord_request( """ Construct a hashed rekord request to submit to Rekor. """ - pass @classmethod @abstractmethod @@ -96,7 +94,6 @@ def _build_dsse_request( """ Construct a dsse request to submit to Rekor. """ - pass # TODO: This should probably live somewhere better. diff --git a/sigstore/_internal/timestamp.py b/sigstore/_internal/timestamp.py index f8bd2433e..de77d7135 100644 --- a/sigstore/_internal/timestamp.py +++ b/sigstore/_internal/timestamp.py @@ -58,8 +58,6 @@ class TimestampError(Exception): A generic error in the TimestampAuthority client. """ - pass - class TimestampAuthorityClient: """Internal client to deal with a Timestamp Authority""" diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index 238a8e3ef..3149b65b5 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -25,10 +25,9 @@ from pathlib import Path from typing import ClassVar, NewType -import cryptography.hazmat.primitives.asymmetric.padding as padding from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec, ed25519, rsa +from cryptography.hazmat.primitives.asymmetric import ec, ed25519, padding, rsa from cryptography.x509 import ( Certificate, load_der_x509_certificate, @@ -147,10 +146,12 @@ class Keyring: Represents a set of keys, each of which is a potentially valid verifier. """ - def __init__(self, public_keys: list[common_v1.PublicKey] = []): + def __init__(self, public_keys: list[common_v1.PublicKey] | None = None): """ Create a new `Keyring`, with `keys` as the initial set of verifying keys. """ + if public_keys is None: + public_keys = [] self._keyring: dict[KeyID, Key] = {} for public_key in public_keys: diff --git a/sigstore/dsse/_predicate.py b/sigstore/dsse/_predicate.py index 7b9948df9..875a738b6 100644 --- a/sigstore/dsse/_predicate.py +++ b/sigstore/dsse/_predicate.py @@ -59,8 +59,6 @@ class Predicate(BaseModel): Base model for in-toto predicates """ - pass - class _SLSAConfigBase(BaseModel): """ diff --git a/sigstore/models.py b/sigstore/models.py index 2237b772e..19d12e8d3 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -63,7 +63,7 @@ ) from sigstore._internal.tuf import DEFAULT_TUF_URL, STAGING_TUF_URL, TrustUpdater from sigstore._utils import KeyID, cert_is_leaf, cert_is_root_ca, is_timerange_valid -from sigstore.errors import Error, MetadataError, TUFError, VerificationError +from sigstore.errors import Error, MetadataError, VerificationError # Versions supported by this client REKOR_VERSIONS = [1, 2] @@ -965,11 +965,8 @@ def from_tuf( tr_path = updater.get_trusted_root_path() inner_tr = trustroot_v1.TrustedRoot.from_json(Path(tr_path).read_bytes()) - try: - sc_path = updater.get_signing_config_path() - inner_sc = trustroot_v1.SigningConfig.from_json(Path(sc_path).read_bytes()) - except TUFError as e: - raise e + sc_path = updater.get_signing_config_path() + inner_sc = trustroot_v1.SigningConfig.from_json(Path(sc_path).read_bytes()) return cls( trustroot_v1.ClientTrustConfig( diff --git a/sigstore/oidc.py b/sigstore/oidc.py index 3abca867a..4ed8d3904 100644 --- a/sigstore/oidc.py +++ b/sigstore/oidc.py @@ -34,6 +34,8 @@ from sigstore._internal import USER_AGENT from sigstore.errors import Error, NetworkError +_logger = logging.getLogger(__name__) + # See: https://github.com/sigstore/fulcio/blob/b2186c0/pkg/config/config.go#L182-L201 _KNOWN_OIDC_ISSUERS = { "https://accounts.google.com": "email", @@ -228,8 +230,6 @@ class IssuerError(Exception): Raised on any communication or format error with an OIDC issuer. """ - pass - class Issuer: """ @@ -340,7 +340,7 @@ def identity_token( # nosec: B107 client_id, client_secret, ) - logging.debug(f"PAYLOAD: data={data}") + _logger.debug(f"PAYLOAD: data={data}") try: resp = self.session.post( self.oidc_config.token_endpoint, diff --git a/sigstore/sign.py b/sigstore/sign.py index 2036e4807..3c8915571 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -51,7 +51,7 @@ from contextlib import contextmanager from datetime import datetime, timezone -import cryptography.x509 as x509 +from cryptography import x509 from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from sigstore_models.common.v1 import HashOutput, MessageSignature diff --git a/test/assets/x509/build-testcases.py b/test/assets/x509/build-testcases.py index 6746354f0..9d1e0701e 100755 --- a/test/assets/x509/build-testcases.py +++ b/test/assets/x509/build-testcases.py @@ -50,7 +50,7 @@ def _keypair(priv_key_file: Path): _ROOT_PUBKEY, _ROOT_PRIVKEY = _keypair(_HERE / "root-privkey.pem") _NONROOT_PUBKEY, _ = _keypair(_HERE / "nonroot-privkey.pem") -_NOT_VALID_BEFORE_DATE = datetime.datetime(2023, 1, 1) +_NOT_VALID_BEFORE_DATE = datetime.datetime(2023, 1, 1, tzinfo=datetime.timezone.utc) _A_VERY_LONG_TIME = datetime.timedelta(days=365 * 1000) diff --git a/test/unit/conftest.py b/test/unit/conftest.py index 8d1b244d1..75f6f61af 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -80,7 +80,7 @@ def target_path(self, name: str) -> Path: try: path = next(matches) except StopIteration as e: - raise Exception(f"Unable to match {name} in targets/") from e + raise RuntimeError(f"Unable to match {name} in targets/") from e if next(matches, None) is None: return path diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index 4340ee007..9c937466a 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -244,7 +244,7 @@ def test_bad_media_type(self, asset): def test_trust_root_tuf_offline(mock_staging_tuf, tuf_dirs): # start with empty target cache, empty local metadata dir - data_dir, cache_dir = tuf_dirs + data_dir, _cache_dir = tuf_dirs # keep track of requests the TrustUpdater invoked by TrustedRoot makes reqs, fail_reqs = mock_staging_tuf diff --git a/test/unit/test_oidc.py b/test/unit/test_oidc.py index eefd1c10b..0aad6d0f6 100644 --- a/test/unit/test_oidc.py +++ b/test/unit/test_oidc.py @@ -19,6 +19,11 @@ from sigstore import oidc +@pytest.fixture +def now() -> int: + return int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp()) + + class TestIdentityToken: def test_invalid_jwt(self): with pytest.raises( @@ -26,8 +31,7 @@ def test_invalid_jwt(self): ): oidc.IdentityToken("invalid jwt") - def test_missing_iss(self, dummy_jwt): - now = int(datetime.datetime.now().timestamp()) + def test_missing_iss(self, dummy_jwt, now: int): jwt = dummy_jwt( { "aud": "sigstore", @@ -43,8 +47,7 @@ def test_missing_iss(self, dummy_jwt): ): oidc.IdentityToken(jwt) - def test_missing_aud(self, dummy_jwt): - now = int(datetime.datetime.now().timestamp()) + def test_missing_aud(self, dummy_jwt, now: int): jwt = dummy_jwt( { "sub": "fakesubject", @@ -61,8 +64,7 @@ def test_missing_aud(self, dummy_jwt): oidc.IdentityToken(jwt) @pytest.mark.parametrize("aud", (None, "not-sigstore")) - def test_invalid_aud(self, dummy_jwt, aud): - now = int(datetime.datetime.now().timestamp()) + def test_invalid_aud(self, dummy_jwt, aud, now: int): jwt = dummy_jwt( { "aud": aud, @@ -79,8 +81,7 @@ def test_invalid_aud(self, dummy_jwt, aud): ): oidc.IdentityToken(jwt) - def test_missing_iat(self, dummy_jwt): - now = int(datetime.datetime.now().timestamp()) + def test_missing_iat(self, dummy_jwt, now: int): jwt = dummy_jwt( { "aud": "sigstore", @@ -97,8 +98,7 @@ def test_missing_iat(self, dummy_jwt): oidc.IdentityToken(jwt) @pytest.mark.parametrize("iat", (None, "not-an-int")) - def test_invalid_iat(self, dummy_jwt, iat): - now = int(datetime.datetime.now().timestamp()) + def test_invalid_iat(self, dummy_jwt, iat, now: int): jwt = dummy_jwt( { "aud": "sigstore", @@ -115,8 +115,7 @@ def test_invalid_iat(self, dummy_jwt, iat): ): oidc.IdentityToken(jwt) - def test_missing_nbf_ok(self, dummy_jwt): - now = int(datetime.datetime.now().timestamp()) + def test_missing_nbf_ok(self, dummy_jwt, now: int): jwt = dummy_jwt( { "aud": "sigstore", @@ -129,8 +128,7 @@ def test_missing_nbf_ok(self, dummy_jwt): assert oidc.IdentityToken(jwt) is not None - def test_invalid_nbf(self, dummy_jwt): - now = int(datetime.datetime.now().timestamp()) + def test_invalid_nbf(self, dummy_jwt, now: int): jwt = dummy_jwt( { "aud": "sigstore", @@ -148,8 +146,7 @@ def test_invalid_nbf(self, dummy_jwt): ): oidc.IdentityToken(jwt) - def test_missing_exp(self, dummy_jwt): - now = int(datetime.datetime.now().timestamp()) + def test_missing_exp(self, dummy_jwt, now: int): jwt = dummy_jwt( { "aud": "sigstore", @@ -165,8 +162,7 @@ def test_missing_exp(self, dummy_jwt): ): oidc.IdentityToken(jwt) - def test_invalid_exp(self, dummy_jwt): - now = int(datetime.datetime.now().timestamp()) + def test_invalid_exp(self, dummy_jwt, now: int): jwt = dummy_jwt( { "aud": "sigstore", @@ -187,8 +183,7 @@ def test_invalid_exp(self, dummy_jwt): @pytest.mark.parametrize( "iss", [k for k, v in oidc._KNOWN_OIDC_ISSUERS.items() if v != "sub"] ) - def test_missing_identity_claim(self, dummy_jwt, iss): - now = int(datetime.datetime.now().timestamp()) + def test_missing_identity_claim(self, dummy_jwt, iss, now: int): jwt = dummy_jwt( { "aud": "sigstore", @@ -207,8 +202,7 @@ def test_missing_identity_claim(self, dummy_jwt, iss): oidc.IdentityToken(jwt) @pytest.mark.parametrize("fed", ("notadict", {"connector_id": 123})) - def test_invalid_federated_claims(self, dummy_jwt, fed): - now = int(datetime.datetime.now().timestamp()) + def test_invalid_federated_claims(self, dummy_jwt, fed, now: int): jwt = dummy_jwt( { "aud": "sigstore", @@ -248,8 +242,9 @@ def test_invalid_federated_claims(self, dummy_jwt, fed): ("hxxps://unknown.issuer.example.com/auth", "sub", "some-subject", None), ], ) - def test_ok(self, dummy_jwt, iss, identity_claim, identity_value, fed_iss): - now = int(datetime.datetime.now().timestamp()) + def test_ok( + self, dummy_jwt, iss, identity_claim, identity_value, fed_iss, now: int + ): jwt = dummy_jwt( { "aud": "sigstore", diff --git a/test/unit/test_session_reuse.py b/test/unit/test_session_reuse.py index 522eac7e1..3f853c7d9 100644 --- a/test/unit/test_session_reuse.py +++ b/test/unit/test_session_reuse.py @@ -29,8 +29,8 @@ def test_rekor_v1_session_reuse_public_api(): mock_session_cls.return_value = mock_session_inst # Access log endpoint multiple times - client.log - client.log + _ = client.log + _ = client.log # Expect 1 session assert mock_session_cls.call_count == 1 diff --git a/test/unit/test_sign.py b/test/unit/test_sign.py index 49e389bbf..b70fd5201 100644 --- a/test/unit/test_sign.py +++ b/test/unit/test_sign.py @@ -14,9 +14,9 @@ import hashlib import secrets -import cryptography.x509 as x509 import pretend import pytest +from cryptography import x509 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec from sigstore_models.common.v1 import HashAlgorithm @@ -146,9 +146,11 @@ def test_sct_verify_keyring_lookup_error(sign_ctx_and_ident_for_env, monkeypatch assert identity is not None payload = secrets.token_bytes(32) - with pytest.raises(VerificationError, match=r"SCT verify failed:"): - with ctx.signer(identity) as signer: - signer.sign_artifact(payload) + with ( + pytest.raises(VerificationError, match=r"SCT verify failed:"), + ctx.signer(identity) as signer, + ): + signer.sign_artifact(payload) @pytest.mark.parametrize("env", ["staging", "production"]) @@ -166,9 +168,8 @@ def test_sct_verify_keyring_error(sign_ctx_and_ident_for_env, monkeypatch): payload = secrets.token_bytes(32) - with pytest.raises(VerificationError): - with ctx.signer(identity) as signer: - signer.sign_artifact(payload) + with pytest.raises(VerificationError), ctx.signer(identity) as signer: + signer.sign_artifact(payload) @pytest.mark.parametrize("env", ["staging", "production"]) diff --git a/test/unit/verify/test_verifier.py b/test/unit/verify/test_verifier.py index 1a500ec93..2a8fe3a3c 100644 --- a/test/unit/verify/test_verifier.py +++ b/test/unit/verify/test_verifier.py @@ -107,7 +107,7 @@ def test_verifier_bundle_artifact(signing_bundle, null_policy, filename): ("a.dsse.staging-rekor-v2.txt",), ) def test_verifier_bundle_dsse(signing_bundle, null_policy, filename): - (file, bundle) = signing_bundle(filename) + (_file, bundle) = signing_bundle(filename) verifier = Verifier.staging() verifier.verify_dsse(bundle, null_policy) @@ -327,15 +327,15 @@ def test_verifier_outside_validity_range( 0 ]._inner.valid_for.end = datetime(2024, 10, 31, tzinfo=timezone.utc) - with caplog.at_level(logging.DEBUG, logger="sigstore.verify.verifier"): - with pytest.raises( - VerificationError, match="not enough sources of verified time" - ): - verifier.verify_artifact( - asset("tsa/bundle.txt").read_bytes(), - Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()), - null_policy, - ) + with ( + caplog.at_level(logging.DEBUG, logger="sigstore.verify.verifier"), + pytest.raises(VerificationError, match="not enough sources"), + ): + verifier.verify_artifact( + asset("tsa/bundle.txt").read_bytes(), + Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()), + null_policy, + ) assert ( "Unable to verify Timestamp because not in CA time range." @@ -354,15 +354,15 @@ def verify_function(*args): monkeypatch.setattr(rfc3161_client.verify._Verifier, "verify", verify_function) - with caplog.at_level(logging.DEBUG, logger="sigstore.verify.verifier"): - with pytest.raises( - VerificationError, match="not enough sources of verified time" - ): - verifier.verify_artifact( - asset("tsa/bundle.txt").read_bytes(), - Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()), - null_policy, - ) + with ( + caplog.at_level(logging.DEBUG, logger="sigstore.verify.verifier"), + pytest.raises(VerificationError, match="not enough sources"), + ): + verifier.verify_artifact( + asset("tsa/bundle.txt").read_bytes(), + Bundle.from_json(asset("tsa/bundle.txt.sigstore").read_bytes()), + null_policy, + ) assert caplog.records[0].message == "Unable to verify Timestamp with CA." diff --git a/uv.lock b/uv.lock index 2390c57dd..b31ce58e0 100644 --- a/uv.lock +++ b/uv.lock @@ -1661,27 +1661,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/06/ae069393fc66e8ff33036d4b368003833bf6e88ccf182e17e7a2f1c754fd/ruff-0.15.22.tar.gz", hash = "sha256:3f15175b1fb580126f58285a5dae6b2ea89000136d980c64499211f116b54809", size = 4785063, upload-time = "2026-07-16T15:14:13.244Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/18/ee54b7ae1e121be7a28ea6da4b67564ebb0530e183a54415ab7e3bcd2c4e/ruff-0.15.22-py3-none-linux_armv6l.whl", hash = "sha256:44423e73493737f5e7c5b41d475483898ff37afcdae38bc3da5085e29af1c2d8", size = 10781258, upload-time = "2026-07-16T15:13:19.452Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d2/2520cb14761ddbeaf57642a76942fc36adcbdbe53b4532241995f6fc485c/ruff-0.15.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b82c6482946e9eda7ff2e091d25b8bad3f718684e1916d41bd56873cee05b697", size = 10999477, upload-time = "2026-07-16T15:13:23.318Z" }, - { url = "https://files.pythonhosted.org/packages/c9/10/74e53572aa758dfaa678c2a2646b5c5515d884b7ca56be4d2ce03ca4b560/ruff-0.15.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:11c1c715af53a09f714e011106bffc419751ec8232fcb5da42173284ea3fec6f", size = 10466716, upload-time = "2026-07-16T15:13:26.162Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/44eaaf0844e028182f2d0a8f2190d0f359159aed0a9e5ab861d892f1ae2a/ruff-0.15.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742a29cf29bddb7c8327895d6a10e0e6c5b38a96dd407af9b5d0857f809c0576", size = 10892644, upload-time = "2026-07-16T15:13:29.229Z" }, - { url = "https://files.pythonhosted.org/packages/9f/21/8edf559014d2b0f82beea19cfb713993ad802ccda16868769979c6090a84/ruff-0.15.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72af58b951b0ae395935ae79763dc349bc0eb706319d28f7a33ad2cfb3cfc178", size = 10576719, upload-time = "2026-07-16T15:13:32.35Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1e/3a13abd392a3b50b62e5938a831f9ab6e588358cacad5c18545b716d2182/ruff-0.15.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d425005c1835eb24e2ee4161cb90e8db263415f4a71c8c72c33abaa6c0c224", size = 11376494, upload-time = "2026-07-16T15:13:35.958Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3e/422d3d95bcf04dd78e1aeac22184d4f9a8fb2c01865d39d44618484a0317/ruff-0.15.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b9b3f8779a4f08c969defc3c8c35abffaa757e601ed5ae66d6d1db6519969a", size = 12208370, upload-time = "2026-07-16T15:13:39.185Z" }, - { url = "https://files.pythonhosted.org/packages/1e/91/5d065a0e0a02bf4813f5119ad278462eed081d2b832eb7c021ade0ec9e65/ruff-0.15.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e0dd1b2e4d3d585f897a0d137cbf4eaf6223bef4e8ce34d6bb12556c5f9249e", size = 11581098, upload-time = "2026-07-16T15:13:42.132Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f9/a0d4871d12fae702eb1f41b686caf05f1f8b124dc6db6f784f53d74918fa/ruff-0.15.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365523eb91d9224e1bcb03b022fbf0facb8f9e23792a2c53d9d4b3924bdbdebb", size = 11399422, upload-time = "2026-07-16T15:13:45.2Z" }, - { url = "https://files.pythonhosted.org/packages/18/80/c843a5176cddbceb0b7e8dd41cf9993490796c1c469348d384f5a5c13c56/ruff-0.15.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fabfd168afdf29fee5be98b831efa9683c94d7c5a3b58b9ce5a2e38444589a74", size = 11381683, upload-time = "2026-07-16T15:13:48.46Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/8485de0ae92239438a36cfc51350db9b9e85c9ebdfaea91b18e422706662/ruff-0.15.22-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:225dbf095a87f1d9f90f5fd7924d2613ee452a75a4308c63a8f50f761787aa7c", size = 10850295, upload-time = "2026-07-16T15:13:51.655Z" }, - { url = "https://files.pythonhosted.org/packages/fa/91/24977ec2ec72eaf15e4394ace2959fdff2dd1e14f03e005e838023407169/ruff-0.15.22-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1877d63b9d24ed278744f1523fd11b85540566d54641f97c566d7d9dc5ca5296", size = 10579640, upload-time = "2026-07-16T15:13:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/9c/47/9b51216951974df1f263ac19da550d34252e0ed7218c25f10c5ef9ed7517/ruff-0.15.22-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1606c510bd7215680d32efab38965f7cdec3ef69f5170a3f4791404ffdd5262", size = 11105077, upload-time = "2026-07-16T15:13:57.915Z" }, - { url = "https://files.pythonhosted.org/packages/c2/47/20e9d4a3b8016778acea5fc32bb50d35d207500a17ddb529ffa6996feef8/ruff-0.15.22-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:630479b18625f5ffc373f77603a22a9f8ac0acd7ff0501178b5db28ec71e9c64", size = 11490980, upload-time = "2026-07-16T15:14:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/4d/76/3f72d8fc38c1cb77b38c56a70da9d0c17700cc1cc50f9649c9d3c8f5ba71/ruff-0.15.22-py3-none-win32.whl", hash = "sha256:e5ba0e4a13fd14abbed2a77b517a3911290c6c6c59ef67784328d1668fab76cf", size = 10789165, upload-time = "2026-07-16T15:14:04.16Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/4965251734c2b6fcdca1b1b187d20bcac3af0ee5b083b89c910bb961ce3a/ruff-0.15.22-py3-none-win_amd64.whl", hash = "sha256:9be63ba1eb936acd2d1342fb8337c356353706fce233b2a15a09a97037e6acde", size = 11938297, upload-time = "2026-07-16T15:14:07.316Z" }, - { url = "https://files.pythonhosted.org/packages/57/c9/e69b1ff4c8b69093ef08b8919ab767af0569666865b39c30a8795d88d3c6/ruff-0.15.22-py3-none-win_arm64.whl", hash = "sha256:e1168075b72158510839f250027659cdd78476f40507dd517892304c41318661", size = 11298172, upload-time = "2026-07-16T15:14:10.51Z" }, +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" }, + { url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" }, + { url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" }, ] [[package]] @@ -1770,7 +1770,7 @@ dev = [ { name = "pretend" }, { name = "pytest" }, { name = "pytest-cov" }, - { name = "ruff", specifier = "<0.15.23" }, + { name = "ruff", specifier = "<0.16.1" }, { name = "types-pyopenssl" }, ]