diff options
| -rw-r--r-- | distribute.egg-info/entry_points.txt | 1 | ||||
| -rw-r--r-- | docs/setuptools.txt | 55 | ||||
| -rwxr-xr-x | setup.cfg | 3 | ||||
| -rw-r--r-- | setuptools/command/__init__.py | 2 | ||||
| -rw-r--r-- | setuptools/command/upload_docs.py | 153 |
5 files changed, 213 insertions, 1 deletions
diff --git a/distribute.egg-info/entry_points.txt b/distribute.egg-info/entry_points.txt index d18df338..f4b74da0 100644 --- a/distribute.egg-info/entry_points.txt +++ b/distribute.egg-info/entry_points.txt @@ -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 diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 06f632ec..7c679fd0 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -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 diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py index f898822b..ea544b50 100644 --- a/setuptools/command/__init__.py +++ b/setuptools/command/__init__.py @@ -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 diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py new file mode 100644 index 00000000..5bbdc7f2 --- /dev/null +++ b/setuptools/command/upload_docs.py @@ -0,0 +1,153 @@ +# -*- 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 |
