Skip to content

Commit 2ac2286

Browse files
committed
Add --skip-existing flag to upload command
This new flag allows users to force twine to skip existing files when uploading to PyPI. For example: ~/s/gh3.py git:develop ❯❯❯ twine upload --skip-existing dist/* Uploading distributions to https://pypi.python.org/pypi Uploading github3.py-1.0.0a2-py2.py3-none-any.whl Skipping github3.py-1.0.0a2-py2.py3-none-any.whl because it appears to already exist Uploading github3.py-1.0.0a2.tar.gz Skipping github3.py-1.0.0a2.tar.gz because it appears to already exist Closes #115
1 parent fdad88d commit 2ac2286

4 files changed

Lines changed: 48 additions & 7 deletions

File tree

15.4 KB
Binary file not shown.

tests/test_upload.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
import os
1717
import textwrap
1818

19+
import mock
1920
import pretend
2021
import pytest
2122

2223
from twine.commands import upload
2324

2425

26+
WHEEL_FIXTURE = 'tests/fixtures/twine-1.5.0-py2.py3-none-any.whl'
27+
28+
2529
def test_ensure_wheel_files_uploaded_first():
2630
files = upload.group_wheel_files_first(["twine/foo.py",
2731
"twine/first.whl",
@@ -92,11 +96,36 @@ def test_get_config_old_format(tmpdir):
9296
try:
9397
upload.upload(dists="foo", repository="pypi", sign=None, identity=None,
9498
username=None, password=None, comment=None,
95-
sign_with=None, config_file=pypirc)
99+
sign_with=None, config_file=pypirc, skip_existing=False)
96100
except KeyError as err:
97101
assert err.args[0] == (
98102
"Missing 'pypi' section from the configuration file.\n"
99103
"Maybe you have a out-dated '{0}' format?\n"
100104
"more info: "
101105
"https://docs.python.org/distutils/packageindex.html#pypirc\n"
102106
).format(pypirc)
107+
108+
109+
def test_skip_existing_skips_files_already_on_PyPI(monkeypatch):
110+
response = mock.Mock(spec=upload.requests.models.Response)
111+
session = mock.Mock(spec=upload.requests.sessions.Session)
112+
113+
monkeypatch.setattr(upload.requests, 'session', lambda: session)
114+
monkeypatch.setattr(upload.os.path, 'exists', lambda args: True)
115+
116+
session.post.return_value = response
117+
response.status_code = 400
118+
response.is_redirect = False
119+
120+
upload.upload(
121+
dists=[WHEEL_FIXTURE],
122+
repository="pypi",
123+
sign=None,
124+
identity=None,
125+
username='username',
126+
password='password',
127+
comment=None,
128+
sign_with=None,
129+
config_file='',
130+
skip_existing=True
131+
)

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ envlist = py26,py27,pypy,py32,py33,py34,docs,pep8
44
[testenv]
55
deps =
66
coverage
7+
mock
78
pretend
89
pytest
910
commands =

twine/commands/upload.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def sign_file(sign_with, filename, identity):
9191

9292

9393
def upload(dists, repository, sign, identity, username, password, comment,
94-
sign_with, config_file):
94+
sign_with, config_file, skip_existing):
9595
# Check that a nonsensical option wasn't given
9696
if not sign and identity:
9797
raise ValueError("sign must be given along with identity")
@@ -214,15 +214,16 @@ def upload(dists, repository, sign, identity, username, password, comment,
214214

215215
data["md5_digest"] = md5_hash.hexdigest()
216216

217-
signed_name = os.path.basename(filename) + ".asc"
217+
basefilename = os.path.basename(filename)
218+
signed_name = basefilename + ".asc"
218219
if signed_name in signatures:
219220
with open(signatures[signed_name], "rb") as gpg:
220221
data["gpg_signature"] = (signed_name, gpg.read())
221222
elif sign:
222223
with open(filename + ".asc", "rb") as gpg:
223224
data["gpg_signature"] = (signed_name, gpg.read())
224225

225-
print("Uploading {0}".format(os.path.basename(filename)))
226+
print("Uploading {0}".format(basefilename))
226227

227228
data_to_send = []
228229
for key, value in data.items():
@@ -235,7 +236,7 @@ def upload(dists, repository, sign, identity, username, password, comment,
235236
with open(filename, "rb") as fp:
236237
data_to_send.append((
237238
"content",
238-
(os.path.basename(filename), fp, "application/octet-stream"),
239+
(basefilename, fp, "application/octet-stream"),
239240
))
240241
encoder = MultipartEncoder(data_to_send)
241242
resp = session.post(
@@ -257,10 +258,14 @@ def upload(dists, repository, sign, identity, username, password, comment,
257258
if resp.is_redirect:
258259
raise exc.RedirectDetected(
259260
('"{0}" attempted to redirect to "{1}" during upload.'
260-
' Aborting...').format(config["respository"],
261+
' Aborting...').format(config["repository"],
261262
resp.headers["location"]))
262263
# Otherwise, raise an HTTPError based on the status code.
263-
resp.raise_for_status()
264+
if resp.status_code == 400 and skip_existing:
265+
print(" Skipping {0} because it appears to already exist".format(
266+
basefilename))
267+
else:
268+
resp.raise_for_status()
264269

265270

266271
def main(args):
@@ -302,6 +307,12 @@ def main(args):
302307
default="~/.pypirc",
303308
help="The .pypirc config file to use",
304309
)
310+
parser.add_argument(
311+
"--skip-existing",
312+
default=False,
313+
action="store_true",
314+
help="Continue uploading files if one already exists",
315+
)
305316
parser.add_argument(
306317
"dists",
307318
nargs="+",

0 commit comments

Comments
 (0)