diff --git a/tests/test_repository.py b/tests/test_repository.py index 2c6bd454..43f7cb8e 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -269,6 +269,49 @@ def test_upload_retry(tmpdir, default_repo, caplog): ] +def test_upload_retry_on_connection_error(tmpdir, default_repo, caplog): + default_repo.disable_progress_bar = True + + default_repo.session = pretend.stub( + post=pretend.raiser(requests.exceptions.ConnectionError) + ) + + fakefile = tmpdir.join("fake.whl") + fakefile.write(".") + + package = pretend.stub( + safe_name="fake", + metadata=pretend.stub(version="2.12.0"), + basefilename="fake.whl", + filename=str(fakefile), + metadata_dictionary=lambda: {"name": "fake"}, + ) + + # Upload with default max_redirects of 5 + default_repo.upload(package) + + assert caplog.messages == [ + ( + 'ConnectionError raised.\n' + f"Package upload appears to have failed. Retry {i} of 5." + ) + for i in range(1, 6) + ] + + caplog.clear() + + # Upload with custom max_redirects of 3 + default_repo.upload(package, 3) + + assert caplog.messages == [ + ( + 'ConnectionError raised.\n' + f"Package upload appears to have failed. Retry {i} of 3." + ) + for i in range(1, 4) + ] + + @pytest.mark.parametrize( "package_meta,repository_url,release_urls", [ diff --git a/twine/repository.py b/twine/repository.py index 04f68504..c12eae23 100644 --- a/twine/repository.py +++ b/twine/repository.py @@ -183,21 +183,31 @@ def upload( ) -> requests.Response: number_of_redirects = 0 while number_of_redirects < max_redirects: - resp = self._upload(package) - - if resp.status_code == requests.codes.OK: - return resp - if 500 <= resp.status_code < 600: + try: + resp = self._upload(package) + except ( + requests.exceptions.ConnectionError, + requests.exceptions.ConnectTimeout, + ) as exc: number_of_redirects += 1 logger.warning( - f'Received "{resp.status_code}: {resp.reason}"' + f"{exc.__class__.__name__} raised." "\nPackage upload appears to have failed." f" Retry {number_of_redirects} of {max_redirects}." ) + continue else: - return resp - - return resp + if resp.status_code == requests.codes.OK: + return resp + if 500 <= resp.status_code < 600: + number_of_redirects += 1 + logger.warning( + f'Received "{resp.status_code}: {resp.reason}"' + "\nPackage upload appears to have failed." + f" Retry {number_of_redirects} of {max_redirects}." + ) + else: + return resp def package_is_uploaded( self, package: package_file.PackageFile, bypass_cache: bool = False