blob: a62da78a02e9a89d546b2c274959018906223145 [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öwis98858c92005-03-21 21:00:59 +00007from distutils.sysconfig import get_python_version
Martin v. Löwisf74b9232005-03-22 15:51:14 +00008from distutils.spawn import spawn
Martin v. Löwis98858c92005-03-21 21:00:59 +00009from distutils import log
Martin v. Löwisf74b9232005-03-22 15:51:14 +000010from md5 import md5
Martin v. Löwis98858c92005-03-21 21:00:59 +000011import os
12import 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")
64 for command, filename in self.distribution.dist_files:
65 self.upload_file(command, filename)
66
67 def upload_file(self, command, filename):
Martin v. Löwisf74b9232005-03-22 15:51:14 +000068 # Sign if requested
69 if self.sign:
70 spawn(("gpg", "--sign", "-a", filename),
71 dry_run=self.dry_run)
Martin v. Löwis98858c92005-03-21 21:00:59 +000072
73 # Fill in the data
74 content = open(filename).read()
75 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,
82 'pyversion':get_python_version(),
83 '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)
Martin v. Löwisbe435bf2005-03-22 20:32:41 +000092 elif command == 'sdist':
93 data['pyversion'] = ''
Martin v. Löwis98858c92005-03-21 21:00:59 +000094 data['comment'] = comment
95
Martin v. Löwisf74b9232005-03-22 15:51:14 +000096 if self.sign:
97 data['gpg_signature'] = (os.path.basename(filename) + ".asc",
98 open(filename+".asc").read())
99
Martin v. Löwis98858c92005-03-21 21:00:59 +0000100 # set up the authentication
101 auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()
102
103 # Build up the MIME payload for the POST data
104 boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
105 sep_boundary = '\n--' + boundary
106 end_boundary = sep_boundary + '--'
107 body = StringIO.StringIO()
108 for key, value in data.items():
109 # handle multiple entries for the same name
110 if type(value) != type([]):
111 value = [value]
112 for value in value:
113 if type(value) is tuple:
114 fn = ';filename="%s"' % value[0]
115 value = value[1]
116 else:
117 fn = ""
118 value = str(value)
119 body.write(sep_boundary)
120 body.write('\nContent-Disposition: form-data; name="%s"'%key)
121 body.write(fn)
122 body.write("\n\n")
123 body.write(value)
124 if value and value[-1] == '\r':
125 body.write('\n') # write an extra newline (lurve Macs)
126 body.write(end_boundary)
127 body.write("\n")
128 body = body.getvalue()
129
130 self.announce("Submitting %s to %s" % (filename, self.repository), log.INFO)
131
132 # build the Request
133 # We can't use urllib2 since we need to send the Basic
134 # auth right with the first request
135 schema, netloc, url, params, query, fragments = \
136 urlparse.urlparse(self.repository)
137 assert not params and not query and not fragments
138 if schema == 'http':
139 http = httplib.HTTPConnection(netloc)
140 elif schema == 'https':
141 http = httplib.HTTPSConnection(netloc)
142 else:
143 raise AssertionError, "unsupported schema "+schema
144
145 data = ''
146 loglevel = log.INFO
147 try:
148 http.connect()
149 http.putrequest("POST", url)
150 http.putheader('Content-type',
151 'multipart/form-data; boundary=%s'%boundary)
152 http.putheader('Content-length', str(len(body)))
153 http.putheader('Authorization', auth)
154 http.endheaders()
155 http.send(body)
156 except socket.error, e:
157 self.announce(e.msg, log.ERROR)
158 return
159
160 r = http.getresponse()
161 if r.status == 200:
162 self.announce('Server response (%s): %s' % (r.status, r.reason),
163 log.INFO)
164 else:
165 self.announce('Upload failed (%s): %s' % (r.status, r.reason),
Martin v. Löwisf74b9232005-03-22 15:51:14 +0000166 log.ERROR)
Martin v. Löwis98858c92005-03-21 21:00:59 +0000167 if self.show_response:
168 print '-'*75, r.read(), '-'*75
169