Path: blob/main/test/lib/python3.9/site-packages/setuptools/command/upload_docs.py
4799 views
# -*- coding: utf-8 -*-1"""upload_docs23Implements a Distutils 'upload_docs' subcommand (upload documentation to4sites other than PyPi such as devpi).5"""67from base64 import standard_b64encode8from distutils import log9from distutils.errors import DistutilsOptionError10import os11import socket12import zipfile13import tempfile14import shutil15import itertools16import functools17import http.client18import urllib.parse19import warnings2021from .._importlib import metadata22from .. import SetuptoolsDeprecationWarning2324from .upload import upload252627def _encode(s):28return s.encode('utf-8', 'surrogateescape')293031class upload_docs(upload):32# override the default repository as upload_docs isn't33# supported by Warehouse (and won't be).34DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/'3536description = 'Upload documentation to sites other than PyPi such as devpi'3738user_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]45boolean_options = upload.boolean_options4647def has_sphinx(self):48return bool(49self.upload_dir is None50and metadata.entry_points(group='distutils.commands', name='build_sphinx')51)5253sub_commands = [('build_sphinx', has_sphinx)]5455def initialize_options(self):56upload.initialize_options(self)57self.upload_dir = None58self.target_dir = None5960def finalize_options(self):61upload.finalize_options(self)62if self.upload_dir is None:63if self.has_sphinx():64build_sphinx = self.get_finalized_command('build_sphinx')65self.target_dir = dict(build_sphinx.builder_target_dirs)['html']66else:67build = self.get_finalized_command('build')68self.target_dir = os.path.join(build.build_base, 'docs')69else:70self.ensure_dirname('upload_dir')71self.target_dir = self.upload_dir72if 'pypi.python.org' in self.repository:73log.warn("Upload_docs command is deprecated for PyPi. Use RTD instead.")74self.announce('Using upload directory %s' % self.target_dir)7576def create_zipfile(self, filename):77zip_file = zipfile.ZipFile(filename, "w")78try:79self.mkpath(self.target_dir) # just in case80for root, dirs, files in os.walk(self.target_dir):81if root == self.target_dir and not files:82tmpl = "no files found in upload directory '%s'"83raise DistutilsOptionError(tmpl % self.target_dir)84for name in files:85full = os.path.join(root, name)86relative = root[len(self.target_dir):].lstrip(os.path.sep)87dest = os.path.join(relative, name)88zip_file.write(full, dest)89finally:90zip_file.close()9192def run(self):93warnings.warn(94"upload_docs is deprecated and will be removed in a future "95"version. Use tools like httpie or curl instead.",96SetuptoolsDeprecationWarning,97)9899# Run sub commands100for cmd_name in self.get_sub_commands():101self.run_command(cmd_name)102103tmp_dir = tempfile.mkdtemp()104name = self.distribution.metadata.get_name()105zip_file = os.path.join(tmp_dir, "%s.zip" % name)106try:107self.create_zipfile(zip_file)108self.upload_file(zip_file)109finally:110shutil.rmtree(tmp_dir)111112@staticmethod113def _build_part(item, sep_boundary):114key, values = item115title = '\nContent-Disposition: form-data; name="%s"' % key116# handle multiple entries for the same name117if not isinstance(values, list):118values = [values]119for value in values:120if isinstance(value, tuple):121title += '; filename="%s"' % value[0]122value = value[1]123else:124value = _encode(value)125yield sep_boundary126yield _encode(title)127yield b"\n\n"128yield value129if value and value[-1:] == b'\r':130yield b'\n' # write an extra newline (lurve Macs)131132@classmethod133def _build_multipart(cls, data):134"""135Build up the MIME payload for the POST data136"""137boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'138sep_boundary = b'\n--' + boundary.encode('ascii')139end_boundary = sep_boundary + b'--'140end_items = end_boundary, b"\n",141builder = functools.partial(142cls._build_part,143sep_boundary=sep_boundary,144)145part_groups = map(builder, data.items())146parts = itertools.chain.from_iterable(part_groups)147body_items = itertools.chain(parts, end_items)148content_type = 'multipart/form-data; boundary=%s' % boundary149return b''.join(body_items), content_type150151def upload_file(self, filename):152with open(filename, 'rb') as f:153content = f.read()154meta = self.distribution.metadata155data = {156':action': 'doc_upload',157'name': meta.get_name(),158'content': (os.path.basename(filename), content),159}160# set up the authentication161credentials = _encode(self.username + ':' + self.password)162credentials = standard_b64encode(credentials).decode('ascii')163auth = "Basic " + credentials164165body, ct = self._build_multipart(data)166167msg = "Submitting documentation to %s" % (self.repository)168self.announce(msg, log.INFO)169170# build the Request171# We can't use urllib2 since we need to send the Basic172# auth right with the first request173schema, netloc, url, params, query, fragments = \174urllib.parse.urlparse(self.repository)175assert not params and not query and not fragments176if schema == 'http':177conn = http.client.HTTPConnection(netloc)178elif schema == 'https':179conn = http.client.HTTPSConnection(netloc)180else:181raise AssertionError("unsupported schema " + schema)182183data = ''184try:185conn.connect()186conn.putrequest("POST", url)187content_type = ct188conn.putheader('Content-type', content_type)189conn.putheader('Content-length', str(len(body)))190conn.putheader('Authorization', auth)191conn.endheaders()192conn.send(body)193except socket.error as e:194self.announce(str(e), log.ERROR)195return196197r = conn.getresponse()198if r.status == 200:199msg = 'Server response (%s): %s' % (r.status, r.reason)200self.announce(msg, log.INFO)201elif r.status == 301:202location = r.getheader('Location')203if location is None:204location = 'https://pythonhosted.org/%s/' % meta.get_name()205msg = 'Upload successful. Visit %s' % location206self.announce(msg, log.INFO)207else:208msg = 'Upload failed (%s): %s' % (r.status, r.reason)209self.announce(msg, log.ERROR)210if self.show_response:211print('-' * 75, r.read(), '-' * 75)212213214