+++ /dev/null
-"""distutils.command.upload\r
-\r
-Implements the Distutils 'upload' subcommand (upload package to PyPI)."""\r
-import os\r
-import socket\r
-import platform\r
-from urllib2 import urlopen, Request, HTTPError\r
-from base64 import standard_b64encode\r
-import urlparse\r
-import cStringIO as StringIO\r
-from hashlib import md5\r
-\r
-from distutils.errors import DistutilsOptionError\r
-from distutils.core import PyPIRCCommand\r
-from distutils.spawn import spawn\r
-from distutils import log\r
-\r
-class upload(PyPIRCCommand):\r
-\r
- description = "upload binary package to PyPI"\r
-\r
- user_options = PyPIRCCommand.user_options + [\r
- ('sign', 's',\r
- 'sign files to upload using gpg'),\r
- ('identity=', 'i', 'GPG identity used to sign files'),\r
- ]\r
-\r
- boolean_options = PyPIRCCommand.boolean_options + ['sign']\r
-\r
- def initialize_options(self):\r
- PyPIRCCommand.initialize_options(self)\r
- self.username = ''\r
- self.password = ''\r
- self.show_response = 0\r
- self.sign = False\r
- self.identity = None\r
-\r
- def finalize_options(self):\r
- PyPIRCCommand.finalize_options(self)\r
- if self.identity and not self.sign:\r
- raise DistutilsOptionError(\r
- "Must use --sign for --identity to have meaning"\r
- )\r
- config = self._read_pypirc()\r
- if config != {}:\r
- self.username = config['username']\r
- self.password = config['password']\r
- self.repository = config['repository']\r
- self.realm = config['realm']\r
-\r
- # getting the password from the distribution\r
- # if previously set by the register command\r
- if not self.password and self.distribution.password:\r
- self.password = self.distribution.password\r
-\r
- def run(self):\r
- if not self.distribution.dist_files:\r
- raise DistutilsOptionError("No dist file created in earlier command")\r
- for command, pyversion, filename in self.distribution.dist_files:\r
- self.upload_file(command, pyversion, filename)\r
-\r
- def upload_file(self, command, pyversion, filename):\r
- # Makes sure the repository URL is compliant\r
- schema, netloc, url, params, query, fragments = \\r
- urlparse.urlparse(self.repository)\r
- if params or query or fragments:\r
- raise AssertionError("Incompatible url %s" % self.repository)\r
-\r
- if schema not in ('http', 'https'):\r
- raise AssertionError("unsupported schema " + schema)\r
-\r
- # Sign if requested\r
- if self.sign:\r
- gpg_args = ["gpg", "--detach-sign", "-a", filename]\r
- if self.identity:\r
- gpg_args[2:2] = ["--local-user", self.identity]\r
- spawn(gpg_args,\r
- dry_run=self.dry_run)\r
-\r
- # Fill in the data - send all the meta-data in case we need to\r
- # register a new release\r
- f = open(filename,'rb')\r
- try:\r
- content = f.read()\r
- finally:\r
- f.close()\r
- meta = self.distribution.metadata\r
- data = {\r
- # action\r
- ':action': 'file_upload',\r
- 'protcol_version': '1',\r
-\r
- # identify release\r
- 'name': meta.get_name(),\r
- 'version': meta.get_version(),\r
-\r
- # file content\r
- 'content': (os.path.basename(filename),content),\r
- 'filetype': command,\r
- 'pyversion': pyversion,\r
- 'md5_digest': md5(content).hexdigest(),\r
-\r
- # additional meta-data\r
- 'metadata_version' : '1.0',\r
- 'summary': meta.get_description(),\r
- 'home_page': meta.get_url(),\r
- 'author': meta.get_contact(),\r
- 'author_email': meta.get_contact_email(),\r
- 'license': meta.get_licence(),\r
- 'description': meta.get_long_description(),\r
- 'keywords': meta.get_keywords(),\r
- 'platform': meta.get_platforms(),\r
- 'classifiers': meta.get_classifiers(),\r
- 'download_url': meta.get_download_url(),\r
- # PEP 314\r
- 'provides': meta.get_provides(),\r
- 'requires': meta.get_requires(),\r
- 'obsoletes': meta.get_obsoletes(),\r
- }\r
- comment = ''\r
- if command == 'bdist_rpm':\r
- dist, version, id = platform.dist()\r
- if dist:\r
- comment = 'built for %s %s' % (dist, version)\r
- elif command == 'bdist_dumb':\r
- comment = 'built for %s' % platform.platform(terse=1)\r
- data['comment'] = comment\r
-\r
- if self.sign:\r
- data['gpg_signature'] = (os.path.basename(filename) + ".asc",\r
- open(filename+".asc").read())\r
-\r
- # set up the authentication\r
- auth = "Basic " + standard_b64encode(self.username + ":" +\r
- self.password)\r
-\r
- # Build up the MIME payload for the POST data\r
- boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'\r
- sep_boundary = '\n--' + boundary\r
- end_boundary = sep_boundary + '--'\r
- body = StringIO.StringIO()\r
- for key, value in data.items():\r
- # handle multiple entries for the same name\r
- if not isinstance(value, list):\r
- value = [value]\r
- for value in value:\r
- if isinstance(value, tuple):\r
- fn = ';filename="%s"' % value[0]\r
- value = value[1]\r
- else:\r
- fn = ""\r
-\r
- body.write(sep_boundary)\r
- body.write('\nContent-Disposition: form-data; name="%s"'%key)\r
- body.write(fn)\r
- body.write("\n\n")\r
- body.write(value)\r
- if value and value[-1] == '\r':\r
- body.write('\n') # write an extra newline (lurve Macs)\r
- body.write(end_boundary)\r
- body.write("\n")\r
- body = body.getvalue()\r
-\r
- self.announce("Submitting %s to %s" % (filename, self.repository), log.INFO)\r
-\r
- # build the Request\r
- headers = {'Content-type':\r
- 'multipart/form-data; boundary=%s' % boundary,\r
- 'Content-length': str(len(body)),\r
- 'Authorization': auth}\r
-\r
- request = Request(self.repository, data=body,\r
- headers=headers)\r
- # send the data\r
- try:\r
- result = urlopen(request)\r
- status = result.getcode()\r
- reason = result.msg\r
- if self.show_response:\r
- msg = '\n'.join(('-' * 75, r.read(), '-' * 75))\r
- self.announce(msg, log.INFO)\r
- except socket.error, e:\r
- self.announce(str(e), log.ERROR)\r
- return\r
- except HTTPError, e:\r
- status = e.code\r
- reason = e.msg\r
-\r
- if status == 200:\r
- self.announce('Server response (%s): %s' % (status, reason),\r
- log.INFO)\r
- else:\r
- self.announce('Upload failed (%s): %s' % (status, reason),\r
- log.ERROR)\r