Skip to content

Commit

Permalink
Merge pull request #1095 from woodruffw-forks/ww/attestations-flag
Browse files Browse the repository at this point in the history
twine/upload: attestations scaffolding
  • Loading branch information
sigmavirus24 authored Apr 30, 2024
2 parents 5de10e8 + 40f4197 commit de2acee
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 5 deletions.
4 changes: 4 additions & 0 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,7 @@ def test_non_interactive_environment(self, monkeypatch):
monkeypatch.setenv("TWINE_NON_INTERACTIVE", "0")
args = self.parse_args([])
assert not args.non_interactive

def test_attestations_flag(self):
args = self.parse_args(["--attestations"])
assert args.attestations
40 changes: 40 additions & 0 deletions tests/test_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,46 @@ def stub_sign(package, *_):
]


def test_split_inputs():
"""Split inputs into dists, signatures, and attestations."""
inputs = [
helpers.WHEEL_FIXTURE,
helpers.WHEEL_FIXTURE + ".asc",
helpers.WHEEL_FIXTURE + ".build.attestation",
helpers.WHEEL_FIXTURE + ".publish.attestation",
helpers.SDIST_FIXTURE,
helpers.SDIST_FIXTURE + ".asc",
helpers.NEW_WHEEL_FIXTURE,
helpers.NEW_WHEEL_FIXTURE + ".frob.attestation",
helpers.NEW_SDIST_FIXTURE,
]

inputs = upload._split_inputs(inputs)

assert inputs.dists == [
helpers.WHEEL_FIXTURE,
helpers.SDIST_FIXTURE,
helpers.NEW_WHEEL_FIXTURE,
helpers.NEW_SDIST_FIXTURE,
]

expected_signatures = {
os.path.basename(dist) + ".asc": dist + ".asc"
for dist in [helpers.WHEEL_FIXTURE, helpers.SDIST_FIXTURE]
}
assert inputs.signatures == expected_signatures

assert inputs.attestations_by_dist == {
helpers.WHEEL_FIXTURE: [
helpers.WHEEL_FIXTURE + ".build.attestation",
helpers.WHEEL_FIXTURE + ".publish.attestation",
],
helpers.SDIST_FIXTURE: [],
helpers.NEW_WHEEL_FIXTURE: [helpers.NEW_WHEEL_FIXTURE + ".frob.attestation"],
helpers.NEW_SDIST_FIXTURE: [],
}


def test_successs_prints_release_urls(upload_settings, stub_repository, capsys):
"""Print PyPI release URLS for each uploaded package."""
stub_repository.release_urls = lambda packages: {RELEASE_URL, NEW_RELEASE_URL}
Expand Down
49 changes: 44 additions & 5 deletions twine/commands/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import fnmatch
import logging
import os.path
from typing import Dict, List, cast
from typing import Dict, List, NamedTuple, cast

import requests
from rich import print
Expand Down Expand Up @@ -91,6 +92,44 @@ def _make_package(
return package


class Inputs(NamedTuple):
"""Represents structured user inputs."""

dists: List[str]
signatures: Dict[str, str]
attestations_by_dist: Dict[str, List[str]]


def _split_inputs(
inputs: List[str],
) -> Inputs:
"""
Split the unstructured list of input files provided by the user into groups.
Three groups are returned: upload files (i.e. dists), signatures, and attestations.
Upload files are returned as a linear list, signatures are returned as a
dict of ``basename -> path``, and attestations are returned as a dict of
``dist-path -> [attestation-path]``.
"""
signatures = {os.path.basename(i): i for i in fnmatch.filter(inputs, "*.asc")}
attestations = fnmatch.filter(inputs, "*.*.attestation")
dists = [
dist
for dist in inputs
if dist not in (set(signatures.values()) | set(attestations))
]

attestations_by_dist = {}
for dist in dists:
dist_basename = os.path.basename(dist)
attestations_by_dist[dist] = [
a for a in attestations if os.path.basename(a).startswith(dist_basename)
]

return Inputs(dists, signatures, attestations_by_dist)


def upload(upload_settings: settings.Settings, dists: List[str]) -> None:
"""Upload one or more distributions to a repository, and display the progress.
Expand All @@ -105,17 +144,17 @@ def upload(upload_settings: settings.Settings, dists: List[str]) -> None:
The configured options related to uploading to a repository.
:param dists:
The distribution files to upload to the repository. This can also include
``.asc`` files; the GPG signatures will be added to the corresponding uploads.
``.asc`` and ``.attestation`` files, which will be added to their respective
file uploads.
:raises twine.exceptions.TwineException:
The upload failed due to a configuration error.
:raises requests.HTTPError:
The repository responded with an error.
"""
dists = commands._find_dists(dists)
# Determine if the user has passed in pre-signed distributions
signatures = {os.path.basename(d): d for d in dists if d.endswith(".asc")}
uploads = [i for i in dists if not i.endswith(".asc")]
# Determine if the user has passed in pre-signed distributions or any attestations.
uploads, signatures, _ = _split_inputs(dists)

upload_settings.check_repository_url()
repository_url = cast(str, upload_settings.repository_config["repository"])
Expand Down
10 changes: 10 additions & 0 deletions twine/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Settings:
def __init__(
self,
*,
attestations: bool = False,
sign: bool = False,
sign_with: str = "gpg",
identity: Optional[str] = None,
Expand All @@ -64,6 +65,8 @@ def __init__(
) -> None:
"""Initialize our settings instance.
:param attestations:
Whether the package file should be uploaded with attestations.
:param sign:
Configure whether the package file should be signed.
:param sign_with:
Expand Down Expand Up @@ -114,6 +117,7 @@ def __init__(
repository_name=repository_name,
repository_url=repository_url,
)
self.attestations = attestations
self._handle_package_signing(
sign=sign,
sign_with=sign_with,
Expand Down Expand Up @@ -175,6 +179,12 @@ def register_argparse_arguments(parser: argparse.ArgumentParser) -> None:
" This overrides --repository. "
"(Can also be set via %(env)s environment variable.)",
)
parser.add_argument(
"--attestations",
action="store_true",
default=False,
help="Upload each file's associated attestations.",
)
parser.add_argument(
"-s",
"--sign",
Expand Down

0 comments on commit de2acee

Please sign in to comment.