Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(2)

Delta Between Two Patch Sets: Lib/distutils/command/upload.py

Issue 21722: teach distutils "upload" to exit with code != 0 when error occurs
Left Patch Set: Created 5 years, 12 months ago
Right Patch Set: Created 5 years, 12 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | Lib/distutils/tests/test_upload.py » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 """ 1 """distutils.command.upload
2 distutils.command.upload
3 2
4 Implements the Distutils 'upload' subcommand (upload package to a package 3 Implements the Distutils 'upload' subcommand (upload package to PyPI)."""
5 index). 4 import os
6 """ 5 import socket
6 import platform
7 from urllib2 import urlopen, Request, HTTPError
8 from base64 import standard_b64encode
9 import urlparse
10 import cStringIO as StringIO
11 from hashlib import md5
7 12
8 import os
9 import io
10 import platform
11 import hashlib
12 from base64 import standard_b64encode
13 from urllib.request import urlopen, Request, HTTPError
14 from urllib.parse import urlparse
15 from distutils.errors import DistutilsError, DistutilsOptionError 13 from distutils.errors import DistutilsError, DistutilsOptionError
16 from distutils.core import PyPIRCCommand 14 from distutils.core import PyPIRCCommand
17 from distutils.spawn import spawn 15 from distutils.spawn import spawn
18 from distutils import log 16 from distutils import log
19 17
20 class upload(PyPIRCCommand): 18 class upload(PyPIRCCommand):
21 19
22 description = "upload binary package to PyPI" 20 description = "upload binary package to PyPI"
23 21
24 user_options = PyPIRCCommand.user_options + [ 22 user_options = PyPIRCCommand.user_options + [
(...skipping 25 matching lines...) Expand all
50 self.repository = config['repository'] 48 self.repository = config['repository']
51 self.realm = config['realm'] 49 self.realm = config['realm']
52 50
53 # getting the password from the distribution 51 # getting the password from the distribution
54 # if previously set by the register command 52 # if previously set by the register command
55 if not self.password and self.distribution.password: 53 if not self.password and self.distribution.password:
56 self.password = self.distribution.password 54 self.password = self.distribution.password
57 55
58 def run(self): 56 def run(self):
59 if not self.distribution.dist_files: 57 if not self.distribution.dist_files:
60 msg = "No dist file created in earlier command" 58 raise DistutilsOptionError("No dist file created in earlier command" )
61 raise DistutilsOptionError(msg)
62 for command, pyversion, filename in self.distribution.dist_files: 59 for command, pyversion, filename in self.distribution.dist_files:
63 self.upload_file(command, pyversion, filename) 60 self.upload_file(command, pyversion, filename)
64 61
65 def upload_file(self, command, pyversion, filename): 62 def upload_file(self, command, pyversion, filename):
66 # Makes sure the repository URL is compliant 63 # Makes sure the repository URL is compliant
67 schema, netloc, url, params, query, fragments = \ 64 schema, netloc, url, params, query, fragments = \
68 urlparse(self.repository) 65 urlparse.urlparse(self.repository)
69 if params or query or fragments: 66 if params or query or fragments:
70 raise AssertionError("Incompatible url %s" % self.repository) 67 raise AssertionError("Incompatible url %s" % self.repository)
71 68
72 if schema not in ('http', 'https'): 69 if schema not in ('http', 'https'):
73 raise AssertionError("unsupported schema " + schema) 70 raise AssertionError("unsupported schema " + schema)
74 71
75 # Sign if requested 72 # Sign if requested
76 if self.sign: 73 if self.sign:
77 gpg_args = ["gpg", "--detach-sign", "-a", filename] 74 gpg_args = ["gpg", "--detach-sign", "-a", filename]
78 if self.identity: 75 if self.identity:
(...skipping 15 matching lines...) Expand all
94 'protcol_version': '1', 91 'protcol_version': '1',
95 92
96 # identify release 93 # identify release
97 'name': meta.get_name(), 94 'name': meta.get_name(),
98 'version': meta.get_version(), 95 'version': meta.get_version(),
99 96
100 # file content 97 # file content
101 'content': (os.path.basename(filename),content), 98 'content': (os.path.basename(filename),content),
102 'filetype': command, 99 'filetype': command,
103 'pyversion': pyversion, 100 'pyversion': pyversion,
104 'md5_digest': hashlib.md5(content).hexdigest(), 101 'md5_digest': md5(content).hexdigest(),
105 102
106 # additional meta-data 103 # additional meta-data
107 'metadata_version': '1.0', 104 'metadata_version' : '1.0',
108 'summary': meta.get_description(), 105 'summary': meta.get_description(),
109 'home_page': meta.get_url(), 106 'home_page': meta.get_url(),
110 'author': meta.get_contact(), 107 'author': meta.get_contact(),
111 'author_email': meta.get_contact_email(), 108 'author_email': meta.get_contact_email(),
112 'license': meta.get_licence(), 109 'license': meta.get_licence(),
113 'description': meta.get_long_description(), 110 'description': meta.get_long_description(),
114 'keywords': meta.get_keywords(), 111 'keywords': meta.get_keywords(),
115 'platform': meta.get_platforms(), 112 'platform': meta.get_platforms(),
116 'classifiers': meta.get_classifiers(), 113 'classifiers': meta.get_classifiers(),
117 'download_url': meta.get_download_url(), 114 'download_url': meta.get_download_url(),
118 # PEP 314 115 # PEP 314
119 'provides': meta.get_provides(), 116 'provides': meta.get_provides(),
120 'requires': meta.get_requires(), 117 'requires': meta.get_requires(),
121 'obsoletes': meta.get_obsoletes(), 118 'obsoletes': meta.get_obsoletes(),
122 } 119 }
123 comment = '' 120 comment = ''
124 if command == 'bdist_rpm': 121 if command == 'bdist_rpm':
125 dist, version, id = platform.dist() 122 dist, version, id = platform.dist()
126 if dist: 123 if dist:
127 comment = 'built for %s %s' % (dist, version) 124 comment = 'built for %s %s' % (dist, version)
128 elif command == 'bdist_dumb': 125 elif command == 'bdist_dumb':
129 comment = 'built for %s' % platform.platform(terse=1) 126 comment = 'built for %s' % platform.platform(terse=1)
130 data['comment'] = comment 127 data['comment'] = comment
131 128
132 if self.sign: 129 if self.sign:
133 data['gpg_signature'] = (os.path.basename(filename) + ".asc", 130 data['gpg_signature'] = (os.path.basename(filename) + ".asc",
134 open(filename+".asc", "rb").read()) 131 open(filename+".asc").read())
135 132
136 # set up the authentication 133 # set up the authentication
137 user_pass = (self.username + ":" + self.password).encode('ascii') 134 auth = "Basic " + standard_b64encode(self.username + ":" +
138 # The exact encoding of the authentication string is debated. 135 self.password)
139 # Anyway PyPI only accepts ascii for both username or password.
140 auth = "Basic " + standard_b64encode(user_pass).decode('ascii')
141 136
142 # Build up the MIME payload for the POST data 137 # Build up the MIME payload for the POST data
143 boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' 138 boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
144 sep_boundary = b'\n--' + boundary.encode('ascii') 139 sep_boundary = '\n--' + boundary
145 end_boundary = sep_boundary + b'--' 140 end_boundary = sep_boundary + '--'
146 body = io.BytesIO() 141 body = StringIO.StringIO()
147 for key, value in data.items(): 142 for key, value in data.items():
148 title = '\nContent-Disposition: form-data; name="%s"' % key
149 # handle multiple entries for the same name 143 # handle multiple entries for the same name
150 if not isinstance(value, list): 144 if not isinstance(value, list):
151 value = [value] 145 value = [value]
152 for value in value: 146 for value in value:
153 if type(value) is tuple: 147 if isinstance(value, tuple):
154 title += '; filename="%s"' % value[0] 148 fn = ';filename="%s"' % value[0]
155 value = value[1] 149 value = value[1]
156 else: 150 else:
157 value = str(value).encode('utf-8') 151 fn = ""
152
158 body.write(sep_boundary) 153 body.write(sep_boundary)
159 body.write(title.encode('utf-8')) 154 body.write('\nContent-Disposition: form-data; name="%s"'%key)
160 body.write(b"\n\n") 155 body.write(fn)
156 body.write("\n\n")
161 body.write(value) 157 body.write(value)
162 if value and value[-1:] == b'\r': 158 if value and value[-1] == '\r':
163 body.write(b'\n') # write an extra newline (lurve Macs) 159 body.write('\n') # write an extra newline (lurve Macs)
164 body.write(end_boundary) 160 body.write(end_boundary)
165 body.write(b"\n") 161 body.write("\n")
166 body = body.getvalue() 162 body = body.getvalue()
167 163
168 msg = "Submitting %s to %s" % (filename, self.repository) 164 self.announce("Submitting %s to %s" % (filename, self.repository), log.I NFO)
169 self.announce(msg, log.INFO)
170 165
171 # build the Request 166 # build the Request
172 headers = { 167 headers = {'Content-type':
173 'Content-type': 'multipart/form-data; boundary=%s' % boundary, 168 'multipart/form-data; boundary=%s' % boundary,
174 'Content-length': str(len(body)), 169 'Content-length': str(len(body)),
175 'Authorization': auth, 170 'Authorization': auth}
176 }
177 171
178 request = Request(self.repository, data=body, 172 request = Request(self.repository, data=body,
179 headers=headers) 173 headers=headers)
180 # send the data 174 # send the data
181 try: 175 try:
182 result = urlopen(request) 176 result = urlopen(request)
183 status = result.getcode() 177 status = result.getcode()
184 reason = result.msg 178 reason = result.msg
185 except OSError as e: 179 if self.show_response:
180 msg = '\n'.join(('-' * 75, result.read(), '-' * 75))
181 self.announce(msg, log.INFO)
182 except socket.error, e:
186 self.announce(str(e), log.ERROR) 183 self.announce(str(e), log.ERROR)
187 raise 184 raise
188 except HTTPError as e: 185 except HTTPError, e:
189 status = e.code 186 status = e.code
190 reason = e.msg 187 reason = e.msg
191 188
192 if status == 200: 189 if status == 200:
193 self.announce('Server response (%s): %s' % (status, reason), 190 self.announce('Server response (%s): %s' % (status, reason),
194 log.INFO) 191 log.INFO)
195 else: 192 else:
196 msg = 'Upload failed (%s): %s' % (status, reason) 193 msg = 'Upload failed (%s): %s' % (status, reason)
197 self.announce(msg, log.ERROR) 194 self.announce(msg, log.ERROR)
198 raise DistutilsError(msg) 195 raise DistutilsError(msg)
199 if self.show_response:
200 text = self._read_pypi_response(result)
201 msg = '\n'.join(('-' * 75, text, '-' * 75))
202 self.announce(msg, log.INFO)
LEFTRIGHT

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+