You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
5.0 KiB
135 lines
5.0 KiB
5 years ago
|
# Copyright 2013 Donald Stufft
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
import argparse
|
||
|
import os.path
|
||
|
|
||
|
from twine.commands import _find_dists
|
||
|
from twine.package import PackageFile
|
||
|
from twine import exceptions
|
||
|
from twine import settings
|
||
|
from twine import utils
|
||
|
|
||
|
|
||
|
def skip_upload(response, skip_existing, package):
|
||
|
filename = package.basefilename
|
||
|
msg_400 = (
|
||
|
# Old PyPI message:
|
||
|
f'A file named "{filename}" already exists for',
|
||
|
# Warehouse message:
|
||
|
'File already exists',
|
||
|
# Nexus Repository OSS message:
|
||
|
'Repository does not allow updating assets',
|
||
|
)
|
||
|
msg_403 = 'Not enough permissions to overwrite artifact'
|
||
|
# NOTE(sigmavirus24): PyPI presently returns a 400 status code with the
|
||
|
# error message in the reason attribute. Other implementations return a
|
||
|
# 409 or 403 status code. We only want to skip an upload if:
|
||
|
# 1. The user has told us to skip existing packages (skip_existing is
|
||
|
# True) AND
|
||
|
# 2. a) The response status code is 409 OR
|
||
|
# 2. b) The response status code is 400 AND it has a reason that matches
|
||
|
# what we expect PyPI or Nexus OSS to return to us. OR
|
||
|
# 2. c) The response status code is 403 AND the text matches what we
|
||
|
# expect Artifactory to return to us.
|
||
|
return (skip_existing and (response.status_code == 409 or
|
||
|
(response.status_code == 400 and
|
||
|
response.reason.startswith(msg_400)) or
|
||
|
(response.status_code == 403 and msg_403 in response.text)))
|
||
|
|
||
|
|
||
|
def upload(upload_settings, dists):
|
||
|
dists = _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")]
|
||
|
upload_settings.check_repository_url()
|
||
|
repository_url = upload_settings.repository_config['repository']
|
||
|
|
||
|
print(f"Uploading distributions to {repository_url}")
|
||
|
|
||
|
repository = upload_settings.create_repository()
|
||
|
uploaded_packages = []
|
||
|
|
||
|
for filename in uploads:
|
||
|
package = PackageFile.from_filename(filename, upload_settings.comment)
|
||
|
skip_message = (
|
||
|
" Skipping {} because it appears to already exist".format(
|
||
|
package.basefilename)
|
||
|
)
|
||
|
|
||
|
# Note: The skip_existing check *needs* to be first, because otherwise
|
||
|
# we're going to generate extra HTTP requests against a hardcoded
|
||
|
# URL for no reason.
|
||
|
if (upload_settings.skip_existing and
|
||
|
repository.package_is_uploaded(package)):
|
||
|
print(skip_message)
|
||
|
continue
|
||
|
|
||
|
signed_name = package.signed_basefilename
|
||
|
if signed_name in signatures:
|
||
|
package.add_gpg_signature(signatures[signed_name], signed_name)
|
||
|
elif upload_settings.sign:
|
||
|
package.sign(upload_settings.sign_with, upload_settings.identity)
|
||
|
|
||
|
resp = repository.upload(package)
|
||
|
|
||
|
# Bug 92. If we get a redirect we should abort because something seems
|
||
|
# funky. The behaviour is not well defined and redirects being issued
|
||
|
# by PyPI should never happen in reality. This should catch malicious
|
||
|
# redirects as well.
|
||
|
if resp.is_redirect:
|
||
|
raise exceptions.RedirectDetected.from_args(
|
||
|
repository_url,
|
||
|
resp.headers["location"],
|
||
|
)
|
||
|
|
||
|
if skip_upload(resp, upload_settings.skip_existing, package):
|
||
|
print(skip_message)
|
||
|
continue
|
||
|
|
||
|
utils.check_status_code(resp, upload_settings.verbose)
|
||
|
|
||
|
uploaded_packages.append(package)
|
||
|
|
||
|
release_urls = repository.release_urls(uploaded_packages)
|
||
|
if release_urls:
|
||
|
print('\nView at:')
|
||
|
for url in release_urls:
|
||
|
print(url)
|
||
|
|
||
|
# Bug 28. Try to silence a ResourceWarning by clearing the connection
|
||
|
# pool.
|
||
|
repository.close()
|
||
|
|
||
|
|
||
|
def main(args):
|
||
|
parser = argparse.ArgumentParser(prog="twine upload")
|
||
|
settings.Settings.register_argparse_arguments(parser)
|
||
|
parser.add_argument(
|
||
|
"dists",
|
||
|
nargs="+",
|
||
|
metavar="dist",
|
||
|
help="The distribution files to upload to the repository "
|
||
|
"(package index). Usually dist/* . May additionally contain "
|
||
|
"a .asc file to include an existing signature with the "
|
||
|
"file upload.",
|
||
|
)
|
||
|
|
||
|
args = parser.parse_args(args)
|
||
|
upload_settings = settings.Settings.from_argparse(args)
|
||
|
|
||
|
# Call the upload function with the arguments from the command line
|
||
|
return upload(upload_settings, args.dists)
|