blob: 266e9b1e8747c48ea04eb303c90ad3ae5391b6da [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
11import platform
12import ConfigParser
13import httplib
14import base64
15import urlparse
16import cStringIO as StringIO
17
18class upload(Command):
19
20 description = "upload binary package to PyPI"
21
22 DEFAULT_REPOSITORY = 'http://www.python.org/pypi'
23
24 user_options = [
25 ('repository=', 'r',
26 "url of repository [default: %s]" % DEFAULT_REPOSITORY),
27 ('show-response', None,
28 'display full response text from server'),
Martin v. Löwisf74b9232005-03-22 15:51:14 +000029 ('sign', 's',
30 'sign files to upload using gpg'),
Martin v. Löwis98858c92005-03-21 21:00:59 +000031 ]
Martin v. Löwisf74b9232005-03-22 15:51:14 +000032 boolean_options = ['show-response', 'sign']
Martin v. Löwis98858c92005-03-21 21:00:59 +000033
34 def initialize_options(self):
35 self.username = ''
36 self.password = ''
37 self.repository = ''
38 self.show_response = 0
Martin v. Löwisf74b9232005-03-22 15:51:14 +000039 self.sign = False
Martin v. Löwis98858c92005-03-21 21:00:59 +000040
41 def finalize_options(self):
42 if os.environ.has_key('HOME'):
43 rc = os.path.join(os.environ['HOME'], '.pypirc')
44 if os.path.exists(rc):
45 self.announce('Using PyPI login from %s' % rc)
46 config = ConfigParser.ConfigParser({
47 'username':'',
48 'password':'',
49 'repository':''})
50 config.read(rc)
51 if not self.repository:
52 self.repository = config.get('server-login', 'repository')
53 if not self.username:
54 self.username = config.get('server-login', 'username')
55 if not self.password:
56 self.password = config.get('server-login', 'password')
57 if not self.repository:
58 self.repository = self.DEFAULT_REPOSITORY
59
60 def run(self):
61 if not self.distribution.dist_files:
62 raise DistutilsOptionError("No dist file created in earlier command")
Martin v. Löwis98da5622005-03-23 18:54:36 +000063 for command, pyversion, filename in self.distribution.dist_files:
64 self.upload_file(command, pyversion, filename)
Martin v. Löwis98858c92005-03-21 21:00:59 +000065
Martin v. Löwis98da5622005-03-23 18:54:36 +000066 def upload_file(self, command, pyversion, filename):
Martin v. Löwisf74b9232005-03-22 15:51:14 +000067 # Sign if requested
68 if self.sign:
Martin v. Löwis8d121582005-03-22 23:02:54 +000069 spawn(("gpg", "--detach-sign", "-a", filename),
Martin v. Löwisf74b9232005-03-22 15:51:14 +000070 dry_run=self.dry_run)
Martin v. Löwis98858c92005-03-21 21:00:59 +000071
72 # Fill in the data
73 content = open(filename).read()
74 data = {
75 ':action':'file_upload',
Martin v. Löwisf74b9232005-03-22 15:51:14 +000076 'protcol_version':'1',
Martin v. Löwis98858c92005-03-21 21:00:59 +000077 'name':self.distribution.get_name(),
78 'version':self.distribution.get_version(),
79 'content':(os.path.basename(filename),content),
80 'filetype':command,
Martin v. Löwis98da5622005-03-23 18:54:36 +000081 'pyversion':pyversion,
Martin v. Löwis98858c92005-03-21 21:00:59 +000082 'md5_digest':md5(content).hexdigest(),
83 }
84 comment = ''
85 if command == 'bdist_rpm':
86 dist, version, id = platform.dist()
87 if dist:
88 comment = 'built for %s %s' % (dist, version)
89 elif command == 'bdist_dumb':
90 comment = 'built for %s' % platform.platform(terse=1)
91 data['comment'] = comment
92
Martin v. Löwisf74b9232005-03-22 15:51:14 +000093 if self.sign:
94 data['gpg_signature'] = (os.path.basename(filename) + ".asc",
95 open(filename+".asc").read())
96
Martin v. Löwis98858c92005-03-21 21:00:59 +000097 # set up the authentication
98 auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()
99
100 # Build up the MIME payload for the POST data
101 boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
102 sep_boundary = '\n--' + boundary
103 end_boundary = sep_boundary + '--'
104 body = StringIO.StringIO()
105 for key, value in data.items():
106 # handle multiple entries for the same name
107 if type(value) != type([]):
108 value = [value]
109 for value in value:
110 if type(value) is tuple:
111 fn = ';filename="%s"' % value[0]
112 value = value[1]
113 else:
114 fn = ""
115 value = str(value)
116 body.write(sep_boundary)
117 body.write('\nContent-Disposition: form-data; name="%s"'%key)
118 body.write(fn)
119 body.write("\n\n")
120 body.write(value)
121 if value and value[-1] == '\r':
122 body.write('\n') # write an extra newline (lurve Macs)
123 body.write(end_boundary)
124 body.write("\n")
125 body = body.getvalue()
126
127 self.announce("Submitting %s to %s" % (filename, self.repository), log.INFO)
128
129 # build the Request
130 # We can't use urllib2 since we need to send the Basic
131 # auth right with the first request
132 schema, netloc, url, params, query, fragments = \
133 urlparse.urlparse(self.repository)
134 assert not params and not query and not fragments
135 if schema == 'http':
136 http = httplib.HTTPConnection(netloc)
137 elif schema == 'https':
138 http = httplib.HTTPSConnection(netloc)
139 else:
140 raise AssertionError, "unsupported schema "+schema
141
142 data = ''
143 loglevel = log.INFO
144 try:
145 http.connect()
146 http.putrequest("POST", url)
147 http.putheader('Content-type',
148 'multipart/form-data; boundary=%s'%boundary)
149 http.putheader('Content-length', str(len(body)))
150 http.putheader('Authorization', auth)
151 http.endheaders()
152 http.send(body)
153 except socket.error, e:
154 self.announce(e.msg, log.ERROR)
155 return
156
157 r = http.getresponse()
158 if r.status == 200:
159 self.announce('Server response (%s): %s' % (r.status, r.reason),
160 log.INFO)
161 else:
162 self.announce('Upload failed (%s): %s' % (r.status, r.reason),
Martin v. Löwisf74b9232005-03-22 15:51:14 +0000163 log.ERROR)
Martin v. Löwis98858c92005-03-21 21:00:59 +0000164 if self.show_response:
165 print '-'*75, r.read(), '-'*75
166