jezdez / Sphinx-PyPI-upload

setuptools commands to help uploading of Sphinx documentation to PyPI

Changed (Δ8.4 KB):

raw changeset »

LICENSE (28 lines added, 0 lines removed)

setup.py (30 lines added, 0 lines removed)

sphinx_pypi_upload.py (165 lines added, 0 lines removed)

Up to file-list LICENSE:

1
Copyright (c) 2009, Jannis Leidel
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without
5
modification, are permitted provided that the following conditions are
6
met:
7
8
    * Redistributions of source code must retain the above copyright
9
      notice, this list of conditions and the following disclaimer.
10
    * Redistributions in binary form must reproduce the above
11
      copyright notice, this list of conditions and the following
12
      disclaimer in the documentation and/or other materials provided
13
      with the distribution.
14
    * Neither the name of the author nor the names of other
15
      contributors may be used to endorse or promote products derived
16
      from this software without specific prior written permission.
17
18
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Up to file-list setup.py:

1
from setuptools import setup
2
3
setup(
4
    name="Sphinx-PyPI-upload",
5
    version='0.1',
6
    author="Jannis Leidel",
7
    author_email="jannis@leidel.info",
8
    url="http://bitbucket.org/jezdez/sphinx-pypi-upload/",
9
    download_url="http://bitbucket.org/jezdez/sphinx-pypi-upload/downloads/",
10
    description="setuptools commands to help uploading of Sphinx documentation to PyPI",
11
    license="BSD",
12
    classifiers=[
13
        "Topic :: Documentation",
14
        "Framework :: Setuptools Plugin",
15
        "Development Status :: 4 - Beta",
16
        "Programming Language :: Python",
17
        "Intended Audience :: Developers",
18
        "Operating System :: OS Independent",
19
        'License :: OSI Approved :: BSD License',
20
        "Topic :: Software Development :: Documentation",
21
        "Topic :: Software Development :: Libraries :: Python Modules",
22
    ],
23
    platforms='any',
24
    py_modules=["sphinx_pypi_upload"],
25
    entry_points = {
26
        "distutils.commands": [
27
            "upload_sphinx = sphinx_pypi_upload:upload",
28
        ]
29
    }
30
)

Up to file-list sphinx_pypi_upload.py:

1
# -*- coding: utf-8 -*-
2
"""
3
    sphinx_pypi_upload
4
    ~~~~~~~~~~~~~~~~~~
5
6
    setuptools commands to help uploading of Sphinx documentation to PyPI
7
8
    :author: Jannis Leidel
9
    :contact: jannis@leidel.info
10
    :copyright: Copyright 2009, Jannis Leidel.
11
    :license: BSD, see LICENSE for details.
12
"""
13
14
import sys
15
import os
16
import socket
17
import zipfile
18
import httplib
19
import base64
20
import urlparse
21
import tempfile
22
import cStringIO as StringIO
23
24
from distutils import log
25
from distutils.core import PyPIRCCommand
26
from distutils.errors import DistutilsOptionError
27
28
class UploadDoc(PyPIRCCommand):
29
    """Distutils command to upload Sphinx documentation."""
30
31
    description = 'Upload Sphinx documentation to PyPI'
32
    user_options = PyPIRCCommand.user_options + [
33
        ('upload-dir=', None, 'directory to upload'),
34
        ]
35
    boolean_options = PyPIRCCommand.boolean_options + ['sign']
36
37
    def initialize_options(self):
38
        PyPIRCCommand.initialize_options(self)
39
        self.upload_dir = None
40
        self.username = ''
41
        self.password = ''
42
        self.show_response = 0
43
        self.sign = False
44
        self.identity = None
45
46
    def finalize_options(self):
47
        PyPIRCCommand.finalize_options(self)
48
        if self.upload_dir is None:
49
            build = self.get_finalized_command('build')
50
            self.upload_dir = os.path.join(build.build_base, 'sphinx')
51
            self.mkpath(self.upload_dir)
52
        self.ensure_dirname('upload_dir')
53
        self.announce('Using upload directory %s' % self.upload_dir)
54
        config = self._read_pypirc()
