jezdez / Sphinx-PyPI-upload
setuptools commands to help uploading of Sphinx documentation to PyPI
Clone this repository (size: 10.8 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/jezdez/sphinx-pypi-upload/
| commit 0: | d9fc6bdaea38 |
| branch: | default |
| tags: | 0.1 |
Initial commit with working version
- View jezdez's profile
-
jezdez's public repos »
- django-adminfiles-de
- djangologging
- django-endless-pagination-de
- django-endless-pagination-fixes
- creole
- pip-wininst
- transifex-buildout
- hgstuff
- akismet
- django-dbtemplates
- Sphinx-PyPI-upload
- django-robots
- django-authority
- pip-config
- jezdez.bitbucket.org
- ports
- virtualenv-packaging
- pip
- django-registration-de
- pycompletion
- django-piston
- django-piston-python-oauth2
- django-licenses
- jzdz
- django-vcstorage
- textmate-missingdrawer
- virtualenv
- pip-uninstall
- setuptools_hg
- djangolocales
- django-staticfiles
- Send message
8 months ago
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)
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. |
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) |
