Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
upload_docs.py222 linesDownload Raw Back to command
1"""upload_docs2 3Implements a Distutils 'upload_docs' subcommand (upload documentation to4sites other than PyPi such as devpi).5"""6 7from base64 import standard_b64encode8from distutils import log9from distutils.errors import DistutilsOptionError10import os11import zipfile12import tempfile13import shutil14import itertools15import functools16import http.client17import urllib.parse18 19from .._importlib import metadata20from ..warnings import SetuptoolsDeprecationWarning21 22from .upload import upload23 24 25def _encode(s):26    return s.encode('utf-8', 'surrogateescape')27 28 29class upload_docs(upload):30    # override the default repository as upload_docs isn't31    # supported by Warehouse (and won't be).32    DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/'33 34    description = 'Upload documentation to sites other than PyPi such as devpi'35 36    user_options = [37        (38            'repository=',39            'r',40            "url of repository [default: %s]" % upload.DEFAULT_REPOSITORY,41        ),42        ('show-response', None, 'display full response text from server'),43        ('upload-dir=', None, 'directory to upload'),44    ]45    boolean_options = upload.boolean_options46 47    def has_sphinx(self):48        return bool(49            self.upload_dir is None50            and metadata.entry_points(group='distutils.commands', name='build_sphinx')51        )52 53    sub_commands = [('build_sphinx', has_sphinx)]  # type: ignore[list-item] # TODO: Fix in typeshed distutils stubs54 55    def initialize_options(self):56        upload.initialize_options(self)57        self.upload_dir = None58        self.target_dir = None59 60    def finalize_options(self):61        log.warn(62            "Upload_docs command is deprecated. Use Read the Docs "63            "(https://readthedocs.org) instead."64        )65        upload.finalize_options(self)66        if self.upload_dir is None:67            if self.has_sphinx():68                build_sphinx = self.get_finalized_command('build_sphinx')69                self.target_dir = dict(build_sphinx.builder_target_dirs)['html']70            else:71                build = self.get_finalized_command('build')72                self.target_dir = os.path.join(build.build_base, 'docs')73        else:74            self.ensure_dirname('upload_dir')75            self.target_dir = self.upload_dir76        self.announce('Using upload directory %s' % self.target_dir)77 78    def create_zipfile(self, filename):79        zip_file = zipfile.ZipFile(filename, "w")80        try:81            self.mkpath(self.target_dir)  # just in case82            for root, dirs, files in os.walk(self.target_dir):83                if root == self.target_dir and not files:84                    tmpl = "no files found in upload directory '%s'"85                    raise DistutilsOptionError(tmpl % self.target_dir)86                for name in files:87                    full = os.path.join(root, name)88                    relative = root[len(self.target_dir) :].lstrip(os.path.sep)89                    dest = os.path.join(relative, name)90                    zip_file.write(full, dest)91        finally:92            zip_file.close()93 94    def run(self):95        SetuptoolsDeprecationWarning.emit(96            "Deprecated command",97            """98            upload_docs is deprecated and will be removed in a future version.99            Instead, use tools like devpi and Read the Docs; or lower level tools like100            httpie and curl to interact directly with your hosting service API.101            """,102            due_date=(2023, 9, 26),  # warning introduced in 27 Jul 2022103        )104 105        # Run sub commands106        for cmd_name in self.get_sub_commands():107            self.run_command(cmd_name)108 109        tmp_dir = tempfile.mkdtemp()110        name = self.distribution.metadata.get_name()111        zip_file = os.path.join(tmp_dir, "%s.zip" % name)112        try:113            self.create_zipfile(zip_file)114            self.upload_file(zip_file)115        finally:116            shutil.rmtree(tmp_dir)117 118    @staticmethod119    def _build_part(item, sep_boundary):120        key, values = item121        title = '\nContent-Disposition: form-data; name="%s"' % key122        # handle multiple entries for the same name123        if not isinstance(values, list):124            values = [values]125        for value in values:126            if isinstance(value, tuple):127                title += '; filename="%s"' % value[0]128                value = value[1]129            else:130                value = _encode(value)131            yield sep_boundary132            yield _encode(title)133            yield b"\n\n"134            yield value135            if value and value[-1:] == b'\r':136                yield b'\n'  # write an extra newline (lurve Macs)137 138    @classmethod139    def _build_multipart(cls, data):140        """141        Build up the MIME payload for the POST data142        """143        boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'144        sep_boundary = b'\n--' + boundary.encode('ascii')145        end_boundary = sep_boundary + b'--'146        end_items = (147            end_boundary,148            b"\n",149        )150        builder = functools.partial(151            cls._build_part,152            sep_boundary=sep_boundary,153        )154        part_groups = map(builder, data.items())155        parts = itertools.chain.from_iterable(part_groups)156        body_items = itertools.chain(parts, end_items)157        content_type = 'multipart/form-data; boundary=%s' % boundary158        return b''.join(body_items), content_type159 160    def upload_file(self, filename):161        with open(filename, 'rb') as f:162            content = f.read()163        meta = self.distribution.metadata164        data = {165            ':action': 'doc_upload',166            'name': meta.get_name(),167            'content': (os.path.basename(filename), content),168        }169        # set up the authentication170        credentials = _encode(self.username + ':' + self.password)171        credentials = standard_b64encode(credentials).decode('ascii')172        auth = "Basic " + credentials173 174        body, ct = self._build_multipart(data)175 176        msg = "Submitting documentation to %s" % (self.repository)177        self.announce(msg, log.INFO)178 179        # build the Request180        # We can't use urllib2 since we need to send the Basic181        # auth right with the first request182        schema, netloc, url, params, query, fragments = urllib.parse.urlparse(183            self.repository184        )185        assert not params and not query and not fragments186        if schema == 'http':187            conn = http.client.HTTPConnection(netloc)188        elif schema == 'https':189            conn = http.client.HTTPSConnection(netloc)190        else:191            raise AssertionError("unsupported schema " + schema)192 193        data = ''194        try:195            conn.connect()196            conn.putrequest("POST", url)197            content_type = ct198            conn.putheader('Content-type', content_type)199            conn.putheader('Content-length', str(len(body)))200            conn.putheader('Authorization', auth)201            conn.endheaders()202            conn.send(body)203        except OSError as e:204            self.announce(str(e), log.ERROR)205            return206 207        r = conn.getresponse()208        if r.status == 200:209            msg = 'Server response (%s): %s' % (r.status, r.reason)210            self.announce(msg, log.INFO)211        elif r.status == 301:212            location = r.getheader('Location')213            if location is None:214                location = 'https://pythonhosted.org/%s/' % meta.get_name()215            msg = 'Upload successful. Visit %s' % location216            self.announce(msg, log.INFO)217        else:218            msg = 'Upload failed (%s): %s' % (r.status, r.reason)219            self.announce(msg, log.ERROR)220        if self.show_response:221            print('-' * 75, r.read(), '-' * 75)222 
codekingpro/portable-devtools · Team Ai