55
        if config != {}:
56
            self.username = config['username']
57
            self.password = config['password']
58
            self.repository = config['repository']
59
            self.realm = config['realm']
60
61
    def create_zipfile(self):
62
        name = self.distribution.metadata.get_name()
63
        tmp_dir = tempfile.mkdtemp()
64
        tmp_file = os.path.join(tmp_dir, "%s.zip" % name)
65
        zip_file = zipfile.ZipFile(tmp_file, "w")
66
        for root, dirs, files in os.walk(self.upload_dir):
67
            if not files:
68
                raise DistutilsOptionError, \
69
                      "no files found in upload directory '%s'" % self.upload_dir
70
            for name in files:
71
                full = os.path.join(root, name)
72
                relative = root[len(self.upload_dir):].lstrip(os.path.sep)
73
                dest = os.path.join(relative, name)
74
                zip_file.write(full, dest)
75
        zip_file.close()
76
        return tmp_file
77
78
    def upload_file(self, filename):
79
        content = open(filename,'rb').read()
80
        meta = self.distribution.metadata
81
        data = {
82
            ':action': 'doc_upload',
83
            'name': meta.get_name(),
84
            'content': (os.path.basename(filename),content),
85
        }
86
        # set up the authentication
87
        auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()
88
89
        # Build up the MIME payload for the POST data
90
        boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
91
        sep_boundary = '\n--' + boundary
92
        end_boundary = sep_boundary + '--'
93
        body = StringIO.StringIO()
94
        for key, value in data.items():
95
            # handle multiple entries for the same name
96
            if type(value) != type([]):
97
                value = [value]
98
            for value in value:
99
                if type(value) is tuple:
100
                    fn = ';filename="%s"' % value[0]
101
                    value = value[1]
102
                else:
103
                    fn = ""
104
                value = str(value)
105
                body.write(sep_boundary)
106
                body.write('\nContent-Disposition: form-data; name="%s"'%key)
107
                body.write(fn)
108
                body.write("\n\n")
109
                body.write(value)
110
                if value and value[-1] == '\r':
111
                    body.write('\n')  # write an extra newline (lurve Macs)
112
        body.write(end_boundary)
113
        body.write("\n")
114
        body = body.getvalue()
115
116
        self.announce("Submitting documentation to %s" % (self.repository), log.INFO)
117
118
        # build the Request
119
        # We can't use urllib2 since we need to send the Basic
120
        # auth right with the first request
121
        schema, netloc, url, params, query, fragments = \
122
            urlparse.urlparse(self.repository)
123
        assert not params and not query and not fragments
124
        if schema == 'http':
125
            http = httplib.HTTPConnection(netloc)
126
        elif schema == 'https':
127
            http = httplib.HTTPSConnection(netloc)
128
        else:
129
            raise AssertionError, "unsupported schema "+schema
130
131
        data = ''
132
        loglevel = log.INFO
133
        try:
134
            http.connect()
135
            http.putrequest("POST", url)
136
            http.putheader('Content-type',
137
                           'multipart/form-data; boundary=%s'%boundary)
138
            http.putheader('Content-length', str(len(body)))
139
            http.putheader('Authorization', auth)
140
            http.endheaders()
141
            http.send(body)
142
        except socket.error, e:
143
            self.announce(str(e), log.ERROR)
144
            return
145
146
        response = http.getresponse()
147
        if response.status == 200:
148
            self.announce('Server response (%s): %s' % (response.status, response.reason),
149
                          log.INFO)
150
        elif response.status == 301:
151
            location = response.getheader('Location')
152
            if location is None:
153
                location = 'http://packages.python.org/%s/' % meta.get_name()
154
            self.announce('Upload successful. Visit %s' % location,
155
                          log.INFO)
156
        else:
157
            self.announce('Upload failed (%s): %s' % (response.status, response.reason),
158
                          log.ERROR)
159
        if self.show_response:
160
            print '-'*75, response.read(), '-'*75
161
162
    def run(self):
163
        zip_file = self.create_zipfile()
164
        self.upload_file(zip_file)
165
        os.remove(zip_file)