|
- """
- Contains classes and utilities that help publishing a hyde website to
- the documentation hosting on http://packages.python.org/.
-
- """
-
- import os
- import getpass
- import zipfile
- import tempfile
- import httplib
- import urlparse
- from base64 import standard_b64encode
- import ConfigParser
-
- from hyde._compat import input
- from hyde.publisher import Publisher
-
- from commando.util import getLoggerWithNullHandler
- logger = getLoggerWithNullHandler('hyde.ext.publishers.pypi')
-
-
- class PyPI(Publisher):
-
- def initialize(self, settings):
- self.settings = settings
- self.project = settings.project
- self.url = getattr(settings, "url", "https://pypi.python.org/pypi/")
- self.username = getattr(settings, "username", None)
- self.password = getattr(settings, "password", None)
- self.prompt_for_credentials()
-
- def prompt_for_credentials(self):
- pypirc_file = os.path.expanduser("~/.pypirc")
- if not os.path.isfile(pypirc_file):
- pypirc = None
- else:
- pypirc = ConfigParser.RawConfigParser()
- pypirc.read([pypirc_file])
- missing_errs = (
- ConfigParser.NoSectionError, ConfigParser.NoOptionError)
- # Try to find username in .pypirc
- if self.username is None:
- if pypirc is not None:
- try:
- self.username = pypirc.get("server-login", "username")
- except missing_errs:
- pass
- # Prompt for username on command-line
- if self.username is None:
- print("Username: ",)
- self.username = input().strip()
- # Try to find password in .pypirc
- if self.password is None:
- if pypirc is not None:
- try:
- self.password = pypirc.get("server-login", "password")
- except missing_errs:
- pass
- # Prompt for username on command-line
- if self.password is None:
- self.password = getpass.getpass("Password: ")
- # Validate the values.
- if not self.username:
- raise ValueError("PyPI requires a username")
- if not self.password:
- raise ValueError("PyPI requires a password")
-
- def publish(self):
- super(PyPI, self).publish()
- tf = tempfile.TemporaryFile()
- try:
- # Bundle it up into a zipfile
- logger.info("building the zipfile")
- root = self.site.config.deploy_root_path
- zf = zipfile.ZipFile(tf, "w", zipfile.ZIP_DEFLATED)
- try:
- for item in root.walker.walk_files():
- logger.info(" adding file: %s", item.path)
- zf.write(item.path, item.get_relative_path(root))
- finally:
- zf.close()
- # Formulate the necessary bits for the HTTP POST.
- # Multipart/form-data encoding. Yuck.
- authz = self.username + ":" + self.password
- authz = "Basic " + standard_b64encode(authz)
- boundary = "-----------" + os.urandom(20).encode("hex")
- # *F841 local variable 'sep_boundary' is assigned to but never used
- # sep_boundary = "\r\n--" + boundary
- # *F841 local variable 'end_boundary' is assigned to but never used
- # end_boundary = "\r\n--" + boundary + "--\r\n"
- content_type = "multipart/form-data; boundary=%s" % (boundary,)
- items = ((":action", "doc_upload"), ("name", self.project))
- body_prefix = ""
- for (name, value) in items:
- body_prefix += "--" + boundary + "\r\n"
- body_prefix += "Content-Disposition: form-data; name=\""
- body_prefix += name + "\"\r\n\r\n"
- body_prefix += value + "\r\n"
- body_prefix += "--" + boundary + "\r\n"
- body_prefix += "Content-Disposition: form-data; name=\"content\""
- body_prefix += "; filename=\"website.zip\"\r\n\r\n"
- body_suffix = "\r\n--" + boundary + "--\r\n"
- content_length = len(body_prefix) + tf.tell() + len(body_suffix)
- # POST it up to PyPI
- logger.info("uploading to PyPI")
- url = urlparse.urlparse(self.url)
- if url.scheme == "https":
- con = httplib.HTTPSConnection(url.netloc)
- else:
- con = httplib.HTTPConnection(url.netloc)
- con.connect()
- try:
- con.putrequest("POST", self.url)
- con.putheader("Content-Type", content_type)
- con.putheader("Content-Length", str(content_length))
- con.putheader("Authorization", authz)
- con.endheaders()
- con.send(body_prefix)
- tf.seek(0)
- data = tf.read(1024 * 32)
- while data:
- con.send(data)
- data = tf.read(1024 * 32)
- con.send(body_suffix)
- r = con.getresponse()
- try:
- # PyPI tries to redirect to the page on success.
- if r.status in (200, 301,):
- logger.info("success!")
- else:
- msg = "Upload failed: %s %s" % (r.status, r.reason,)
- raise Exception(msg)
- finally:
- r.close()
- finally:
- con.close()
- finally:
- tf.close()
|