Fixed #1885: --formats=tar,gztar was not working properly in the sdist command
[python.git] / Lib / distutils / command / upload.py
blobe30347e507b0eabb517e4dbf2d343ab79b3870c9
1 """distutils.command.upload
3 Implements the Distutils 'upload' subcommand (upload package to PyPI)."""
5 from distutils.errors import *
6 from distutils.core import PyPIRCCommand
7 from distutils.spawn import spawn
8 from distutils import log
9 from hashlib import md5
10 import os
11 import socket
12 import platform
13 import httplib
14 import base64
15 import urlparse
16 import cStringIO as StringIO
17 from ConfigParser import ConfigParser
20 class upload(PyPIRCCommand):
22 description = "upload binary package to PyPI"
24 user_options = PyPIRCCommand.user_options + [
25 ('sign', 's',
26 'sign files to upload using gpg'),
27 ('identity=', 'i', 'GPG identity used to sign files'),
30 boolean_options = PyPIRCCommand.boolean_options + ['sign']
32 def initialize_options(self):
33 PyPIRCCommand.initialize_options(self)
34 self.username = ''
35 self.password = ''
36 self.show_response = 0
37 self.sign = False
38 self.identity = None
40 def finalize_options(self):
41 PyPIRCCommand.finalize_options(self)
42 if self.identity and not self.sign:
43 raise DistutilsOptionError(
44 "Must use --sign for --identity to have meaning"
46 config = self._read_pypirc()
47 if config != {}:
48 self.username = config['username']
49 self.password = config['password']
50 self.repository = config['repository']
51 self.realm = config['realm']
53 # getting the password from the distribution
54 # if previously set by the register command
55 if not self.password and self.distribution.password:
56 self.password = self.distribution.password
58 def run(self):
59 if not self.distribution.dist_files:
60 raise DistutilsOptionError("No dist file created in earlier command")
61 for command, pyversion, filename in self.distribution.dist_files:
62 self.upload_file(command, pyversion, filename)
64 def upload_file(self, command, pyversion, filename):
65 # Sign if requested
66 if self.sign:
67 gpg_args = ["gpg", "--detach-sign", "-a", filename]
68 if self.identity:
69 gpg_args[2:2] = ["--local-user", self.identity]
70 spawn(gpg_args,
71 dry_run=self.dry_run)
73 # Fill in the data - send all the meta-data in case we need to
74 # register a new release
75 content = open(filename,'rb').read()
76 meta = self.distribution.metadata
77 data = {
78 # action
79 ':action': 'file_upload',
80 'protcol_version': '1',
82 # identify release
83 'name': meta.get_name(),
84 'version': meta.get_version(),
86 # file content
87 'content': (os.path.basename(filename),content),
88 'filetype': command,
89 'pyversion': pyversion,
90 'md5_digest': md5(content).hexdigest(),
92 # additional meta-data
93 'metadata_version' : '1.0',
94 'summary': meta.get_description(),
95 'home_page': meta.get_url(),
96 'author': meta.get_contact(),
97 'author_email': meta.get_contact_email(),
98 'license': meta.get_licence(),
99 'description': meta.get_long_description(),
100 'keywords': meta.get_keywords(),
101 'platform': meta.get_platforms(),
102 'classifiers': meta.get_classifiers(),
103 'download_url': meta.get_download_url(),
104 # PEP 314
105 'provides': meta.get_provides(),
106 'requires': meta.get_requires(),
107 'obsoletes': meta.get_obsoletes(),
109 comment = ''
110 if command == 'bdist_rpm':
111 dist, version, id = platform.dist()
112 if dist:
113 comment = 'built for %s %s' % (dist, version)
114 elif command == 'bdist_dumb':
115 comment = 'built for %s' % platform.platform(terse=1)
116 data['comment'] = comment
118 if self.sign:
119 data['gpg_signature'] = (os.path.basename(filename) + ".asc",
120 open(filename+".asc").read())
122 # set up the authentication
123 auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()
125 # Build up the MIME payload for the POST data
126 boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
127 sep_boundary = '\n--' + boundary
128 end_boundary = sep_boundary + '--'
129 body = StringIO.StringIO()
130 for key, value in data.items():
131 # handle multiple entries for the same name
132 if type(value) != type([]):
133 value = [value]
134 for value in value:
135 if type(value) is tuple:
136 fn = ';filename="%s"' % value[0]
137 value = value[1]
138 else:
139 fn = ""
140 value = str(value)
141 body.write(sep_boundary)
142 body.write('\nContent-Disposition: form-data; name="%s"'%key)
143 body.write(fn)
144 body.write("\n\n")
145 body.write(value)
146 if value and value[-1] == '\r':
147 body.write('\n') # write an extra newline (lurve Macs)
148 body.write(end_boundary)
149 body.write("\n")
150 body = body.getvalue()
152 self.announce("Submitting %s to %s" % (filename, self.repository), log.INFO)
154 # build the Request
155 # We can't use urllib2 since we need to send the Basic
156 # auth right with the first request
157 schema, netloc, url, params, query, fragments = \
158 urlparse.urlparse(self.repository)
159 assert not params and not query and not fragments
160 if schema == 'http':
161 http = httplib.HTTPConnection(netloc)
162 elif schema == 'https':
163 http = httplib.HTTPSConnection(netloc)
164 else:
165 raise AssertionError, "unsupported schema "+schema
167 data = ''
168 loglevel = log.INFO
169 try:
170 http.connect()
171 http.putrequest("POST", url)
172 http.putheader('Content-type',
173 'multipart/form-data; boundary=%s'%boundary)
174 http.putheader('Content-length', str(len(body)))
175 http.putheader('Authorization', auth)
176 http.endheaders()
177 http.send(body)
178 except socket.error, e:
179 self.announce(str(e), log.ERROR)
180 return
182 r = http.getresponse()
183 if r.status == 200:
184 self.announce('Server response (%s): %s' % (r.status, r.reason),
185 log.INFO)
186 else:
187 self.announce('Upload failed (%s): %s' % (r.status, r.reason),
188 log.ERROR)
189 if self.show_response:
190 print '-'*75, r.read(), '-'*75