blob: 3b5a0fc9d9f3c1b29955fcdfccb07e172c04ed81 [file] [log] [blame]
Martin v. Löwis55f1bb82005-03-21 20:56:35 +00001"""distutils.command.upload
2
3Implements the Distutils 'upload' subcommand (upload package to PyPI)."""
4
Martin v. Löwis98858c92005-03-21 21:00:59 +00005from distutils.errors import *
6from distutils.core import Command
Martin v. Löwisf74b9232005-03-22 15:51:14 +00007from distutils.spawn import spawn
Martin v. Löwis98858c92005-03-21 21:00:59 +00008from distutils import log
Martin v. Löwisf74b9232005-03-22 15:51:14 +00009from md5 import md5
Martin v. Löwis98858c92005-03-21 21:00:59 +000010import os
Martin v. Löwisca5d8fe2005-03-24 19:40:57 +000011import socket
Martin v. Löwis98858c92005-03-21 21:00:59 +000012import platform
13import ConfigParser
14import httplib
15import base64
16import urlparse
17import cStringIO as StringIO
18
19class upload(Command):
20
21 description = "upload binary package to PyPI"
22
23 DEFAULT_REPOSITORY = 'http://www.python.org/pypi'
24
25 user_options = [
26 ('repository=', 'r',
27 "url of repository [default: %s]" % DEFAULT_REPOSITORY),
28 ('show-response', None,
29 'display full response text from server'),
Martin v. Löwisf74b9232005-03-22 15:51:14 +000030 ('sign', 's',
31 'sign files to upload using gpg'),
Martin v. Löwis98858c92005-03-21 21:00:59 +000032 ]
Martin v. Löwisf74b9232005-03-22 15:51:14 +000033 boolean_options = ['show-response', 'sign']
Martin v. Löwis98858c92005-03-21 21:00:59 +000034
35 def initialize_options(self):
36 self.username = ''
37 self.password = ''
38 self.repository = ''
39 self.show_response = 0
Martin v. Löwisf74b9232005-03-22 15:51:14 +000040 self.sign = False
Martin v. Löwis98858c92005-03-21 21:00:59 +000041
42 def finalize_options(self):
43 if os.environ.has_key('HOME'):
44 rc = os.path.join(os.environ['HOME'], '.pypirc')
45 if os.path.exists(rc):
46 self.announce('Using PyPI login from %s' % rc)
47 config = ConfigParser.ConfigParser({
48 'username':'',
49 'password':'',
50 'repository':''})
51 config.read(rc)
52 if not self.repository:
53 self.repository = config.get('server-login', 'repository')
54 if not self.username:
55 self.username = config.get('server-login', 'username')
56 if not self.password:
57 self.password = config.get('server-login', 'password')
58 if not self.repository:
59 self.repository = self.DEFAULT_REPOSITORY
60
61 def run(self):
62 if not self.distribution.dist_files:
63 raise DistutilsOptionError("No dist file created in earlier command")
Martin v. Löwis98da5622005-03-23 18:54:36 +000064 for command, pyversion, filename in self.distribution.dist_files:
65 self.upload_file(command, pyversion, filename)
Martin v. Löwis98858c92005-03-21 21:00:59 +000066
Martin v. Löwis98da5622005-03-23 18:54:36 +000067 def upload_file(self, command, pyversion, filename):
Martin v. Löwisf74b9232005-03-22 15:51:14 +000068 # Sign if requested
69 if self.sign:
Martin v. Löwis8d121582005-03-22 23:02:54 +000070 spawn(("gpg", "--detach-sign", "-a", filename),
Martin v. Löwisf74b9232005-03-22 15:51:14 +000071 dry_run=self.dry_run)
Martin v. Löwis98858c92005-03-21 21:00:59 +000072
73 # Fill in the data
Phillip J. Eby5cb78462005-07-07 15:36:20 +000074 content = open(filename,'rb').read()
Martin v. Löwis98858c92005-03-21 21:00:59 +000075 data = {
76 ':action':'file_upload',
Martin v. Löwisf74b9232005-03-22 15:51:14 +000077 'protcol_version':'1',
Martin v. Löwis98858c92005-03-21 21:00:59 +000078 'name':self.distribution.get_name(),
79 'version':self.distribution.get_version(),
80 'content':(os.path.basename(filename),content),
81 'filetype':command,
Martin v. Löwis98da5622005-03-23 18:54:36 +000082 'pyversion':pyversion,
Martin v. Löwis98858c92005-03-21 21:00:59 +000083 'md5_digest':md5(content).hexdigest(),
84 }
85 comment = ''
86 if command == 'bdist_rpm':
87 dist, version, id = platform.dist()
88 if dist:
89 comment = 'built for %s %s' % (dist, version)
90 elif command == 'bdist_dumb':
91 comment = 'built for %s' % platform.platform(terse=1)
92 data['comment'] = comment
93
Martin v. Löwisf74b9232005-03-22 15:51:14 +000094 if self.sign:
95 data['gpg_signature'] = (os.path.basename(filename) + ".asc",
96 open(filename+".asc").read())
97
Martin v. Löwis98858c92005-03-21 21:00:59 +000098 # set up the authentication
99 auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()
100
101 # Build up the MIME payload for the POST data
102 boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
103 sep_boundary = '\n--' + boundary
104 end_boundary = sep_boundary + '--'
105 body = StringIO.StringIO()
106 for key, value in data.items():
107 # handle multiple entries for the same name
108 if type(value) != type([]):
109 value = [value]
110 for value in value:
111 if type(value) is tuple:
112 fn = ';filename="%s"' % value[0]
113 value = value[1]
114 else:
115 fn = ""
116 value = str(value)
117 body.write(sep_boundary)
118 body.write('\nContent-Disposition: form-data; name="%s"'%key)
119 body.write(fn)
120 body.write("\n\n")
121 body.write(value)
122 if value and value[-1] == '\r':
123 body.write('\n') # write an extra newline (lurve Macs)
124 body.write(end_boundary)
125 body.write("\n")
126 body = body.getvalue()
127
128 self.announce("Submitting %s to %s" % (filename, self.repository), log.INFO)
129
130 # build the Request
131 # We can't use urllib2 since we need to send the Basic
132 # auth right with the first request
133 schema, netloc, url, params, query, fragments = \
134 urlparse.urlparse(self.repository)
135 assert not params and not query and not fragments
Tim Peterseba28be2005-03-28 01:08:02 +0000136 if schema == 'http':
Martin v. Löwis98858c92005-03-21 21:00:59 +0000137 http = httplib.HTTPConnection(netloc)
138 elif schema == 'https':
139 http = httplib.HTTPSConnection(netloc)
140 else:
141 raise AssertionError, "unsupported schema "+schema
142
143 data = ''
144 loglevel = log.INFO
145 try:
146 http.connect()
147 http.putrequest("POST", url)
Tim Peterseba28be2005-03-28 01:08:02 +0000148 http.putheader('Content-type',
Martin v. Löwis98858c92005-03-21 21:00:59 +0000149 'multipart/form-data; boundary=%s'%boundary)
150 http.putheader('Content-length', str(len(body)))
151 http.putheader('Authorization', auth)
152 http.endheaders()
153 http.send(body)
154 except socket.error, e:
155 self.announce(e.msg, log.ERROR)
156 return
157
158 r = http.getresponse()
159 if r.status == 200:
Tim Peterseba28be2005-03-28 01:08:02 +0000160 self.announce('Server response (%s): %s' % (r.status, r.reason),
Martin v. Löwis98858c92005-03-21 21:00:59 +0000161 log.INFO)
162 else:
Tim Peterseba28be2005-03-28 01:08:02 +0000163 self.announce('Upload failed (%s): %s' % (r.status, r.reason),
Martin v. Löwisf74b9232005-03-22 15:51:14 +0000164 log.ERROR)
Martin v. Löwis98858c92005-03-21 21:00:59 +0000165 if self.show_response:
166 print '-'*75, r.read(), '-'*75