Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
upload.py209 linesDownload Raw Back to command
1"""2distutils.command.upload3 4Implements the Distutils 'upload' subcommand (upload package to a package5index).6"""7 8import hashlib9import io10import logging11import os12from base64 import standard_b64encode13from urllib.parse import urlparse14from urllib.request import HTTPError, Request, urlopen15 16from .._itertools import always_iterable17from ..core import PyPIRCCommand18from ..errors import DistutilsError, DistutilsOptionError19from ..spawn import spawn20 21# PyPI Warehouse supports MD5, SHA256, and Blake2 (blake2-256)22# https://bugs.python.org/issue4069823_FILE_CONTENT_DIGESTS = {24    "md5_digest": getattr(hashlib, "md5", None),25    "sha256_digest": getattr(hashlib, "sha256", None),26    "blake2_256_digest": getattr(hashlib, "blake2b", None),27}28 29 30class upload(PyPIRCCommand):31    description = "upload binary package to PyPI"32 33    user_options = PyPIRCCommand.user_options + [34        ('sign', 's', 'sign files to upload using gpg'),35        ('identity=', 'i', 'GPG identity used to sign files'),36    ]37 38    boolean_options = PyPIRCCommand.boolean_options + ['sign']39 40    def initialize_options(self):41        PyPIRCCommand.initialize_options(self)42        self.username = ''43        self.password = ''44        self.show_response = 045        self.sign = False46        self.identity = None47 48    def finalize_options(self):49        PyPIRCCommand.finalize_options(self)50        if self.identity and not self.sign:51            raise DistutilsOptionError("Must use --sign for --identity to have meaning")52        config = self._read_pypirc()53        if config != {}:54            self.username = config['username']55            self.password = config['password']56            self.repository = config['repository']57            self.realm = config['realm']58 59        # getting the password from the distribution60        # if previously set by the register command61        if not self.password and self.distribution.password:62            self.password = self.distribution.password63 64    def run(self):65        if not self.distribution.dist_files:66            msg = (67                "Must create and upload files in one command "68                "(e.g. setup.py sdist upload)"69            )70            raise DistutilsOptionError(msg)71        for command, pyversion, filename in self.distribution.dist_files:72            self.upload_file(command, pyversion, filename)73 74    def upload_file(self, command, pyversion, filename):  # noqa: C90175        # Makes sure the repository URL is compliant76        schema, netloc, url, params, query, fragments = urlparse(self.repository)77        if params or query or fragments:78            raise AssertionError("Incompatible url %s" % self.repository)79 80        if schema not in ('http', 'https'):81            raise AssertionError("unsupported schema " + schema)82 83        # Sign if requested84        if self.sign:85            gpg_args = ["gpg", "--detach-sign", "-a", filename]86            if self.identity:87                gpg_args[2:2] = ["--local-user", self.identity]88            spawn(gpg_args, dry_run=self.dry_run)89 90        # Fill in the data - send all the meta-data in case we need to91        # register a new release92        f = open(filename, 'rb')93        try:94            content = f.read()95        finally:96            f.close()97 98        meta = self.distribution.metadata99        data = {100            # action101            ':action': 'file_upload',102            'protocol_version': '1',103            # identify release104            'name': meta.get_name(),105            'version': meta.get_version(),106            # file content107            'content': (os.path.basename(filename), content),108            'filetype': command,109            'pyversion': pyversion,110            # additional meta-data111            'metadata_version': '1.0',112            'summary': meta.get_description(),113            'home_page': meta.get_url(),114            'author': meta.get_contact(),115            'author_email': meta.get_contact_email(),116            'license': meta.get_licence(),117            'description': meta.get_long_description(),118            'keywords': meta.get_keywords(),119            'platform': meta.get_platforms(),120            'classifiers': meta.get_classifiers(),121            'download_url': meta.get_download_url(),122            # PEP 314123            'provides': meta.get_provides(),124            'requires': meta.get_requires(),125            'obsoletes': meta.get_obsoletes(),126        }127 128        data['comment'] = ''129 130        # file content digests131        for digest_name, digest_cons in _FILE_CONTENT_DIGESTS.items():132            if digest_cons is None:133                continue134            try:135                data[digest_name] = digest_cons(content).hexdigest()136            except ValueError:137                # hash digest not available or blocked by security policy138                pass139 140        if self.sign:141            with open(filename + ".asc", "rb") as f:142                data['gpg_signature'] = (os.path.basename(filename) + ".asc", f.read())143 144        # set up the authentication145        user_pass = (self.username + ":" + self.password).encode('ascii')146        # The exact encoding of the authentication string is debated.147        # Anyway PyPI only accepts ascii for both username or password.148        auth = "Basic " + standard_b64encode(user_pass).decode('ascii')149 150        # Build up the MIME payload for the POST data151        boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'152        sep_boundary = b'\r\n--' + boundary.encode('ascii')153        end_boundary = sep_boundary + b'--\r\n'154        body = io.BytesIO()155        for key, values in data.items():156            title = '\r\nContent-Disposition: form-data; name="%s"' % key157            for value in make_iterable(values):158                if type(value) is tuple:159                    title += '; filename="%s"' % value[0]160                    value = value[1]161                else:162                    value = str(value).encode('utf-8')163                body.write(sep_boundary)164                body.write(title.encode('utf-8'))165                body.write(b"\r\n\r\n")166                body.write(value)167        body.write(end_boundary)168        body = body.getvalue()169 170        msg = f"Submitting {filename} to {self.repository}"171        self.announce(msg, logging.INFO)172 173        # build the Request174        headers = {175            'Content-type': 'multipart/form-data; boundary=%s' % boundary,176            'Content-length': str(len(body)),177            'Authorization': auth,178        }179 180        request = Request(self.repository, data=body, headers=headers)181        # send the data182        try:183            result = urlopen(request)184            status = result.getcode()185            reason = result.msg186        except HTTPError as e:187            status = e.code188            reason = e.msg189        except OSError as e:190            self.announce(str(e), logging.ERROR)191            raise192 193        if status == 200:194            self.announce(f'Server response ({status}): {reason}', logging.INFO)195            if self.show_response:196                text = self._read_pypi_response(result)197                msg = '\n'.join(('-' * 75, text, '-' * 75))198                self.announce(msg, logging.INFO)199        else:200            msg = f'Upload failed ({status}): {reason}'201            self.announce(msg, logging.ERROR)202            raise DistutilsError(msg)203 204 205def make_iterable(values):206    if values is None:207        return [None]208    return always_iterable(values, base_type=(bytes, str, tuple))209 
codekingpro/portable-devtools · Team Ai