1 """distutils.command.upload
3 Implements the Distutils 'upload' subcommand (upload package to PyPI)."""
5 from distutils.errors import *
6 from distutils.core import Command
7 from distutils.spawn import spawn
8 from distutils import log
10 from hashlib import md5
20 import cStringIO as StringIO
22 class upload(Command):
24 description = "upload binary package to PyPI"
26 DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi'
30 "url of repository [default: %s]" % DEFAULT_REPOSITORY),
31 ('show-response', None,
32 'display full response text from server'),
34 'sign files to upload using gpg'),
35 ('identity=', 'i', 'GPG identity used to sign files'),
37 boolean_options = ['show-response', 'sign']
39 def initialize_options(self):
43 self.show_response = 0
47 def finalize_options(self):
48 if self.identity and not self.sign:
49 raise DistutilsOptionError(
50 "Must use --sign for --identity to have meaning"
52 if os.environ.has_key('HOME'):
53 rc = os.path.join(os.environ['HOME'], '.pypirc')
54 if os.path.exists(rc):
55 self.announce('Using PyPI login from %s' % rc)
56 config = ConfigParser.ConfigParser({
61 if not self.repository:
62 self.repository = config.get('server-login', 'repository')
64 self.username = config.get('server-login', 'username')
66 self.password = config.get('server-login', 'password')
67 if not self.repository:
68 self.repository = self.DEFAULT_REPOSITORY
71 if not self.distribution.dist_files:
72 raise DistutilsOptionError("No dist file created in earlier command")
73 for command, pyversion, filename in self.distribution.dist_files:
74 self.upload_file(command, pyversion, filename)
76 def upload_file(self, command, pyversion, filename):
79 gpg_args = ["gpg", "--detach-sign", "-a", filename]
81 gpg_args[2:2] = ["--local-user", self.identity]
86 content = open(filename,'rb').read()
87 basename = os.path.basename(filename)
89 if command=='bdist_egg' and self.distribution.has_ext_modules():
90 comment = "built on %s" % platform.platform(terse=1)
92 ':action':'file_upload',
93 'protcol_version':'1',
94 'name':self.distribution.get_name(),
95 'version':self.distribution.get_version(),
96 'content':(basename,content),
98 'pyversion':pyversion,
99 'md5_digest':md5(content).hexdigest(),
101 if command == 'bdist_rpm':
102 dist, version, id = platform.dist()
104 comment = 'built for %s %s' % (dist, version)
105 elif command == 'bdist_dumb':
106 comment = 'built for %s' % platform.platform(terse=1)
107 data['comment'] = comment
110 data['gpg_signature'] = (os.path.basename(filename) + ".asc",
111 open(filename+".asc").read())
113 # set up the authentication
114 auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()
116 # Build up the MIME payload for the POST data
117 boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
118 sep_boundary = '\n--' + boundary
119 end_boundary = sep_boundary + '--'
120 body = StringIO.StringIO()
121 for key, value in data.items():
122 # handle multiple entries for the same name
123 if type(value) != type([]):
126 if type(value) is tuple:
127 fn = ';filename="%s"' % value[0]
132 body.write(sep_boundary)
133 body.write('\nContent-Disposition: form-data; name="%s"'%key)
137 if value and value[-1] == '\r':
138 body.write('\n') # write an extra newline (lurve Macs)
139 body.write(end_boundary)
141 body = body.getvalue()
143 self.announce("Submitting %s to %s" % (filename, self.repository), log.INFO)
146 # We can't use urllib2 since we need to send the Basic
147 # auth right with the first request
148 schema, netloc, url, params, query, fragments = \
149 urlparse.urlparse(self.repository)
150 assert not params and not query and not fragments
152 http = httplib.HTTPConnection(netloc)
153 elif schema == 'https':
154 http = httplib.HTTPSConnection(netloc)
156 raise AssertionError, "unsupported schema "+schema
162 http.putrequest("POST", url)
163 http.putheader('Content-type',
164 'multipart/form-data; boundary=%s'%boundary)
165 http.putheader('Content-length', str(len(body)))
166 http.putheader('Authorization', auth)
169 except socket.error, e:
170 self.announce(str(e), log.ERROR)
173 r = http.getresponse()
175 self.announce('Server response (%s): %s' % (r.status, r.reason),
178 self.announce('Upload failed (%s): %s' % (r.status, r.reason),
180 if self.show_response:
181 print '-'*75, r.read(), '-'*75