A fork of hyde, the static site generation. Some patches will be pushed upstream.
 
 
 

140 lines
5.4 KiB

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