Commit 45a293a4 authored by Jannis Leidel's avatar Jannis Leidel

Added a upload_docs command to upload project documentation to PyPI's packages.python.org

--HG--
branch : distribute
extra : rebase_source : e8d62df101ab017b71ec9c923f64bc3494ba8979
parent 847659f5
......@@ -7,6 +7,7 @@ build_py = setuptools.command.build_py:build_py
saveopts = setuptools.command.saveopts:saveopts
egg_info = setuptools.command.egg_info:egg_info
register = setuptools.command.register:register
upload_docs = setuptools.command.upload_docs:upload_docs
install_egg_info = setuptools.command.install_egg_info:install_egg_info
alias = setuptools.command.alias:alias
easy_install = setuptools.command.easy_install:easy_install
......
......@@ -2369,6 +2369,61 @@ The ``upload`` command has a few options worth noting:
The URL of the repository to upload to. Defaults to
http://pypi.python.org/pypi (i.e., the main PyPI installation).
.. _upload_docs:
``upload_docs`` - Upload package documentation to PyPI
======================================================
PyPI now supports uploading project documentation to the dedicated URL
http://packages.python.org/<project>/.
The ``upload_docs`` command will create the necessary zip file out of a
documentation directory and will post to the repository.
Note that to upload the documentation of a project, the corresponding version
must already be registered with PyPI, using the distutils ``register``
command -- just like the ``upload`` command.
Assuming there is an ``Example`` project with documentation in the
subdirectory ``docs``, e.g.::
Example/
|-- example.py
|-- setup.cfg
|-- setup.py
|-- docs
| |-- build
| | `-- html
| | | |-- index.html
| | | `-- tips_tricks.html
| |-- conf.py
| |-- index.txt
| `-- tips_tricks.txt
You can simply pass the documentation directory path to the ``upload_docs``
command::
python setup.py upload_docs --upload-dir=docs/build/html
As with any other ``setuptools`` based command, you can define useful
defaults in the ``setup.cfg`` of your Python project, e.g.::
[upload_docs]
upload-dir = docs/build/html
The ``upload_docs`` command has the following options:
``--upload-dir``
The directory to be uploaded to the repository.
``--show-response``
Display the full response text from server; this is useful for debugging
PyPI problems.
``--repository=URL, -r URL``
The URL of the repository to upload to. Defaults to
http://pypi.python.org/pypi (i.e., the main PyPI installation).
------------------------------------
Extending and Reusing ``setuptools``
......
......@@ -6,3 +6,6 @@ tag_svn_revision = 1
release = egg_info -RDb ''
source = register sdist binary
binary = bdist_egg upload --show-response
[upload_docs]
upload-dir = docs/build/html
\ No newline at end of file
......@@ -2,7 +2,7 @@ __all__ = [
'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop',
'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts',
'sdist', 'setopt', 'test', 'upload', 'install_egg_info', 'install_scripts',
'register', 'bdist_wininst',
'register', 'bdist_wininst', 'upload_docs',
]
import sys
......
# -*- coding: utf-8 -*-
"""upload_docs
Implements a Distutils 'upload_docs' subcommand (upload documentation to
PyPI's packages.python.org).
"""
import os
import socket
import zipfile
import httplib
import base64
import urlparse
import tempfile
import cStringIO as StringIO
from distutils import log
from distutils.errors import DistutilsOptionError
from distutils.command.upload import upload
class upload_docs(upload):
description = 'Upload documentation to PyPI'
user_options = [
('repository=', 'r',
"url of repository [default: %s]" % upload.DEFAULT_REPOSITORY),
('show-response', None,
'display full response text from server'),
('upload-dir=', None, 'directory to upload'),
]
boolean_options = upload.boolean_options
def initialize_options(self):
upload.initialize_options(self)
self.upload_dir = None
def finalize_options(self):
upload.finalize_options(self)
if self.upload_dir is None:
build = self.get_finalized_command('build')
self.upload_dir = os.path.join(build.build_base, 'docs')
self.mkpath(self.upload_dir)
self.ensure_dirname('upload_dir')
self.announce('Using upload directory %s' % self.upload_dir)
def create_zipfile(self):
name = self.distribution.metadata.get_name()
tmp_dir = tempfile.mkdtemp()
tmp_file = os.path.join(tmp_dir, "%s.zip" % name)
zip_file = zipfile.ZipFile(tmp_file, "w")
for root, dirs, files in os.walk(self.upload_dir):
if not files:
raise DistutilsOptionError(
"no files found in upload directory '%s'"
% self.upload_dir)
for name in files:
full = os.path.join(root, name)
relative = root[len(self.upload_dir):].lstrip(os.path.sep)
dest = os.path.join(relative, name)
zip_file.write(full, dest)
zip_file.close()
return tmp_file
def run(self):
zip_file = self.create_zipfile()
self.upload_file(zip_file)
def upload_file(self, filename):
content = open(filename, 'rb').read()
meta = self.distribution.metadata
data = {
':action': 'doc_upload',
'name': meta.get_name(),
'content': (os.path.basename(filename), content),
}
# set up the authentication
auth = "Basic " + base64.encodestring(
self.username + ":" + self.password).strip()
# Build up the MIME payload for the POST data
boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
sep_boundary = '\n--' + boundary
end_boundary = sep_boundary + '--'
body = StringIO.StringIO()
for key, value in data.items():
# handle multiple entries for the same name
if type(value) != type([]):
value = [value]
for value in value:
if type(value) is tuple:
fn = ';filename="%s"' % value[0]
value = value[1]
else:
fn = ""
value = str(value)
body.write(sep_boundary)
body.write('\nContent-Disposition: form-data; name="%s"'%key)
body.write(fn)
body.write("\n\n")
body.write(value)
if value and value[-1] == '\r':
body.write('\n') # write an extra newline (lurve Macs)
body.write(end_boundary)
body.write("\n")
body = body.getvalue()
self.announce("Submitting documentation to %s" % (self.repository),
log.INFO)
# build the Request
# We can't use urllib2 since we need to send the Basic
# auth right with the first request
schema, netloc, url, params, query, fragments = \
urlparse.urlparse(self.repository)
assert not params and not query and not fragments
if schema == 'http':
http = httplib.HTTPConnection(netloc)
elif schema == 'https':
http = httplib.HTTPSConnection(netloc)
else:
raise AssertionError("unsupported schema "+schema)
data = ''
loglevel = log.INFO
try:
http.connect()
http.putrequest("POST", url)
http.putheader('Content-type',
'multipart/form-data; boundary=%s'%boundary)
http.putheader('Content-length', str(len(body)))
http.putheader('Authorization', auth)
http.endheaders()
http.send(body)
except socket.error, e:
self.announce(str(e), log.ERROR)
return
r = http.getresponse()
if r.status == 200:
self.announce('Server response (%s): %s' % (r.status, r.reason),
log.INFO)
elif r.status == 301:
location = r.getheader('Location')
if location is None:
location = 'http://packages.python.org/%s/' % meta.get_name()
self.announce('Upload successful. Visit %s' % location,
log.INFO)
else:
self.announce('Upload failed (%s): %s' % (r.status, r.reason),
log.ERROR)
if self.show_response:
print '-'*75, r.read(), '-'*75
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment