1 """distutils.command.upload
3 Implements the Distutils 'upload' subcommand (upload package to PyPI)."""
7 from urllib2
import urlopen
, Request
, HTTPError
8 from base64
import standard_b64encode
10 import cStringIO
as StringIO
11 from hashlib
import md5
13 from distutils
.errors
import DistutilsOptionError
14 from distutils
.core
import PyPIRCCommand
15 from distutils
.spawn
import spawn
16 from distutils
import log
18 class upload(PyPIRCCommand
):
20 description
= "upload binary package to PyPI"
22 user_options
= PyPIRCCommand
.user_options
+ [
24 'sign files to upload using gpg'),
25 ('identity=', 'i', 'GPG identity used to sign files'),
28 boolean_options
= PyPIRCCommand
.boolean_options
+ ['sign']
30 def initialize_options(self
):
31 PyPIRCCommand
.initialize_options(self
)
34 self
.show_response
= 0
38 def finalize_options(self
):
39 PyPIRCCommand
.finalize_options(self
)
40 if self
.identity
and not self
.sign
:
41 raise DistutilsOptionError(
42 "Must use --sign for --identity to have meaning"
44 config
= self
._read
_pypirc
()
46 self
.username
= config
['username']
47 self
.password
= config
['password']
48 self
.repository
= config
['repository']
49 self
.realm
= config
['realm']
51 # getting the password from the distribution
52 # if previously set by the register command
53 if not self
.password
and self
.distribution
.password
:
54 self
.password
= self
.distribution
.password
57 if not self
.distribution
.dist_files
:
58 raise DistutilsOptionError("No dist file created in earlier command")
59 for command
, pyversion
, filename
in self
.distribution
.dist_files
:
60 self
.upload_file(command
, pyversion
, filename
)
62 def upload_file(self
, command
, pyversion
, filename
):
63 # Makes sure the repository URL is compliant
64 schema
, netloc
, url
, params
, query
, fragments
= \
65 urlparse
.urlparse(self
.repository
)
66 if params
or query
or fragments
:
67 raise AssertionError("Incompatible url %s" % self
.repository
)
69 if schema
not in ('http', 'https'):
70 raise AssertionError("unsupported schema " + schema
)
74 gpg_args
= ["gpg", "--detach-sign", "-a", filename
]
76 gpg_args
[2:2] = ["--local-user", self
.identity
]
80 # Fill in the data - send all the meta-data in case we need to
81 # register a new release
82 content
= open(filename
,'rb').read()
83 meta
= self
.distribution
.metadata
86 ':action': 'file_upload',
87 'protcol_version': '1',
90 'name': meta
.get_name(),
91 'version': meta
.get_version(),
94 'content': (os
.path
.basename(filename
),content
),
96 'pyversion': pyversion
,
97 'md5_digest': md5(content
).hexdigest(),
99 # additional meta-data
100 'metadata_version' : '1.0',
101 'summary': meta
.get_description(),
102 'home_page': meta
.get_url(),
103 'author': meta
.get_contact(),
104 'author_email': meta
.get_contact_email(),
105 'license': meta
.get_licence(),
106 'description': meta
.get_long_description(),
107 'keywords': meta
.get_keywords(),
108 'platform': meta
.get_platforms(),
109 'classifiers': meta
.get_classifiers(),
110 'download_url': meta
.get_download_url(),
112 'provides': meta
.get_provides(),
113 'requires': meta
.get_requires(),
114 'obsoletes': meta
.get_obsoletes(),
117 if command
== 'bdist_rpm':
118 dist
, version
, id = platform
.dist()
120 comment
= 'built for %s %s' % (dist
, version
)
121 elif command
== 'bdist_dumb':
122 comment
= 'built for %s' % platform
.platform(terse
=1)
123 data
['comment'] = comment
126 data
['gpg_signature'] = (os
.path
.basename(filename
) + ".asc",
127 open(filename
+".asc").read())
129 # set up the authentication
130 auth
= "Basic " + standard_b64encode(self
.username
+ ":" +
133 # Build up the MIME payload for the POST data
134 boundary
= '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
135 sep_boundary
= '\n--' + boundary
136 end_boundary
= sep_boundary
+ '--'
137 body
= StringIO
.StringIO()
138 for key
, value
in data
.items():
139 # handle multiple entries for the same name
140 if not isinstance(value
, list):
143 if isinstance(value
, tuple):
144 fn
= ';filename="%s"' % value
[0]
149 body
.write(sep_boundary
)
150 body
.write('\nContent-Disposition: form-data; name="%s"'%key
)
154 if value
and value
[-1] == '\r':
155 body
.write('\n') # write an extra newline (lurve Macs)
156 body
.write(end_boundary
)
158 body
= body
.getvalue()
160 self
.announce("Submitting %s to %s" % (filename
, self
.repository
), log
.INFO
)
163 headers
= {'Content-type':
164 'multipart/form-data; boundary=%s' % boundary
,
165 'Content-length': str(len(body
)),
166 'Authorization': auth
}
168 request
= Request(self
.repository
, data
=body
,
172 result
= urlopen(request
)
173 status
= result
.getcode()
175 except socket
.error
, e
:
176 self
.announce(str(e
), log
.ERROR
)
183 self
.announce('Server response (%s): %s' % (status
, reason
),
186 self
.announce('Upload failed (%s): %s' % (status
, reason
),
188 if self
.show_response
:
189 self
.announce('-'*75, result
.read(), '-'*75)