1 """distutils.command.upload
3 Implements the Distutils 'upload' subcommand (upload package to PyPI)."""
5 from distutils
.errors
import *
6 from distutils
.core
import PyPIRCCommand
7 from distutils
.spawn
import spawn
8 from distutils
import log
9 from hashlib
import md5
16 import cStringIO
as StringIO
17 from ConfigParser
import ConfigParser
20 class upload(PyPIRCCommand
):
22 description
= "upload binary package to PyPI"
24 user_options
= PyPIRCCommand
.user_options
+ [
26 'sign files to upload using gpg'),
27 ('identity=', 'i', 'GPG identity used to sign files'),
30 boolean_options
= PyPIRCCommand
.boolean_options
+ ['sign']
32 def initialize_options(self
):
33 PyPIRCCommand
.initialize_options(self
)
36 self
.show_response
= 0
40 def finalize_options(self
):
41 PyPIRCCommand
.finalize_options(self
)
42 if self
.identity
and not self
.sign
:
43 raise DistutilsOptionError(
44 "Must use --sign for --identity to have meaning"
46 config
= self
._read
_pypirc
()
48 self
.username
= config
['username']
49 self
.password
= config
['password']
50 self
.repository
= config
['repository']
51 self
.realm
= config
['realm']
53 # getting the password from the distribution
54 # if previously set by the register command
55 if not self
.password
and self
.distribution
.password
:
56 self
.password
= self
.distribution
.password
59 if not self
.distribution
.dist_files
:
60 raise DistutilsOptionError("No dist file created in earlier command")
61 for command
, pyversion
, filename
in self
.distribution
.dist_files
:
62 self
.upload_file(command
, pyversion
, filename
)
64 def upload_file(self
, command
, pyversion
, filename
):
67 gpg_args
= ["gpg", "--detach-sign", "-a", filename
]
69 gpg_args
[2:2] = ["--local-user", self
.identity
]
73 # Fill in the data - send all the meta-data in case we need to
74 # register a new release
75 content
= open(filename
,'rb').read()
76 meta
= self
.distribution
.metadata
79 ':action': 'file_upload',
80 'protcol_version': '1',
83 'name': meta
.get_name(),
84 'version': meta
.get_version(),
87 'content': (os
.path
.basename(filename
),content
),
89 'pyversion': pyversion
,
90 'md5_digest': md5(content
).hexdigest(),
92 # additional meta-data
93 'metadata_version' : '1.0',
94 'summary': meta
.get_description(),
95 'home_page': meta
.get_url(),
96 'author': meta
.get_contact(),
97 'author_email': meta
.get_contact_email(),
98 'license': meta
.get_licence(),
99 'description': meta
.get_long_description(),
100 'keywords': meta
.get_keywords(),
101 'platform': meta
.get_platforms(),
102 'classifiers': meta
.get_classifiers(),
103 'download_url': meta
.get_download_url(),
105 'provides': meta
.get_provides(),
106 'requires': meta
.get_requires(),
107 'obsoletes': meta
.get_obsoletes(),
110 if command
== 'bdist_rpm':
111 dist
, version
, id = platform
.dist()
113 comment
= 'built for %s %s' % (dist
, version
)
114 elif command
== 'bdist_dumb':
115 comment
= 'built for %s' % platform
.platform(terse
=1)
116 data
['comment'] = comment
119 data
['gpg_signature'] = (os
.path
.basename(filename
) + ".asc",
120 open(filename
+".asc").read())
122 # set up the authentication
123 auth
= "Basic " + base64
.encodestring(self
.username
+ ":" + self
.password
).strip()
125 # Build up the MIME payload for the POST data
126 boundary
= '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
127 sep_boundary
= '\n--' + boundary
128 end_boundary
= sep_boundary
+ '--'
129 body
= StringIO
.StringIO()
130 for key
, value
in data
.items():
131 # handle multiple entries for the same name
132 if type(value
) != type([]):
135 if type(value
) is tuple:
136 fn
= ';filename="%s"' % value
[0]
141 body
.write(sep_boundary
)
142 body
.write('\nContent-Disposition: form-data; name="%s"'%key
)
146 if value
and value
[-1] == '\r':
147 body
.write('\n') # write an extra newline (lurve Macs)
148 body
.write(end_boundary
)
150 body
= body
.getvalue()
152 self
.announce("Submitting %s to %s" % (filename
, self
.repository
), log
.INFO
)
155 # We can't use urllib2 since we need to send the Basic
156 # auth right with the first request
157 schema
, netloc
, url
, params
, query
, fragments
= \
158 urlparse
.urlparse(self
.repository
)
159 assert not params
and not query
and not fragments
161 http
= httplib
.HTTPConnection(netloc
)
162 elif schema
== 'https':
163 http
= httplib
.HTTPSConnection(netloc
)
165 raise AssertionError, "unsupported schema "+schema
171 http
.putrequest("POST", url
)
172 http
.putheader('Content-type',
173 'multipart/form-data; boundary=%s'%boundary
)
174 http
.putheader('Content-length', str(len(body
)))
175 http
.putheader('Authorization', auth
)
178 except socket
.error
, e
:
179 self
.announce(str(e
), log
.ERROR
)
182 r
= http
.getresponse()
184 self
.announce('Server response (%s): %s' % (r
.status
, r
.reason
),
187 self
.announce('Upload failed (%s): %s' % (r
.status
, r
.reason
),
189 if self
.show_response
:
190 print '-'*75, r
.read(), '-'*75