diff --git a/src/poetry/publishing/publisher.py b/src/poetry/publishing/publisher.py index f752a0d5ee4..23bddc1eb35 100644 --- a/src/poetry/publishing/publisher.py +++ b/src/poetry/publishing/publisher.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import urllib.parse from typing import TYPE_CHECKING @@ -18,6 +19,16 @@ logger = logging.getLogger(__name__) +def _normalize_legacy_repository_url(url: str) -> str: + parsed = urllib.parse.urlsplit(url) + + if parsed.path.endswith("/legacy") and not parsed.path.endswith("/legacy/"): + parsed = parsed._replace(path=f"{parsed.path}/") + return urllib.parse.urlunsplit(parsed) + + return url + + class Publisher: """ Registers and publishes packages to remote repositories. @@ -52,6 +63,7 @@ def publish( url = self._poetry.config.get(f"repositories.{repository_name}.url") if url is None: raise RuntimeError(f"Repository {repository_name} is not defined") + url = _normalize_legacy_repository_url(url) if not (username and password): # Check if we have a token first diff --git a/tests/publishing/test_publisher.py b/tests/publishing/test_publisher.py index 53f30e75f78..8d4300a91be 100644 --- a/tests/publishing/test_publisher.py +++ b/tests/publishing/test_publisher.py @@ -82,6 +82,31 @@ def test_publish_can_publish_to_given_repository( assert f"Publishing {project_name} ({uploader_version}) to foo" in io.fetch_output() +def test_publish_normalizes_legacy_repository_url_without_trailing_slash( + fixture_dir: FixtureDirGetter, mocker: MockerFixture, config: Config +) -> None: + uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth") + uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") + + poetry = Factory().create_poetry(fixture_dir("sample_project")) + poetry._config = config + poetry.config.merge( + { + "repositories": {"testpypi": {"url": "https://test.pypi.org/legacy"}}, + "http-basic": {"testpypi": {"username": "foo", "password": "bar"}}, + } + ) + + publisher = Publisher(poetry, NullIO()) + publisher.publish("testpypi", None, None) + + assert uploader_auth.call_args == [("foo", "bar")] + assert uploader_upload.call_args == [ + ("https://test.pypi.org/legacy/",), + {"cert": True, "client_cert": None, "dry_run": False, "skip_existing": False}, + ] + + def test_publish_raises_error_for_undefined_repository( fixture_dir: FixtureDirGetter, config: Config ) -> None: