Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions flit/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import sys
from dataclasses import dataclass
from typing import Optional
from urllib.parse import urlparse
from urllib.parse import urlparse, urlunparse

from flit_core.common import make_metadata, Metadata, Module
from .config import read_flit_config
Expand Down Expand Up @@ -44,6 +44,19 @@ def is_pypi(self):
return self.url.rstrip('/').endswith('/legacy')


def normalize_repository_url(url: str) -> str:
"""Ensure Warehouse's ``/legacy`` upload API has a trailing slash.

``https://test.pypi.org/legacy`` (no slash) is a different resource from
``.../legacy/`` and does not accept uploads (issue #179).
"""
parsed = urlparse(url)
path = parsed.path or ''
if path.rstrip('/') == '/legacy' and not path.endswith('/'):
return urlunparse(parsed._replace(path=path + '/'))
return url


def get_repositories(file="~/.pypirc"):
"""Get the known repositories from a pypirc file.

Expand Down Expand Up @@ -278,7 +291,8 @@ def upload_file(file:Path, metadata:Metadata, repo: RepoDetails):

log.info('Uploading %s...', file)
resp = requests.post(
repo.url, data=data, files=files, auth=(repo.username, repo.password),
normalize_repository_url(repo.url), data=data, files=files,
auth=(repo.username, repo.password),
)
resp.raise_for_status()

Expand Down
39 changes: 39 additions & 0 deletions tests/test_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,45 @@ def test_upload_pypirc_file(copy_sample):
assert repo.password == pypirc3_pass


@responses.activate
def test_upload_legacy_url_without_trailing_slash(copy_sample):
# Warehouse's /legacy upload API requires a trailing slash. Without it the
# request hits a different resource (404 historically, 405 today). See #179.
repo = RepoDetails(
url="https://test.pypi.org/legacy",
username="user",
password="pw",
)
responses.add(responses.POST, "https://test.pypi.org/legacy/", status=200)
td = copy_sample("module1_toml")

with temp_pypirc(pypirc1) as pypirc, patch(
"flit.upload.get_repository", return_value=repo
):
upload.main(td / "pyproject.toml", repo_name="pypi", pypirc_path=pypirc)

assert len(responses.calls) == 2
for call in responses.calls:
assert call.request.url == "https://test.pypi.org/legacy/"


def test_normalize_repository_url():
assert (
upload.normalize_repository_url("https://test.pypi.org/legacy")
== "https://test.pypi.org/legacy/"
)
assert (
upload.normalize_repository_url("https://upload.pypi.org/legacy")
== "https://upload.pypi.org/legacy/"
)
already = "https://upload.pypi.org/legacy/"
assert upload.normalize_repository_url(already) == already
other = "https://pypi.example.com"
assert upload.normalize_repository_url(other) == other
with_query = "https://test.pypi.org/legacy?foo=1"
assert upload.normalize_repository_url(with_query) == "https://test.pypi.org/legacy/?foo=1"


def test_upload_invalid_pypirc_file(copy_sample):
with patch("flit.upload.upload_file"):
td = copy_sample("module1_toml")
Expand Down