comparison lib/python3.8/site-packages/setuptools/command/upload_docs.py @ 0:9e54283cc701 draft

"planemo upload commit d12c32a45bcd441307e632fca6d9af7d60289d44"
author guerler
date Mon, 27 Jul 2020 03:47:31 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:9e54283cc701
1 # -*- coding: utf-8 -*-
2 """upload_docs
3
4 Implements a Distutils 'upload_docs' subcommand (upload documentation to
5 PyPI's pythonhosted.org).
6 """
7
8 from base64 import standard_b64encode
9 from distutils import log
10 from distutils.errors import DistutilsOptionError
11 import os
12 import socket
13 import zipfile
14 import tempfile
15 import shutil
16 import itertools
17 import functools
18
19 from setuptools.extern import six
20 from setuptools.extern.six.moves import http_client, urllib
21
22 from pkg_resources import iter_entry_points
23 from .upload import upload
24
25
26 def _encode(s):
27 errors = 'strict' if six.PY2 else 'surrogateescape'
28 return s.encode('utf-8', errors)
29
30
31 class upload_docs(upload):
32 # override the default repository as upload_docs isn't
33 # supported by Warehouse (and won't be).
34 DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/'
35
36 description = 'Upload documentation to PyPI'
37
38 user_options = [
39 ('repository=', 'r',
40 "url of repository [default: %s]" % upload.DEFAULT_REPOSITORY),
41 ('show-response', None,
42 'display full response text from server'),
43 ('upload-dir=', None, 'directory to upload'),
44 ]
45 boolean_options = upload.boolean_options
46
47 def has_sphinx(self):
48 if self.upload_dir is None:
49 for ep in iter_entry_points('distutils.commands', 'build_sphinx'):
50 return True
51
52 sub_commands = [('build_sphinx', has_sphinx)]
53
54 def initialize_options(self):
55 upload.initialize_options(self)
56 self.upload_dir = None
57 self.target_dir = None
58
59 def finalize_options(self):
60 upload.finalize_options(self)
61 if self.upload_dir is None:
62 if self.has_sphinx():
63 build_sphinx = self.get_finalized_command('build_sphinx')
64 self.target_dir = build_sphinx.builder_target_dir
65 else:
66 build = self.get_finalized_command('build')
67 self.target_dir = os.path.join(build.build_base, 'docs')
68 else:
69 self.ensure_dirname('upload_dir')
70 self.target_dir = self.upload_dir
71 if 'pypi.python.org' in self.repository:
72 log.warn("Upload_docs command is deprecated. Use RTD instead.")
73 self.announce('Using upload directory %s' % self.target_dir)
74
75 def create_zipfile(self, filename):
76 zip_file = zipfile.ZipFile(filename, "w")
77 try:
78 self.mkpath(self.target_dir) # just in case
79 for root, dirs, files in os.walk(self.target_dir):
80 if root == self.target_dir and not files:
81 tmpl = "no files found in upload directory '%s'"
82 raise DistutilsOptionError(tmpl % self.target_dir)
83 for name in files:
84 full = os.path.join(root, name)
85 relative = root[len(self.target_dir):].lstrip(os.path.sep)
86 dest = os.path.join(relative, name)
87 zip_file.write(full, dest)
88 finally:
89 zip_file.close()
90
91 def run(self):
92 # Run sub commands
93 for cmd_name in self.get_sub_commands():
94 self.run_command(cmd_name)
95
96 tmp_dir = tempfile.mkdtemp()
97 name = self.distribution.metadata.get_name()
98 zip_file = os.path.join(tmp_dir, "%s.zip" % name)
99 try:
100 self.create_zipfile(zip_file)
101 self.upload_file(zip_file)
102 finally:
103 shutil.rmtree(tmp_dir)
104
105 @staticmethod
106 def _build_part(item, sep_boundary):
107 key, values = item
108 title = '\nContent-Disposition: form-data; name="%s"' % key
109 # handle multiple entries for the same name
110 if not isinstance(values, list):
111 values = [values]
112 for value in values:
113 if isinstance(value, tuple):
114 title += '; filename="%s"' % value[0]
115 value = value[1]
116 else:
117 value = _encode(value)
118 yield sep_boundary
119 yield _encode(title)
120 yield b"\n\n"
121 yield value
122 if value and value[-1:] == b'\r':
123 yield b'\n' # write an extra newline (lurve Macs)
124
125 @classmethod
126 def _build_multipart(cls, data):
127 """
128 Build up the MIME payload for the POST data
129 """
130 boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
131 sep_boundary = b'\n--' + boundary.encode('ascii')
132 end_boundary = sep_boundary + b'--'
133 end_items = end_boundary, b"\n",
134 builder = functools.partial(
135 cls._build_part,
136 sep_boundary=sep_boundary,
137 )
138 part_groups = map(builder, data.items())
139 parts = itertools.chain.from_iterable(part_groups)
140 body_items = itertools.chain(parts, end_items)
141 content_type = 'multipart/form-data; boundary=%s' % boundary
142 return b''.join(body_items), content_type
143
144 def upload_file(self, filename):
145 with open(filename, 'rb') as f:
146 content = f.read()
147 meta = self.distribution.metadata
148 data = {
149 ':action': 'doc_upload',
150 'name': meta.get_name(),
151 'content': (os.path.basename(filename), content),
152 }
153 # set up the authentication
154 credentials = _encode(self.username + ':' + self.password)
155 credentials = standard_b64encode(credentials)
156 if not six.PY2:
157 credentials = credentials.decode('ascii')
158 auth = "Basic " + credentials
159
160 body, ct = self._build_multipart(data)
161
162 msg = "Submitting documentation to %s" % (self.repository)
163 self.announce(msg, log.INFO)
164
165 # build the Request
166 # We can't use urllib2 since we need to send the Basic
167 # auth right with the first request
168 schema, netloc, url, params, query, fragments = \
169 urllib.parse.urlparse(self.repository)
170 assert not params and not query and not fragments
171 if schema == 'http':
172 conn = http_client.HTTPConnection(netloc)
173 elif schema == 'https':
174 conn = http_client.HTTPSConnection(netloc)
175 else:
176 raise AssertionError("unsupported schema " + schema)
177
178 data = ''
179 try:
180 conn.connect()
181 conn.putrequest("POST", url)
182 content_type = ct
183 conn.putheader('Content-type', content_type)
184 conn.putheader('Content-length', str(len(body)))
185 conn.putheader('Authorization', auth)
186 conn.endheaders()
187 conn.send(body)
188 except socket.error as e:
189 self.announce(str(e), log.ERROR)
190 return
191
192 r = conn.getresponse()
193 if r.status == 200:
194 msg = 'Server response (%s): %s' % (r.status, r.reason)
195 self.announce(msg, log.INFO)
196 elif r.status == 301:
197 location = r.getheader('Location')
198 if location is None:
199 location = 'https://pythonhosted.org/%s/' % meta.get_name()
200 msg = 'Upload successful. Visit %s' % location
201 self.announce(msg, log.INFO)
202 else:
203 msg = 'Upload failed (%s): %s' % (r.status, r.reason)
204 self.announce(msg, log.ERROR)
205 if self.show_response:
206 print('-' * 75, r.read(), '-' * 75)