From cbef2949856f5e0957be1cbdfdc92325f9ae9f1e Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sat, 27 Oct 2018 17:21:11 -0400 Subject: Add upload_file to setuptools.command.upload --- setuptools/command/upload.py | 147 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) (limited to 'setuptools/command/upload.py') diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 72f24d8f..dae7d74d 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -1,14 +1,24 @@ +import io +import os +import hashlib import getpass + +from base64 import standard_b64encode + from distutils import log from distutils.command import upload as orig +from distutils.errors import DistutilsError + +from six.moves.urllib.request import urlopen, Request +from six.moves.urllib.error import HTTPError +from six.moves.urllib.parse import urlparse class upload(orig.upload): """ Override default upload behavior to obtain password in a variety of different ways. """ - def run(self): try: orig.upload.run(self) @@ -33,6 +43,141 @@ class upload(orig.upload): self._prompt_for_password() ) + def upload_file(self, command, pyversion, filename): + # Makes sure the repository URL is compliant + schema, netloc, url, params, query, fragments = \ + urlparse(self.repository) + if params or query or fragments: + raise AssertionError("Incompatible url %s" % self.repository) + + if schema not in ('http', 'https'): + raise AssertionError("unsupported schema " + schema) + + # Sign if requested + if self.sign: + gpg_args = ["gpg", "--detach-sign", "-a", filename] + if self.identity: + gpg_args[2:2] = ["--local-user", self.identity] + spawn(gpg_args, + dry_run=self.dry_run) + + # Fill in the data - send all the meta-data in case we need to + # register a new release + with open(filename, 'rb') as f: + content = f.read() + + meta = self.distribution.metadata + + data = { + # action + ':action': 'file_upload', + 'protocol_version': '1', + + # identify release + 'name': meta.get_name(), + 'version': meta.get_version(), + + # file content + 'content': (os.path.basename(filename),content), + 'filetype': command, + 'pyversion': pyversion, + 'md5_digest': hashlib.md5(content).hexdigest(), + + # additional meta-data + 'metadata_version': '1.0', + 'summary': meta.get_description(), + 'home_page': meta.get_url(), + 'author': meta.get_contact(), + 'author_email': meta.get_contact_email(), + 'license': meta.get_licence(), + 'description': meta.get_long_description(), + 'keywords': meta.get_keywords(), + 'platform': meta.get_platforms(), + 'classifiers': meta.get_classifiers(), + 'download_url': meta.get_download_url(), + # PEP 314 + 'provides': meta.get_provides(), + 'requires': meta.get_requires(), + 'obsoletes': meta.get_obsoletes(), + } + comment = '' + if command == 'bdist_rpm': + dist, version, id = platform.dist() + if dist: + comment = 'built for %s %s' % (dist, version) + elif command == 'bdist_dumb': + comment = 'built for %s' % platform.platform(terse=1) + data['comment'] = comment + + if self.sign: + data['gpg_signature'] = (os.path.basename(filename) + ".asc", + open(filename+".asc", "rb").read()) + + # set up the authentication + user_pass = (self.username + ":" + self.password).encode('ascii') + # The exact encoding of the authentication string is debated. + # Anyway PyPI only accepts ascii for both username or password. + auth = "Basic " + standard_b64encode(user_pass).decode('ascii') + + # Build up the MIME payload for the POST data + boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = b'\r\n--' + boundary.encode('ascii') + end_boundary = sep_boundary + b'--\r\n' + body = io.BytesIO() + for key, value in data.items(): + title = '\r\nContent-Disposition: form-data; name="%s"' % key + # handle multiple entries for the same name + if not isinstance(value, list): + value = [value] + for value in value: + if type(value) is tuple: + title += '; filename="%s"' % value[0] + value = value[1] + else: + value = str(value).encode('utf-8') + body.write(sep_boundary) + body.write(title.encode('utf-8')) + body.write(b"\r\n\r\n") + body.write(value) + body.write(end_boundary) + body = body.getvalue() + + msg = "Submitting %s to %s" % (filename, self.repository) + self.announce(msg, log.INFO) + + # build the Request + headers = { + 'Content-type': 'multipart/form-data; boundary=%s' % boundary, + 'Content-length': str(len(body)), + 'Authorization': auth, + } + + request = Request(self.repository, data=body, + headers=headers) + # send the data + try: + result = urlopen(request) + status = result.getcode() + reason = result.msg + except HTTPError as e: + status = e.code + reason = e.msg + except OSError as e: + self.announce(str(e), log.ERROR) + raise + + if status == 200: + self.announce('Server response (%s): %s' % (status, reason), + log.INFO) + if self.show_response: + text = self._read_pypi_response(result) + msg = '\n'.join(('-' * 75, text, '-' * 75)) + self.announce(msg, log.INFO) + else: + msg = 'Upload failed (%s): %s' % (status, reason) + self.announce(msg, log.ERROR) + raise DistutilsError(msg) + def _load_password_from_keyring(self): """ Attempt to load password from keyring. Suppress Exceptions. -- cgit v1.2.1 From 33185837dbc1f75f7894b9cbc3e56c1c6a868c4c Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 5 Nov 2018 10:26:50 -0500 Subject: Use get_metadata_version in upload_file Previously this value was hard-coded to '1.0', which was inaccurate for many packages. Fixes #1381 --- setuptools/command/upload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'setuptools/command/upload.py') diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index dae7d74d..01fa026c 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -84,7 +84,7 @@ class upload(orig.upload): 'md5_digest': hashlib.md5(content).hexdigest(), # additional meta-data - 'metadata_version': '1.0', + 'metadata_version': str(meta.get_metadata_version()), 'summary': meta.get_description(), 'home_page': meta.get_url(), 'author': meta.get_contact(), -- cgit v1.2.1 From b5c9c5f42db36a07dc27d39c1be2a311cc567d99 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Wed, 7 Nov 2018 16:23:13 -0500 Subject: Fix gpg signature code in upload_file This fixes an issue where `distutils.spawn.spawn` was not available in the ported upload_file, which is only used when signing the data. This also adds a test that the gpg signature command is invoked and included in the uploaded data. --- setuptools/command/upload.py | 1 + 1 file changed, 1 insertion(+) (limited to 'setuptools/command/upload.py') diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 01fa026c..1851ed28 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -7,6 +7,7 @@ from base64 import standard_b64encode from distutils import log from distutils.command import upload as orig +from distutils.spawn import spawn from distutils.errors import DistutilsError -- cgit v1.2.1 From 727dd60f6a11f38d165250c543ba135687fa2e61 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Wed, 7 Nov 2018 16:41:19 -0500 Subject: Fix bdist_rpm and bdist_dumb in upload_file This fixes uploads when bdist_rpm or bdist_dumb are the command, both of which insert a comment about what platform they are built for. --- setuptools/command/upload.py | 1 + 1 file changed, 1 insertion(+) (limited to 'setuptools/command/upload.py') diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 1851ed28..99d86011 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -2,6 +2,7 @@ import io import os import hashlib import getpass +import platform from base64 import standard_b64encode -- cgit v1.2.1 From fe2c9e4292699635c91174bc049aefe81bf6116c Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Wed, 7 Nov 2018 17:07:58 -0500 Subject: Fix show_response behavior on Python 2 The `upload.show_response` feature was not added until Python 3. Rather than backport it, it is now enabled only if supported. This also adds a "smoke test" for the feature. --- setuptools/command/upload.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'setuptools/command/upload.py') diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 99d86011..f57fe796 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -172,9 +172,11 @@ class upload(orig.upload): self.announce('Server response (%s): %s' % (status, reason), log.INFO) if self.show_response: - text = self._read_pypi_response(result) - msg = '\n'.join(('-' * 75, text, '-' * 75)) - self.announce(msg, log.INFO) + text = getattr(self, '_read_pypi_response', + lambda x: None)(result) + if text is not None: + msg = '\n'.join(('-' * 75, text, '-' * 75)) + self.announce(msg, log.INFO) else: msg = 'Upload failed (%s): %s' % (status, reason) self.announce(msg, log.ERROR) -- cgit v1.2.1 From 2b5b91332a01c665cab77ad7962e87525850d7f5 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 12 Nov 2018 10:08:55 -0500 Subject: Remove bdist_rpm and bdist_dumb comment This comment is not used anywhere and `platform.dist()` is deprecated. See CPython PR #10414: https://github.com/python/cpython/pull/10414 and bpo-35186: https://bugs.python.org/issue35186 --- setuptools/command/upload.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'setuptools/command/upload.py') diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index f57fe796..3b8cab5e 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -102,14 +102,8 @@ class upload(orig.upload): 'requires': meta.get_requires(), 'obsoletes': meta.get_obsoletes(), } - comment = '' - if command == 'bdist_rpm': - dist, version, id = platform.dist() - if dist: - comment = 'built for %s %s' % (dist, version) - elif command == 'bdist_dumb': - comment = 'built for %s' % platform.platform(terse=1) - data['comment'] = comment + + data['comment'] = '' if self.sign: data['gpg_signature'] = (os.path.basename(filename) + ".asc", -- cgit v1.2.1 From ba7698287094f7274ae7cbabaf6baedc175ac213 Mon Sep 17 00:00:00 2001 From: Oleg Sharov Date: Tue, 13 Nov 2018 12:52:43 +0400 Subject: import internal version of six --- setuptools/command/upload.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'setuptools/command/upload.py') diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 3b8cab5e..dd17f7a9 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -12,9 +12,9 @@ from distutils.spawn import spawn from distutils.errors import DistutilsError -from six.moves.urllib.request import urlopen, Request -from six.moves.urllib.error import HTTPError -from six.moves.urllib.parse import urlparse +from setuptools.extern.six.moves.urllib.request import urlopen, Request +from setuptools.extern.six.moves.urllib.error import HTTPError +from setuptools.extern.six.moves.urllib.parse import urlparse class upload(orig.upload): """ -- cgit v1.2.1 From 0c9624fd5ee5abe3fb0d1e3dfa68a9cbaf261aed Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Dec 2018 13:47:21 -0500 Subject: Feed the hobgoblins (delint). --- setuptools/command/upload.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'setuptools/command/upload.py') diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index dd17f7a9..6db8888b 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -2,7 +2,6 @@ import io import os import hashlib import getpass -import platform from base64 import standard_b64encode @@ -16,6 +15,7 @@ from setuptools.extern.six.moves.urllib.request import urlopen, Request from setuptools.extern.six.moves.urllib.error import HTTPError from setuptools.extern.six.moves.urllib.parse import urlparse + class upload(orig.upload): """ Override default upload behavior to obtain password @@ -80,7 +80,7 @@ class upload(orig.upload): 'version': meta.get_version(), # file content - 'content': (os.path.basename(filename),content), + 'content': (os.path.basename(filename), content), 'filetype': command, 'pyversion': pyversion, 'md5_digest': hashlib.md5(content).hexdigest(), -- cgit v1.2.1