blob: aea2e4b9c410c6ed6fdc762e806dff1cce2edb02 [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
7from md5 import md5
8from distutils.sysconfig import get_python_version
9from distutils import log
10import 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'),
29 ]
30 boolean_options = ['show-response']
31
32 def initialize_options(self):
33 self.username = ''
34 self.password = ''
35 self.repository = ''
36 self.show_response = 0
37
38 def finalize_options(self):
39 if os.environ.has_key('HOME'):
40 rc = os.path.join(os.environ['HOME'], '.pypirc')
41 if os.path.exists(rc):
42 self.announce('Using PyPI login from %s' % rc)
43 config = ConfigParser.ConfigParser({
44 'username':'',
45 'password':'',
46 'repository':''})
47 config.read(rc)
48 if not self.repository:
49 self.repository = config.get('server-login', 'repository')
50 if not self.username:
51 self.username = config.get('server-login', 'username')
52 if not self.password:
53 self.password = config.get('server-login', 'password')
54 if not self.repository:
55 self.repository = self.DEFAULT_REPOSITORY
56
57 def run(self):
58 if not self.distribution.dist_files:
59 raise DistutilsOptionError("No dist file created in earlier command")
60 for command, filename in self.distribution.dist_files:
61 self.upload_file(command, filename)
62
63 def upload_file(self, command, filename):
64
65 # Fill in the data
66 content = open(filename).read()
67 data = {
68 ':action':'file_upload',
69 'name':self.distribution.get_name(),
70 'version':self.distribution.get_version(),
71 'content':(os.path.basename(filename),content),
72 'filetype':command,
73 'pyversion':get_python_version(),
74 'md5_digest':md5(content).hexdigest(),
75 }
76 comment = ''
77 if command == 'bdist_rpm':
78 dist, version, id = platform.dist()
79 if dist:
80 comment = 'built for %s %s' % (dist, version)
81 elif command == 'bdist_dumb':
82 comment = 'built for %s' % platform.platform(terse=1)
83 data['comment'] = comment
84
85 # set up the authentication
86 auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()
87
88 # Build up the MIME payload for the POST data
89 boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
90 sep_boundary = '\n--' + boundary
91 end_boundary = sep_boundary + '--'
92 body = StringIO.StringIO()
93 for key, value in data.items():
94 # handle multiple entries for the same name
95 if type(value) != type([]):
96 value = [value]
97 for value in value:
98 if type(value) is tuple:
99 fn = ';filename="%s"' % value[0]
100 value = value[1]
101 else:
102 fn = ""
103 value = str(value)
104 body.write(sep_boundary)
105 body.write('\nContent-Disposition: form-data; name="%s"'%key)
106 body.write(fn)
107 body.write("\n\n")
108 body.write(value)
109 if value and value[-1] == '\r':
110 body.write('\n') # write an extra newline (lurve Macs)
111 body.write(end_boundary)
112 body.write("\n")
113 body = body.getvalue()
114
115 self.announce("Submitting %s to %s" % (filename, self.repository), log.INFO)
116
117 # build the Request
118 # We can't use urllib2 since we need to send the Basic
119 # auth right with the first request
120 schema, netloc, url, params, query, fragments = \
121 urlparse.urlparse(self.repository)
122 assert not params and not query and not fragments
123 if schema == 'http':
124 http = httplib.HTTPConnection(netloc)
125 elif schema == 'https':
126 http = httplib.HTTPSConnection(netloc)
127 else:
128 raise AssertionError, "unsupported schema "+schema
129
130 data = ''
131 loglevel = log.INFO
132 try:
133 http.connect()
134 http.putrequest("POST", url)
135 http.putheader('Content-type',
136 'multipart/form-data; boundary=%s'%boundary)
137 http.putheader('Content-length', str(len(body)))
138 http.putheader('Authorization', auth)
139 http.endheaders()
140 http.send(body)
141 except socket.error, e:
142 self.announce(e.msg, log.ERROR)
143 return
144
145 r = http.getresponse()
146 if r.status == 200:
147 self.announce('Server response (%s): %s' % (r.status, r.reason),
148 log.INFO)
149 else:
150 self.announce('Upload failed (%s): %s' % (r.status, r.reason),
151 log.INFO)
152 if self.show_response:
153 print '-'*75, r.read(), '-'*75
154