Path: blob/master/venv/Lib/site-packages/setuptools/command/upload_docs.py
811 views
# -*- coding: utf-8 -*-1"""upload_docs23Implements a Distutils 'upload_docs' subcommand (upload documentation to4PyPI's pythonhosted.org).5"""67from base64 import standard_b64encode8from distutils import log9from distutils.errors import DistutilsOptionError10import os11import socket12import zipfile13import tempfile14import shutil15import itertools16import functools1718from setuptools.extern import six19from setuptools.extern.six.moves import http_client, urllib2021from pkg_resources import iter_entry_points22from .upload import upload232425def _encode(s):26errors = 'strict' if six.PY2 else 'surrogateescape'27return s.encode('utf-8', errors)282930class upload_docs(upload):31# override the default repository as upload_docs isn't32# supported by Warehouse (and won't be).33DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/'3435description = 'Upload documentation to PyPI'3637user_options = [38('repository=', 'r',39"url of repository [default: %s]" % upload.DEFAULT_REPOSITORY),40('show-response', None,41'display full response text from server'),42('upload-dir=', None, 'directory to upload'),43]44boolean_options = upload.boolean_options4546def has_sphinx(self):47if self.upload_dir is None:48for ep in iter_entry_points('distutils.commands', 'build_sphinx'):49return True5051sub_commands = [('build_sphinx', has_sphinx)]5253def initialize_options(self):54upload.initialize_options(self)55self.upload_dir = None56self.target_dir = None5758def finalize_options(self):59upload.finalize_options(self)60if self.upload_dir is None:61if self.has_sphinx():62build_sphinx = self.get_finalized_command('build_sphinx')63self.target_dir = build_sphinx.builder_target_dir64else:65build = self.get_finalized_command('build')66self.target_dir = os.path.join(build.build_base, 'docs')67else:68self.ensure_dirname('upload_dir')69self.target_dir = self.upload_dir70if 'pypi.python.org' in self.repository:71log.warn("Upload_docs command is deprecated. Use RTD instead.")72self.announce('Using upload directory %s' % self.target_dir)7374def create_zipfile(self, filename):75zip_file = zipfile.ZipFile(filename, "w")76try:77self.mkpath(self.target_dir) # just in case78for root, dirs, files in os.walk(self.target_dir):79if root == self.target_dir and not files:80tmpl = "no files found in upload directory '%s'"81raise DistutilsOptionError(tmpl % self.target_dir)82for name in files:83full = os.path.join(root, name)84relative = root[len(self.target_dir):].lstrip(os.path.sep)85dest = os.path.join(relative, name)86zip_file.write(full, dest)87finally:88zip_file.close()8990def run(self):91# Run sub commands92for cmd_name in self.get_sub_commands():93self.run_command(cmd_name)9495tmp_dir = tempfile.mkdtemp()96name = self.distribution.metadata.get_name()97zip_file = os.path.join(tmp_dir, "%s.zip" % name)98try:99self.create_zipfile(zip_file)100self.upload_file(zip_file)101finally:102shutil.rmtree(tmp_dir)103104@staticmethod105def _build_part(item, sep_boundary):106key, values = item107title = '\nContent-Disposition: form-data; name="%s"' % key108# handle multiple entries for the same name109if not isinstance(values, list):110values = [values]111for value in values:112if isinstance(value, tuple):113title += '; filename="%s"' % value[0]114value = value[1]115else:116value = _encode(value)117yield sep_boundary118yield _encode(title)119yield b"\n\n"120yield value121if value and value[-1:] == b'\r':122yield b'\n' # write an extra newline (lurve Macs)123124@classmethod125def _build_multipart(cls, data):126"""127Build up the MIME payload for the POST data128"""129boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'130sep_boundary = b'\n--' + boundary.encode('ascii')131end_boundary = sep_boundary + b'--'132end_items = end_boundary, b"\n",133builder = functools.partial(134cls._build_part,135sep_boundary=sep_boundary,136)137part_groups = map(builder, data.items())138parts = itertools.chain.from_iterable(part_groups)139body_items = itertools.chain(parts, end_items)140content_type = 'multipart/form-data; boundary=%s' % boundary141return b''.join(body_items), content_type142143def upload_file(self, filename):144with open(filename, 'rb') as f:145content = f.read()146meta = self.distribution.metadata147data = {148':action': 'doc_upload',149'name': meta.get_name(),150'content': (os.path.basename(filename), content),151}152# set up the authentication153credentials = _encode(self.username + ':' + self.password)154credentials = standard_b64encode(credentials)155if not six.PY2:156credentials = credentials.decode('ascii')157auth = "Basic " + credentials158159body, ct = self._build_multipart(data)160161msg = "Submitting documentation to %s" % (self.repository)162self.announce(msg, log.INFO)163164# build the Request165# We can't use urllib2 since we need to send the Basic166# auth right with the first request167schema, netloc, url, params, query, fragments = \168urllib.parse.urlparse(self.repository)169assert not params and not query and not fragments170if schema == 'http':171conn = http_client.HTTPConnection(netloc)172elif schema == 'https':173conn = http_client.HTTPSConnection(netloc)174else:175raise AssertionError("unsupported schema " + schema)176177data = ''178try:179conn.connect()180conn.putrequest("POST", url)181content_type = ct182conn.putheader('Content-type', content_type)183conn.putheader('Content-length', str(len(body)))184conn.putheader('Authorization', auth)185conn.endheaders()186conn.send(body)187except socket.error as e:188self.announce(str(e), log.ERROR)189return190191r = conn.getresponse()192if r.status == 200:193msg = 'Server response (%s): %s' % (r.status, r.reason)194self.announce(msg, log.INFO)195elif r.status == 301:196location = r.getheader('Location')197if location is None:198location = 'https://pythonhosted.org/%s/' % meta.get_name()199msg = 'Upload successful. Visit %s' % location200self.announce(msg, log.INFO)201else:202msg = 'Upload failed (%s): %s' % (r.status, r.reason)203self.announce(msg, log.ERROR)204if self.show_response:205print('-' * 75, r.read(), '-' * 75)206207208