1 """distutils.command.upload
3 Implements the Distutils 'upload' subcommand (upload package to PyPI)."""
8 from urllib2
import urlopen
, Request
, HTTPError
11 import cStringIO
as StringIO
12 from ConfigParser
import ConfigParser
13 from hashlib
import md5
15 from distutils
.errors
import *
16 from distutils
.core
import PyPIRCCommand
17 from distutils
.spawn
import spawn
18 from distutils
import log
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
):
65 # Makes sure the repository URL is compliant
66 schema
, netloc
, url
, params
, query
, fragments
= \
67 urlparse
.urlparse(self
.repository
)
68 if params
or query
or fragments
:
69 raise AssertionError("Incompatible url %s" % self
.repository
)
71 if schema
not in ('http', 'https'):
72 raise AssertionError("unsupported schema " + schema
)
76 gpg_args
= ["gpg", "--detach-sign", "-a", filename
]
78 gpg_args
[2:2] = ["--local-user", self
.identity
]
82 # Fill in the data - send all the meta-data in case we need to
83 # register a new release
84 content
= open(filename
,'rb').read()
85 meta
= self
.distribution
.metadata
88 ':action': 'file_upload',
89 'protcol_version': '1',
92 'name': meta
.get_name(),
93 'version': meta
.get_version(),
96 'content': (os
.path
.basename(filename
),content
),
98 'pyversion': pyversion
,
99 'md5_digest': md5(content
).hexdigest(),
101 # additional meta-data
102 'metadata_version' : '1.0',
103 'summary': meta
.get_description(),
104 'home_page': meta
.get_url(),
105 'author': meta
.get_contact(),
106 'author_email': meta
.get_contact_email(),
107 'license': meta
.get_licence(),
108 'description': meta
.get_long_description(),
109 'keywords': meta
.get_keywords(),
110 'platform': meta
.get_platforms(),
111 'classifiers': meta
.get_classifiers(),
112 'download_url': meta
.get_download_url(),
114 'provides': meta
.get_provides(),
115 'requires': meta
.get_requires(),
116 'obsoletes': meta
.get_obsoletes(),
119 if command
== 'bdist_rpm':
120 dist
, version
, id = platform
.dist()
122 comment
= 'built for %s %s' % (dist
, version
)
123 elif command
== 'bdist_dumb':
124 comment
= 'built for %s' % platform
.platform(terse
=1)
125 data
['comment'] = comment
128 data
['gpg_signature'] = (os
.path
.basename(filename
) + ".asc",
129 open(filename
+".asc").read())
131 # set up the authentication
132 auth
= "Basic " + base64
.encodestring(self
.username
+ ":" +
133 self
.password
).strip()
135 # Build up the MIME payload for the POST data
136 boundary
= '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
137 sep_boundary
= '\n--' + boundary
138 end_boundary
= sep_boundary
+ '--'
139 body
= StringIO
.StringIO()
140 for key
, value
in data
.items():
141 # handle multiple entries for the same name
142 if not isinstance(value
, list):
145 if isinstance(value
, tuple):
146 fn
= ';filename="%s"' % value
[0]
151 body
.write(sep_boundary
)
152 body
.write('\nContent-Disposition: form-data; name="%s"'%key
)
156 if value
and value
[-1] == '\r':
157 body
.write('\n') # write an extra newline (lurve Macs)
158 body
.write(end_boundary
)
160 body
= body
.getvalue()
162 self
.announce("Submitting %s to %s" % (filename
, self
.repository
), log
.INFO
)
165 headers
= {'Content-type':
166 'multipart/form-data; boundary=%s' % boundary
,
167 'Content-length': str(len(body
)),
168 'Authorization': auth
}
170 request
= Request(self
.repository
, data
=body
,
174 result
= urlopen(request
)
175 status
= result
.getcode()
177 except socket
.error
, e
:
178 self
.announce(str(e
), log
.ERROR
)
185 self
.announce('Server response (%s): %s' % (status
, reason
),
188 self
.announce('Upload failed (%s): %s' % (status
, reason
),
190 if self
.show_response
:
191 self
.announce('-'*75, result
.read(), '-'*75)