Browse Source

Merge pull request #49 from rfk/hyde

---

This is a simple publisher that uploads a zipfile to PyPI.  I am starting to do some basic websites for my python modules using Hyde and hosting them on packages.python.org.
main
Lakshmi Vyasarajan 13 years ago
parent
commit
29cf0ab32a
5 changed files with 160 additions and 4 deletions
  1. +1
    -1
      AUTHORS.rst
  2. +1
    -1
      CHANGELOG.rst
  3. +140
    -0
      hyde/ext/publishers/pypi.py
  4. +14
    -0
      hyde/ext/templates/jinja.py
  5. +4
    -2
      hyde/site.py

+ 1
- 1
AUTHORS.rst View File

@@ -31,7 +31,7 @@ Contributors

* Bug fix: LessCSSPlugin: return original text if not a .less file
* Added 'use_figure' configuration option for syntax tag
* Pyfs publisher with `mimetype` and `etags` support
* PyFS publisher with `mtime` and `etags` support

- |tinnet|_



+ 1
- 1
CHANGELOG.rst View File

@@ -37,7 +37,7 @@ Thanks to @rfk.

* Updated to use nose 1.0
* Bug fix: LessCSSPlugin: return original text if not a .less file
* PyFS publisher with mimetypes and etags support.
* PyFS publisher with mtime and etags support.

Version 0.8
==============


+ 140
- 0
hyde/ext/publishers/pypi.py View File

@@ -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()


+ 14
- 0
hyde/ext/templates/jinja.py View File

@@ -48,6 +48,13 @@ def content_url(context, path):
"""
return context['site'].content_url(path)

@contextfunction
def full_url(context, path):
"""
Returns the full url given a partial path.
"""
return context['site'].full_url(path)


@contextfilter
def date_format(ctx, dt, fmt=None):
@@ -561,6 +568,7 @@ class Jinja2Template(Template):
extensions=settings['extensions'])
self.env.globals['media_url'] = media_url
self.env.globals['content_url'] = content_url
self.env.globals['full_url'] = full_url
self.env.globals['engine'] = engine
self.env.globals['deps'] = {}
self.env.filters['markdown'] = markdown
@@ -669,6 +677,12 @@ class Jinja2Template(Template):
"""
return '{{ media_url(\'%s\') }}' % url

def get_full_url_statement(self, url):
"""
Returns the full url statement.
"""
return '{{ full_url(\'%s\') }}' % url

def render_resource(self, resource, context):
"""
Renders the given resource using the context


+ 4
- 2
hyde/site.py View File

@@ -4,6 +4,7 @@ Parses & holds information about the site to be generated.
"""
import os
import fnmatch
import urlparse
from hyde.exceptions import HydeException
from hyde.fs import FS, File, Folder
from hyde.model import Config
@@ -398,7 +399,8 @@ class Site(object):
Determines if the given path is media or content based on the
configuration and returns the appropriate url.
"""

if urlparse.urlparse(path)[:2] != ("",""):
return path
if self.is_media(path):
return self.media_url(
FS(path).get_relative_path(
@@ -411,4 +413,4 @@ class Site(object):
Given the relative path, determines if it is content or media.
"""
folder = self.content.source.child_folder(path)
return folder.is_descendant_of(self.config.media_root_path)
return folder.is_descendant_of(self.config.media_root_path)

Loading…
Cancel
Save