|
|
@@ -0,0 +1,140 @@ |
|
|
|
""" |
|
|
|
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.fs import File, Folder |
|
|
|
from hyde.publisher import Publisher |
|
|
|
|
|
|
|
from hyde.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 = raw_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") |
|
|
|
sep_boundary = "\r\n--" + boundary |
|
|
|
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() |
|
|
|
|
|
|
|
|