PageRenderTime 45ms CodeModel.GetById 31ms app.highlight 11ms RepoModel.GetById 2ms app.codeStats 0ms

/hyde/ext/publishers/pypi.py

http://github.com/hyde/hyde
Python | 137 lines | 112 code | 8 blank | 17 comment | 16 complexity | b602fab342fcdffddae122e93f637a7a MD5 | raw file
  1"""
  2Contains classes and utilities that help publishing a hyde website to
  3the documentation hosting on http://packages.python.org/.
  4
  5"""
  6
  7import os
  8import getpass
  9import zipfile
 10import tempfile
 11from base64 import standard_b64encode
 12
 13from hyde._compat import (configparser, input, HTTPConnection, HTTPSConnection,
 14                          parse)
 15from hyde.publisher import Publisher
 16
 17from commando.util import getLoggerWithNullHandler
 18logger = getLoggerWithNullHandler('hyde.ext.publishers.pypi')
 19
 20
 21class PyPI(Publisher):
 22
 23    def initialize(self, settings):
 24        self.settings = settings
 25        self.project = settings.project
 26        self.url = getattr(settings, "url", "https://pypi.python.org/pypi/")
 27        self.username = getattr(settings, "username", None)
 28        self.password = getattr(settings, "password", None)
 29        self.prompt_for_credentials()
 30
 31    def prompt_for_credentials(self):
 32        pypirc_file = os.path.expanduser("~/.pypirc")
 33        if not os.path.isfile(pypirc_file):
 34            pypirc = None
 35        else:
 36            pypirc = configparser.Rawconfigparser()
 37            pypirc.read([pypirc_file])
 38        missing_errs = (
 39            configparser.NoSectionError, configparser.NoOptionError)
 40        #  Try to find username in .pypirc
 41        if self.username is None:
 42            if pypirc is not None:
 43                try:
 44                    self.username = pypirc.get("server-login", "username")
 45                except missing_errs:
 46                    pass
 47        #  Prompt for username on command-line
 48        if self.username is None:
 49            print("Username: ",)
 50            self.username = input().strip()
 51        #  Try to find password in .pypirc
 52        if self.password is None:
 53            if pypirc is not None:
 54                try:
 55                    self.password = pypirc.get("server-login", "password")
 56                except missing_errs:
 57                    pass
 58        #  Prompt for username on command-line
 59        if self.password is None:
 60            self.password = getpass.getpass("Password: ")
 61        #  Validate the values.
 62        if not self.username:
 63            raise ValueError("PyPI requires a username")
 64        if not self.password:
 65            raise ValueError("PyPI requires a password")
 66
 67    def publish(self):
 68        super(PyPI, self).publish()
 69        tf = tempfile.TemporaryFile()
 70        try:
 71            #  Bundle it up into a zipfile
 72            logger.info("building the zipfile")
 73            root = self.site.config.deploy_root_path
 74            zf = zipfile.ZipFile(tf, "w", zipfile.ZIP_DEFLATED)
 75            try:
 76                for item in root.walker.walk_files():
 77                    logger.info("  adding file: %s", item.path)
 78                    zf.write(item.path, item.get_relative_path(root))
 79            finally:
 80                zf.close()
 81            #  Formulate the necessary bits for the HTTP POST.
 82            #  Multipart/form-data encoding.  Yuck.
 83            authz = self.username + ":" + self.password
 84            authz = "Basic " + standard_b64encode(authz)
 85            boundary = "-----------" + os.urandom(20).encode("hex")
 86            # *F841 local variable 'sep_boundary' is assigned to but never used
 87            # sep_boundary = "\r\n--" + boundary
 88            # *F841 local variable 'end_boundary' is assigned to but never used
 89            # end_boundary = "\r\n--" + boundary + "--\r\n"
 90            content_type = "multipart/form-data; boundary=%s" % (boundary,)
 91            items = ((":action", "doc_upload"), ("name", self.project))
 92            body_prefix = ""
 93            for (name, value) in items:
 94                body_prefix += "--" + boundary + "\r\n"
 95                body_prefix += "Content-Disposition: form-data; name=\""
 96                body_prefix += name + "\"\r\n\r\n"
 97                body_prefix += value + "\r\n"
 98            body_prefix += "--" + boundary + "\r\n"
 99            body_prefix += "Content-Disposition: form-data; name=\"content\""
100            body_prefix += "; filename=\"website.zip\"\r\n\r\n"
101            body_suffix = "\r\n--" + boundary + "--\r\n"
102            content_length = len(body_prefix) + tf.tell() + len(body_suffix)
103            #  POST it up to PyPI
104            logger.info("uploading to PyPI")
105            url = parse.urlparse(self.url)
106            if url.scheme == "https":
107                con = HTTPSConnection(url.netloc)
108            else:
109                con = HTTPConnection(url.netloc)
110            con.connect()
111            try:
112                con.putrequest("POST", self.url)
113                con.putheader("Content-Type", content_type)
114                con.putheader("Content-Length", str(content_length))
115                con.putheader("Authorization", authz)
116                con.endheaders()
117                con.send(body_prefix)
118                tf.seek(0)
119                data = tf.read(1024 * 32)
120                while data:
121                    con.send(data)
122                    data = tf.read(1024 * 32)
123                con.send(body_suffix)
124                r = con.getresponse()
125                try:
126                    #  PyPI tries to redirect to the page on success.
127                    if r.status in (200, 301,):
128                        logger.info("success!")
129                    else:
130                        msg = "Upload failed: %s %s" % (r.status, r.reason,)
131                        raise Exception(msg)
132                finally:
133                    r.close()
134            finally:
135                con.close()
136        finally:
137            tf.close()