From 0b8d3abccf780bb9e90955035106a91159e3ddc9 Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Mon, 21 Oct 2024 19:50:20 +0200 Subject: [PATCH 1/2] Make minimum Python version 3.9 again --- .github/workflows/tests.yml | 2 ++ CHANGELOG.md | 5 +++++ pyproject.toml | 8 ++++++-- src/pypi_attestations/_impl.py | 15 ++++++++------- test/test_impl.py | 9 +++++---- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 70d20db..3972c5e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,6 +11,8 @@ jobs: strategy: matrix: python: + - "3.9" + - "3.10" - "3.11" - "3.12" - "3.13" diff --git a/CHANGELOG.md b/CHANGELOG.md index aa70e10..aa61e80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- The minimum Python version required has been brought back to `3.9` + ([#64](https://github.com/trailofbits/pypi-attestations/pull/64)). + ### Fixed - `python -m pypi_attestations verify` now handles inputs like `dist/*` diff --git a/pyproject.toml b/pyproject.toml index ff7310e..67dddb9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "sigstore~=3.4", "sigstore-protobuf-specs", ] -requires-python = ">=3.11" +requires-python = ">=3.9" [tool.setuptools.dynamic] version = { attr = "pypi_attestations.__version__" } @@ -57,6 +57,7 @@ omit = ["src/pypi_attestations/_cli.py", "src/pypi_attestations/__main__.py"] mypy_path = "src" packages = "pypi_attestations" plugins = ["pydantic.mypy"] +python_version = "3.9" allow_redefinition = true check_untyped_defs = true disallow_incomplete_defs = true @@ -75,7 +76,7 @@ warn_unused_ignores = true [tool.ruff] line-length = 100 -target-version = "py311" +target-version = "py39" [tool.ruff.lint] select = ["E", "F", "I", "W", "UP", "ANN", "D", "COM", "ISC", "TCH", "SLF"] @@ -84,6 +85,9 @@ select = ["E", "F", "I", "W", "UP", "ANN", "D", "COM", "ISC", "TCH", "SLF"] # COM812 and ISC001 can cause conflicts when using ruff as a formatter. # See https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules. ignore = ["ANN101", "ANN102", "D203", "D213", "COM812", "ISC001"] +# Needed since Pydantic relies on runtime type annotations, and we target Python versions +# < 3.10. See https://docs.astral.sh/ruff/rules/non-pep604-annotation/#why-is-this-bad +pyupgrade.keep-runtime-typing = true [tool.ruff.lint.per-file-ignores] diff --git a/src/pypi_attestations/_impl.py b/src/pypi_attestations/_impl.py index 962c3db..ad93a46 100644 --- a/src/pypi_attestations/_impl.py +++ b/src/pypi_attestations/_impl.py @@ -7,7 +7,7 @@ import base64 from enum import Enum -from typing import TYPE_CHECKING, Annotated, Any, Literal, NewType +from typing import TYPE_CHECKING, Annotated, Any, Literal, NewType, Optional, Union, get_args import sigstore.errors from annotated_types import MinLen # noqa: TCH002 @@ -187,7 +187,7 @@ def verify( dist: Distribution, *, staging: bool = False, - ) -> tuple[str, dict[str, Any] | None]: + ) -> tuple[str, Optional[dict[str, Any]]]: """Verify against an existing Python distribution. The `identity` can be an object confirming to @@ -203,7 +203,8 @@ def verify( # NOTE: Can't do `isinstance` with `Publisher` since it's # a `_GenericAlias`; instead we punch through to the inner # `_Publisher` union. - if isinstance(identity, _Publisher): + # Use of typing.get_args is needed for Python < 3.10 + if isinstance(identity, get_args(_Publisher)): policy = identity._as_policy() # noqa: SLF001 else: policy = identity @@ -387,7 +388,7 @@ class _PublisherBase(BaseModel): model_config = ConfigDict(alias_generator=to_snake) kind: str - claims: dict[str, Any] | None = None + claims: Optional[dict[str, Any]] = None def _as_policy(self) -> VerificationPolicy: """Return an appropriate `sigstore.policy.VerificationPolicy` for this publisher.""" @@ -483,7 +484,7 @@ class GitHubPublisher(_PublisherBase): action. """ - environment: str | None = None + environment: Optional[str] = None """ The optional name GitHub Actions environment that the publishing action was performed from. @@ -505,7 +506,7 @@ class GitLabPublisher(_PublisherBase): `bar` owned by group `foo` and subgroup `baz`. """ - environment: str | None = None + environment: Optional[str] = None """ The optional environment that the publishing action was performed from. """ @@ -531,7 +532,7 @@ def _as_policy(self) -> VerificationPolicy: return policy.AllOf(policies) -_Publisher = GitHubPublisher | GitLabPublisher +_Publisher = Union[GitHubPublisher, GitLabPublisher] Publisher = Annotated[_Publisher, Field(discriminator="kind")] diff --git a/test/test_impl.py b/test/test_impl.py index 5fefdd5..109d5bc 100644 --- a/test/test_impl.py +++ b/test/test_impl.py @@ -4,7 +4,7 @@ import os from hashlib import sha256 from pathlib import Path -from typing import Any +from typing import Any, Optional import pretend import pytest @@ -133,7 +133,7 @@ def test_verify_github_attested(self) -> None: assert predicate == {} @pytest.mark.parametrize("claims", (None, {}, {"ref": "refs/tags/v0.0.4a2"})) - def test_verify_from_github_publisher(self, claims: dict | None) -> None: + def test_verify_from_github_publisher(self, claims: Optional[dict]) -> None: publisher = impl.GitHubPublisher( repository="trailofbits/pypi-attestation-models", workflow="release.yml", @@ -556,7 +556,7 @@ def test_as_policy(self) -> None: assert len(pol._children) == 3 @pytest.mark.parametrize("claims", [None, {}, {"something": "unrelated"}, {"ref": None}]) - def test_as_policy_invalid(self, claims: dict | None) -> None: + def test_as_policy_invalid(self, claims: Optional[dict]) -> None: publisher = impl.GitLabPublisher(repository="fake/fake", claims=claims) with pytest.raises(impl.VerificationError, match="refusing to build a policy"): @@ -600,7 +600,8 @@ class TestBase64Bytes: def test_decoding(self) -> None: # This raises when using our custom type. When using Pydantic's Base64Bytes, # this succeeds - with pytest.raises(ValueError, match="Only base64 data is allowed"): + # The exception message is different in Python 3.9 vs >=3.10 + with pytest.raises(ValueError, match="Non-base64 digit found|Only base64 data is allowed"): DummyModel(base64_bytes=b"a\n\naaa") def test_encoding(self) -> None: From 196c88939df829f0331f0d3879441a0e986645f0 Mon Sep 17 00:00:00 2001 From: Facundo Tuesca Date: Mon, 21 Oct 2024 19:52:06 +0200 Subject: [PATCH 2/2] prep 0.0.13 --- CHANGELOG.md | 5 ++++- src/pypi_attestations/__init__.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa61e80..251c1f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.13] + ### Changed - The minimum Python version required has been brought back to `3.9` @@ -144,7 +146,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial implementation -[Unreleased]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.12...HEAD +[Unreleased]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.13...HEAD +[0.0.13]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.12...v0.0.13 [0.0.12]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.11...v0.0.12 [0.0.11]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.10...v0.0.11 [0.0.10]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.9...v0.0.10 diff --git a/src/pypi_attestations/__init__.py b/src/pypi_attestations/__init__.py index 14db06c..6be0050 100644 --- a/src/pypi_attestations/__init__.py +++ b/src/pypi_attestations/__init__.py @@ -1,6 +1,6 @@ """The `pypi-attestations` APIs.""" -__version__ = "0.0.12" +__version__ = "0.0.13" from ._impl import ( Attestation,