Skip to content

Commit 31afa30

Browse files
authored
Make minimum Python version 3.9 again (#64)
1 parent b172958 commit 31afa30

File tree

6 files changed

+31
-15
lines changed

6 files changed

+31
-15
lines changed

.github/workflows/tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ jobs:
1111
strategy:
1212
matrix:
1313
python:
14+
- "3.9"
15+
- "3.10"
1416
- "3.11"
1517
- "3.12"
1618
- "3.13"

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.0.13]
11+
12+
### Changed
13+
14+
- The minimum Python version required has been brought back to `3.9`
15+
([#64](https://github.com/trailofbits/pypi-attestations/pull/64)).
16+
1017
### Fixed
1118

1219
- `python -m pypi_attestations verify` now handles inputs like `dist/*`
@@ -139,7 +146,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
139146

140147
- Initial implementation
141148

142-
[Unreleased]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.12...HEAD
149+
[Unreleased]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.13...HEAD
150+
[0.0.13]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.12...v0.0.13
143151
[0.0.12]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.11...v0.0.12
144152
[0.0.11]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.10...v0.0.11
145153
[0.0.10]: https://github.com/trailofbits/pypi-attestation-models/compare/v0.0.9...v0.0.10

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies = [
1717
"sigstore~=3.4",
1818
"sigstore-protobuf-specs",
1919
]
20-
requires-python = ">=3.11"
20+
requires-python = ">=3.9"
2121

2222
[tool.setuptools.dynamic]
2323
version = { attr = "pypi_attestations.__version__" }
@@ -57,6 +57,7 @@ omit = ["src/pypi_attestations/_cli.py", "src/pypi_attestations/__main__.py"]
5757
mypy_path = "src"
5858
packages = "pypi_attestations"
5959
plugins = ["pydantic.mypy"]
60+
python_version = "3.9"
6061
allow_redefinition = true
6162
check_untyped_defs = true
6263
disallow_incomplete_defs = true
@@ -75,7 +76,7 @@ warn_unused_ignores = true
7576

7677
[tool.ruff]
7778
line-length = 100
78-
target-version = "py311"
79+
target-version = "py39"
7980

8081
[tool.ruff.lint]
8182
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"]
8485
# COM812 and ISC001 can cause conflicts when using ruff as a formatter.
8586
# See https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules.
8687
ignore = ["ANN101", "ANN102", "D203", "D213", "COM812", "ISC001"]
88+
# Needed since Pydantic relies on runtime type annotations, and we target Python versions
89+
# < 3.10. See https://docs.astral.sh/ruff/rules/non-pep604-annotation/#why-is-this-bad
90+
pyupgrade.keep-runtime-typing = true
8791

8892
[tool.ruff.lint.per-file-ignores]
8993

src/pypi_attestations/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""The `pypi-attestations` APIs."""
22

3-
__version__ = "0.0.12"
3+
__version__ = "0.0.13"
44

55
from ._impl import (
66
Attestation,

src/pypi_attestations/_impl.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import base64
99
from enum import Enum
10-
from typing import TYPE_CHECKING, Annotated, Any, Literal, NewType
10+
from typing import TYPE_CHECKING, Annotated, Any, Literal, NewType, Optional, Union, get_args
1111

1212
import sigstore.errors
1313
from annotated_types import MinLen # noqa: TCH002
@@ -187,7 +187,7 @@ def verify(
187187
dist: Distribution,
188188
*,
189189
staging: bool = False,
190-
) -> tuple[str, dict[str, Any] | None]:
190+
) -> tuple[str, Optional[dict[str, Any]]]:
191191
"""Verify against an existing Python distribution.
192192
193193
The `identity` can be an object confirming to
@@ -203,7 +203,8 @@ def verify(
203203
# NOTE: Can't do `isinstance` with `Publisher` since it's
204204
# a `_GenericAlias`; instead we punch through to the inner
205205
# `_Publisher` union.
206-
if isinstance(identity, _Publisher):
206+
# Use of typing.get_args is needed for Python < 3.10
207+
if isinstance(identity, get_args(_Publisher)):
207208
policy = identity._as_policy() # noqa: SLF001
208209
else:
209210
policy = identity
@@ -387,7 +388,7 @@ class _PublisherBase(BaseModel):
387388
model_config = ConfigDict(alias_generator=to_snake)
388389

389390
kind: str
390-
claims: dict[str, Any] | None = None
391+
claims: Optional[dict[str, Any]] = None
391392

392393
def _as_policy(self) -> VerificationPolicy:
393394
"""Return an appropriate `sigstore.policy.VerificationPolicy` for this publisher."""
@@ -483,7 +484,7 @@ class GitHubPublisher(_PublisherBase):
483484
action.
484485
"""
485486

486-
environment: str | None = None
487+
environment: Optional[str] = None
487488
"""
488489
The optional name GitHub Actions environment that the publishing
489490
action was performed from.
@@ -505,7 +506,7 @@ class GitLabPublisher(_PublisherBase):
505506
`bar` owned by group `foo` and subgroup `baz`.
506507
"""
507508

508-
environment: str | None = None
509+
environment: Optional[str] = None
509510
"""
510511
The optional environment that the publishing action was performed from.
511512
"""
@@ -531,7 +532,7 @@ def _as_policy(self) -> VerificationPolicy:
531532
return policy.AllOf(policies)
532533

533534

534-
_Publisher = GitHubPublisher | GitLabPublisher
535+
_Publisher = Union[GitHubPublisher, GitLabPublisher]
535536
Publisher = Annotated[_Publisher, Field(discriminator="kind")]
536537

537538

test/test_impl.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
from hashlib import sha256
66
from pathlib import Path
7-
from typing import Any
7+
from typing import Any, Optional
88

99
import pretend
1010
import pytest
@@ -133,7 +133,7 @@ def test_verify_github_attested(self) -> None:
133133
assert predicate == {}
134134

135135
@pytest.mark.parametrize("claims", (None, {}, {"ref": "refs/tags/v0.0.4a2"}))
136-
def test_verify_from_github_publisher(self, claims: dict | None) -> None:
136+
def test_verify_from_github_publisher(self, claims: Optional[dict]) -> None:
137137
publisher = impl.GitHubPublisher(
138138
repository="trailofbits/pypi-attestation-models",
139139
workflow="release.yml",
@@ -556,7 +556,7 @@ def test_as_policy(self) -> None:
556556
assert len(pol._children) == 3
557557

558558
@pytest.mark.parametrize("claims", [None, {}, {"something": "unrelated"}, {"ref": None}])
559-
def test_as_policy_invalid(self, claims: dict | None) -> None:
559+
def test_as_policy_invalid(self, claims: Optional[dict]) -> None:
560560
publisher = impl.GitLabPublisher(repository="fake/fake", claims=claims)
561561

562562
with pytest.raises(impl.VerificationError, match="refusing to build a policy"):
@@ -600,7 +600,8 @@ class TestBase64Bytes:
600600
def test_decoding(self) -> None:
601601
# This raises when using our custom type. When using Pydantic's Base64Bytes,
602602
# this succeeds
603-
with pytest.raises(ValueError, match="Only base64 data is allowed"):
603+
# The exception message is different in Python 3.9 vs >=3.10
604+
with pytest.raises(ValueError, match="Non-base64 digit found|Only base64 data is allowed"):
604605
DummyModel(base64_bytes=b"a\n\naaa")
605606

606607
def test_encoding(self) -> None:

0 commit comments

Comments
 (0)