comparison env/lib/python3.7/site-packages/setuptools/command/upload.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 import io
2 import os
3 import hashlib
4 import getpass
5
6 from base64 import standard_b64encode
7
8 from distutils import log
9 from distutils.command import upload as orig
10 from distutils.spawn import spawn
11
12 from distutils.errors import DistutilsError
13
14 from setuptools.extern.six.moves.urllib.request import urlopen, Request
15 from setuptools.extern.six.moves.urllib.error import HTTPError
16 from setuptools.extern.six.moves.urllib.parse import urlparse
17
18
19 class upload(orig.upload):
20 """
21 Override default upload behavior to obtain password
22 in a variety of different ways.
23 """
24 def run(self):
25 try:
26 orig.upload.run(self)
27 finally:
28 self.announce(
29 "WARNING: Uploading via this command is deprecated, use twine "
30 "to upload instead (https://pypi.org/p/twine/)",
31 log.WARN
32 )
33
34 def finalize_options(self):
35 orig.upload.finalize_options(self)
36 self.username = (
37 self.username or
38 getpass.getuser()
39 )
40 # Attempt to obtain password. Short circuit evaluation at the first
41 # sign of success.
42 self.password = (
43 self.password or
44 self._load_password_from_keyring() or
45 self._prompt_for_password()
46 )
47
48 def upload_file(self, command, pyversion, filename):
49 # Makes sure the repository URL is compliant
50 schema, netloc, url, params, query, fragments = \
51 urlparse(self.repository)
52 if params or query or fragments:
53 raise AssertionError("Incompatible url %s" % self.repository)
54
55 if schema not in ('http', 'https'):
56 raise AssertionError("unsupported schema " + schema)
57
58 # Sign if requested
59 if self.sign:
60 gpg_args = ["gpg", "--detach-sign", "-a", filename]
61 if self.identity:
62 gpg_args[2:2] = ["--local-user", self.identity]
63 spawn(gpg_args,
64 dry_run=self.dry_run)
65
66 # Fill in the data - send all the meta-data in case we need to
67 # register a new release
68 with open(filename, 'rb') as f:
69 content = f.read()
70
71 meta = self.distribution.metadata
72
73 data = {
74 # action
75 ':action': 'file_upload',
76 'protocol_version': '1',
77
78 # identify release
79 'name': meta.get_name(),
80 'version': meta.get_version(),
81
82 # file content
83 'content': (os.path.basename(filename), content),
84 'filetype': command,
85 'pyversion': pyversion,
86 'md5_digest': hashlib.md5(content).hexdigest(),
87
88 # additional meta-data
89 'metadata_version': str(meta.get_metadata_version()),
90 'summary': meta.get_description(),
91 'home_page': meta.get_url(),
92 'author': meta.get_contact(),
93 'author_email': meta.get_contact_email(),
94 'license': meta.get_licence(),
95 'description': meta.get_long_description(),
96 'keywords': meta.get_keywords(),
97 'platform': meta.get_platforms(),
98 'classifiers': meta.get_classifiers(),
99 'download_url': meta.get_download_url(),
100 # PEP 314
101 'provides': meta.get_provides(),
102 'requires': meta.get_requires(),
103 'obsoletes': meta.get_obsoletes(),
104 }
105
106 data['comment'] = ''
107
108 if self.sign:
109 data['gpg_signature'] = (os.path.basename(filename) + ".asc",
110 open(filename+".asc", "rb").read())
111
112 # set up the authentication
113 user_pass = (self.username + ":" + self.password).encode('ascii')
114 # The exact encoding of the authentication string is debated.
115 # Anyway PyPI only accepts ascii for both username or password.
116 auth = "Basic " + standard_b64encode(user_pass).decode('ascii')
117
118 # Build up the MIME payload for the POST data
119 boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
120 sep_boundary = b'\r\n--' + boundary.encode('ascii')
121 end_boundary = sep_boundary + b'--\r\n'
122 body = io.BytesIO()
123 for key, value in data.items():
124 title = '\r\nContent-Disposition: form-data; name="%s"' % key
125 # handle multiple entries for the same name
126 if not isinstance(value, list):
127 value = [value]
128 for value in value:
129 if type(value) is tuple:
130 title += '; filename="%s"' % value[0]
131 value = value[1]
132 else:
133 value = str(value).encode('utf-8')
134 body.write(sep_boundary)
135 body.write(title.encode('utf-8'))
136 body.write(b"\r\n\r\n")
137 body.write(value)
138 body.write(end_boundary)
139 body = body.getvalue()
140
141 msg = "Submitting %s to %s" % (filename, self.repository)
142 self.announce(msg, log.INFO)
143
144 # build the Request
145 headers = {
146 'Content-type': 'multipart/form-data; boundary=%s' % boundary,
147 'Content-length': str(len(body)),
148 'Authorization': auth,
149 }
150
151 request = Request(self.repository, data=body,
152 headers=headers)
153 # send the data
154 try:
155 result = urlopen(request)
156 status = result.getcode()
157 reason = result.msg
158 except HTTPError as e:
159 status = e.code
160 reason = e.msg
161 except OSError as e:
162 self.announce(str(e), log.ERROR)
163 raise
164
165 if status == 200:
166 self.announce('Server response (%s): %s' % (status, reason),
167 log.INFO)
168 if self.show_response:
169 text = getattr(self, '_read_pypi_response',
170 lambda x: None)(result)
171 if text is not None:
172 msg = '\n'.join(('-' * 75, text, '-' * 75))
173 self.announce(msg, log.INFO)
174 else:
175 msg = 'Upload failed (%s): %s' % (status, reason)
176 self.announce(msg, log.ERROR)
177 raise DistutilsError(msg)
178
179 def _load_password_from_keyring(self):
180 """
181 Attempt to load password from keyring. Suppress Exceptions.
182 """
183 try:
184 keyring = __import__('keyring')
185 return keyring.get_password(self.repository, self.username)
186 except Exception:
187 pass
188
189 def _prompt_for_password(self):
190 """
191 Prompt for a password on the tty. Suppress Exceptions.
192 """
193 try:
194 return getpass.getpass()
195 except (Exception, KeyboardInterrupt):
196 pass