← Back to team overview

python-jenkins-developers team mailing list archive

[Merge] lp:~hashar/python-jenkins/trunk into lp:python-jenkins

 

Antoine "hashar" Musso has proposed merging lp:~hashar/python-jenkins/trunk into lp:python-jenkins.

Commit message:
fix pep8 issues and refactor sphinx documentation.

Requested reviews:
  Python Jenkins Developers (python-jenkins-developers)

For more details, see:
https://code.launchpad.net/~hashar/python-jenkins/trunk/+merge/158768

My place had a rainy saturday so I decided to do some maintenance work on python-jenkins. This branch has:
- r17: fix pep8 errors on the original code

pep8 is a python standard that is always nice to stick to. Lot of other developers are expecting that style.  I haven't tested the changes though :(

- r18: overhaul the sphinx documentation

The module had documentation in both an index.rst and inlined in the jenkins/__init__.py file. The revision drop index.rst manually maintained doc in favor of the inlined one.  I have synced a couple of outdated doc blocks.  See commit message for details :-]

-- 
https://code.launchpad.net/~hashar/python-jenkins/trunk/+merge/158768
Your team Python Jenkins Developers is requested to review the proposed merge of lp:~hashar/python-jenkins/trunk into lp:python-jenkins.
=== added file '.pep8'
--- .pep8	1970-01-01 00:00:00 +0000
+++ .pep8	2013-04-13 22:12:24 +0000
@@ -0,0 +1,2 @@
+[pep8]
+ignore = E221,E501

=== modified file 'doc/Makefile'
--- doc/Makefile	2011-09-04 00:00:30 +0000
+++ doc/Makefile	2013-04-13 22:12:24 +0000
@@ -5,14 +5,16 @@
 SPHINXOPTS    =
 SPHINXBUILD   = sphinx-build
 PAPER         =
-BUILDDIR      = _build
+BUILDDIR      = build
 
 # Internal variables.
 PAPEROPT_a4     = -D latex_paper_size=a4
 PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
 
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
 
 help:
 	@echo "Please use \`make <target>' where <target> is one of"
@@ -29,6 +31,9 @@
 	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
 	@echo "  text       to make text files"
 	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
 	@echo "  changes    to make an overview of all changed/added/deprecated items"
 	@echo "  linkcheck  to check all external links for integrity"
 	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
@@ -100,7 +105,7 @@
 latexpdf:
 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
 	@echo "Running LaTeX files through pdflatex..."
-	make -C $(BUILDDIR)/latex all-pdf
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
 	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
 
 text:
@@ -113,6 +118,24 @@
 	@echo
 	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
 
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+		"(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
 changes:
 	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
 	@echo

=== added directory 'doc/build'
=== added directory 'doc/source'
=== added file 'doc/source/api.rst'
--- doc/source/api.rst	1970-01-01 00:00:00 +0000
+++ doc/source/api.rst	2013-04-13 22:12:24 +0000
@@ -0,0 +1,8 @@
+:title: API reference
+
+API reference
+=============
+
+.. automodule:: jenkins
+    :members:
+    :undoc-members:

=== renamed file 'doc/conf.py' => 'doc/source/conf.py'
--- doc/conf.py	2011-09-04 00:00:30 +0000
+++ doc/source/conf.py	2013-04-13 22:12:24 +0000
@@ -3,7 +3,8 @@
 # Python Jenkins documentation build configuration file, created by
 # sphinx-quickstart on Sat Sep  3 16:24:58 2011.
 #
-# This file is execfile()d with the current directory set to its containing dir.
+# This file is execfile()d with the current directory set to its containing
+# dir.
 #
 # Note that not all possible configuration values are present in this
 # autogenerated file.
@@ -11,25 +12,31 @@
 # All configuration values have a default; values that are commented out
 # serve to show the default.
 
-import sys, os
-
-import sys, os
-sys.path.insert(0, os.path.abspath('..'))
+import os
+import sys
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 #sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath('../..'))
+sys.path.insert(0, os.path.abspath('../../jenkins'))
 
-# -- General configuration -----------------------------------------------------
+# -- General configuration ----------------------------------------------------
 
 # If your documentation needs a minimal Sphinx version, state it here.
 #needs_sphinx = '1.0'
 
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
 extensions = ['sphinx.ext.autodoc']
 
+# Also document __init__
+autoclass_content = 'both'
+
+# Change that to 'alphabetical' if you want
+autodoc_member_order = 'bysource'
+
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
 
@@ -69,7 +76,7 @@
 # directories to ignore when looking for source files.
 exclude_patterns = ['_build']
 
-# The reST default role (used for this markup: `text`) to use for all documents.
+# The reST default role (used for this markup: `text`) to use for all documents
 #default_role = None
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
@@ -90,7 +97,7 @@
 #modindex_common_prefix = []
 
 
-# -- Options for HTML output ---------------------------------------------------
+# -- Options for HTML output --------------------------------------------------
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
@@ -123,7 +130,7 @@
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+#html_static_path = ['_static']
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
@@ -170,7 +177,7 @@
 htmlhelp_basename = 'PythonJenkinsdoc'
 
 
-# -- Options for LaTeX output --------------------------------------------------
+# -- Options for LaTeX output -------------------------------------------------
 
 # The paper size ('letter' or 'a4').
 #latex_paper_size = 'letter'
@@ -179,10 +186,10 @@
 #latex_font_size = '10pt'
 
 # Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
+# (source start file, target name, title, author, documentclass [howto/manual])
 latex_documents = [
-  ('index', 'PythonJenkins.tex', u'Python Jenkins Documentation',
-   u'Ken Conley, James Page, Tully Foote, Matthew Gertner', 'manual'),
+    ('index', 'PythonJenkins.tex', u'Python Jenkins Documentation',
+     u'Ken Conley, James Page, Tully Foote, Matthew Gertner', 'manual'),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
@@ -209,7 +216,7 @@
 #latex_domain_indices = True
 
 
-# -- Options for manual page output --------------------------------------------
+# -- Options for manual page output -------------------------------------------
 
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).

=== added file 'doc/source/example.rst'
--- doc/source/example.rst	1970-01-01 00:00:00 +0000
+++ doc/source/example.rst	2013-04-13 22:12:24 +0000
@@ -0,0 +1,22 @@
+Example usage
+=============
+
+Example usage::
+
+    j = jenkins.Jenkins('http://your_url_here', 'username', 'password')
+    j.get_jobs()
+    j.create_job('empty', jenkins.EMPTY_CONFIG_XML)
+    j.disable_job('empty')
+    j.copy_job('empty', 'empty_copy')
+    j.enable_job('empty_copy')
+    j.reconfig_job('empty_copy', jenkins.RECONFIG_XML)
+
+    j.delete_job('empty')
+    j.delete_job('empty_copy')
+
+    # build a parameterized job
+    j.build_job('api-test', {'param1': 'test value 1', 'param2': 'test value 2'})
+    build_info = j.get_build_info('build_name', next_build_number)
+    print(build_info)
+
+Look at the :doc:`api` for more details.

=== renamed file 'doc/index.rst' => 'doc/source/index.rst'
--- doc/index.rst	2012-03-01 18:03:09 +0000
+++ doc/source/index.rst	2013-04-13 22:12:24 +0000
@@ -5,213 +5,19 @@
 <http://jenkins-ci.org/>`_ continuous integration server. It is useful
 for creating and managing jobs as well as build nodes.
 
-Example usage::
-
-    j = jenkins.Jenkins('http://your_url_here', 'username', 'password')
-    j.get_jobs()
-    j.create_job('empty', jenkins.EMPTY_CONFIG_XML)
-    j.disable_job('empty')
-    j.copy_job('empty', 'empty_copy')
-    j.enable_job('empty_copy')
-    j.reconfig_job('empty_copy', jenkins.RECONFIG_XML)
-
-    j.delete_job('empty')
-    j.delete_job('empty_copy')
-
-    # build a parameterized job
-    j.build_job('api-test', {'param1': 'test value 1', 'param2': 'test value 2'})
-    build_info = j.get_build_info('build_name', next_build_number)
-    print(build_info)
+Table of content:
+
+.. toctree::
+    :maxdepth: 2
+    :glob:
+
+    *
 
 Python Jenkins development is hosted on Launchpad: https://launchpad.net/python-jenkins
 
-Installing
-==========
-
-``pip``::
-
-    pip install python-jenkins
-    
-``easy_install``::
-
-    easy_install python-jenkins
-
-Ubuntu Oneiric or later::
-
-    apt-get install python-jenkins
-
-
-API documentation
-=================
-
-.. class:: JenkinsException
-
-    General exception type for jenkins-API-related failures.
-
-.. class:: Jenkins(url, [username=None, [password=None]])
-    
-    Create handle to Jenkins instance.
-
-    All methods will raise :class:`JenkinsException` on failure.
-
-    :param username: Server username, ``str``
-    :param password: Server password, ``str``
-    :param url: URL of Jenkins server, ``str``
-
-
-    .. method:: get_jobs(self)
-
-        Get list of jobs running.  Each job is a dictionary with
-        'name', 'url', and 'color' keys.
-
-        :returns: list of jobs, ``[ { str: str} ]``
-
-    .. method:: job_exists(name)
-
-        :param name: Name of Jenkins job, ``str``
-        :returns: ``True`` if Jenkins job exists
-
-    .. method:: build_job(name, [parameters=None, [token=None]])
-
-        Trigger build job.
-        
-        :param parameters: parameters for job, or ``None``, ``dict``
-  
-    .. method:: build_job_url(name, [parameters=None, [token=None]])
-
-        Get URL to trigger build job.  Authenticated setups may require configuring a token on the server side.
-        
-        :param parameters: parameters for job, or None., ``dict``
-        :param token: (optional) token for building job, ``str``
-        :returns: URL for building job
-
-    .. method:: create_job(name, config_xml)
-
-        Create a new Jenkins job
-
-        :param name: Name of Jenkins job, ``str``
-        :param config_xml: config file text, ``str``
-    
-    .. method:: copy_job(from_name, to_name)
-
-        Copy a Jenkins job
-
-        :param from_name: Name of Jenkins job to copy from, ``str``
-        :param to_name: Name of Jenkins job to copy to, ``str``
-
-    .. method:: delete_job(name)
-
-        Delete Jenkins job permanently.
-        
-        :param name: Name of Jenkins job, ``str``
-    
-    .. method:: enable_job(name)
-
-        Enable Jenkins job.
-
-        :param name: Name of Jenkins job, ``str``
-
-    .. method:: disable_job(name)
-
-        Disable Jenkins job. To re-enable, call :meth:`Jenkins.enable_job`.
-
-        :param name: Name of Jenkins job, ``str``
-
-    .. method:: get_build_info(name, number)
-
-        Get build information dictionary.
-
-        :param name: Job name, ``str``
-        :param name: Build number, ``int``
-        :returns: dictionary of build information
-
-    .. method:: get_job_config(name) -> str
-
-        Get configuration XML of existing Jenkins job.  
-
-        :param name: Name of Jenkins job, ``str``
-        :returns: Job configuration XML
-
-    .. method:: get_job_info(name)
-
-        Get job information dictionary.
-
-        :param name: Job name, ``str``
-        :returns: dictionary of job information
-
-    .. method:: debug_job_info(job_name)
-
-        Print out job info in more readable format
-
-    .. method:: reconfig_job(name, config_xml)
-
-        Change configuration of existing Jenkins job.  To create a new job, see :meth:`Jenkins.create_job`.
-
-        :param name: Name of Jenkins job, ``str``
-        :param config_xml: New XML configuration, ``str``
-
-    .. method:: get_node_info(name) -> dict
-
-        Get node information dictionary
-
-        :param name: Node name, ``str``
-        :returns: Dictionary of node info, ``dict``
- 
-    .. method:: node_exists(name) -> bool
-
-        :param name: Name of Jenkins node, ``str``
-        :returns: ``True`` if Jenkins node exists
-            
-    .. method:: create_node(name, [numExecutors=2, [nodeDescription=None, [remoteFS='/var/lib/jenkins', [labels=None, [exclusive=False]]]]])
-
-        :param name: name of node to create, ``str``
-        :param numExecutors: number of executors for node, ``int``
-        :param nodeDescription: Description of node, ``str``
-        :param remoteFS: Remote filesystem location to use, ``str``
-        :param labels: Labels to associate with node, ``str``
-        :param exclusive: Use this node for tied jobs only, ``bool``
-
-    .. method:: delete_node(name)
-
-        Delete Jenkins node permanently.
-        
-        :param name: Name of Jenkins node, ``str``
-    
-    .. method:: get_queue_info(self)
-
-        :returns: list of job dictionaries, ``[dict]``
-
-        Example::
-
-            >>> queue_info = j.get_queue_info()
-            >>> print(queue_info[0])
-            {u'task': {u'url': u'http://your_url/job/my_job/', u'color': u'aborted_anime', u'name': u'my_job'}, u'stuck': False, u'actions': [{u'causes': [{u'shortDescription': u'Started by timer'}]}], u'buildable': False, u'params': u'', u'buildableStartMilliseconds': 1315087293316, u'why': u'Build #2,532 is already in progress (ETA:10 min)', u'blocked': True}
-
-    .. method:: get_info(self)
-
-        Get information on this Master.  This information
-        includes job list and view information.
-
-        :returns: dictionary of information about Master, ``dict``
-
-        Example::
-
-            >>> info = j.get_info()
-            >>> jobs = info['jobs']
-            >>> print(jobs[0])
-            {u'url': u'http://your_url_here/job/my_job/', u'color': u'blue', u'name': u'my_job'}
-
-
-    .. method:: jenkins_open(req)
-
-        Utility routine for opening an HTTP request to a Jenkins server.   This should only be used
-        to extends the :class:`Jenkins` API.
-    
-
 Indices and tables
 ==================
 
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
-

=== added file 'doc/source/install.rst'
--- doc/source/install.rst	1970-01-01 00:00:00 +0000
+++ doc/source/install.rst	2013-04-13 22:12:24 +0000
@@ -0,0 +1,24 @@
+:title: Installing
+
+Installing
+==========
+
+The module is known to pip and Debian based distribution as
+``python-jenkins``.
+
+``pip``::
+
+    pip install python-jenkins
+
+``easy_install``::
+
+    easy_install python-jenkins
+
+The module has been packaged since Ubuntu Oneiric (11.10)::
+
+    apt-get install python-jenkins
+
+For developpement purpose you can get a fake module installed on your system
+that will point to your working copy.  Simply use::
+
+    python setup.py develop

=== modified file 'jenkins/__init__.py'
--- jenkins/__init__.py	2012-06-25 11:48:32 +0000
+++ jenkins/__init__.py	2013-04-13 22:12:24 +0000
@@ -38,57 +38,45 @@
 # Matthew Gertner <matthew.gertner@xxxxxxxxx>
 
 '''
-Python API for Jenkins
-
-Examples::
-
-    j = jenkins.Jenkins('http://your_url_here', 'username', 'password')
-    j.get_jobs()
-    j.create_job('empty', jenkins.EMPTY_CONFIG_XML)
-    j.disable_job('empty')
-    j.copy_job('empty', 'empty_copy')
-    j.enable_job('empty_copy')
-    j.reconfig_job('empty_copy', jenkins.RECONFIG_XML)
-
-    j.delete_job('empty')
-    j.delete_job('empty_copy')
-
-    # build a parameterized job
-    j.build_job('api-test', {'param1': 'test value 1', 'param2': 'test value 2'})
+.. module:: jenkins
+    :platform: Unix, Windows
+    :synopsis: Python API to interact with Jenkins
+
+See examples at :doc:`example`
 '''
 
-import sys
+#import sys
 import urllib2
 import urllib
 import base64
-import traceback
+#import traceback
 import json
 import httplib
 
-LAUNCHER_SSH             = 'hudson.plugins.sshslaves.SSHLauncher'
-LAUNCHER_COMMAND         = 'hudson.slaves.CommandLauncher'
+LAUNCHER_SSH = 'hudson.plugins.sshslaves.SSHLauncher'
+LAUNCHER_COMMAND = 'hudson.slaves.CommandLauncher'
 LAUNCHER_WINDOWS_SERVICE = 'hudson.os.windows.ManagedWindowsServiceLauncher'
 
-INFO         = 'api/json'
-JOB_INFO     = 'job/%(name)s/api/json?depth=0'
-Q_INFO       = 'queue/api/json?depth=0'
+INFO = 'api/json'
+JOB_INFO = 'job/%(name)s/api/json?depth=0'
+Q_INFO = 'queue/api/json?depth=0'
 CANCEL_QUEUE = 'queue/item/%(number)s/cancelQueue'
-CREATE_JOB   = 'createItem?name=%(name)s' #also post config.xml
-CONFIG_JOB   = 'job/%(name)s/config.xml'
-DELETE_JOB   = 'job/%(name)s/doDelete'
-ENABLE_JOB   = 'job/%(name)s/enable'
-DISABLE_JOB  = 'job/%(name)s/disable'
-COPY_JOB     = 'createItem?name=%(to_name)s&mode=copy&from=%(from_name)s'
-BUILD_JOB    = 'job/%(name)s/build'
-STOP_BUILD   = 'job/%(name)s/%(number)s/stop'
+CREATE_JOB = 'createItem?name=%(name)s'  # also post config.xml
+CONFIG_JOB = 'job/%(name)s/config.xml'
+DELETE_JOB = 'job/%(name)s/doDelete'
+ENABLE_JOB = 'job/%(name)s/enable'
+DISABLE_JOB = 'job/%(name)s/disable'
+COPY_JOB = 'createItem?name=%(to_name)s&mode=copy&from=%(from_name)s'
+BUILD_JOB = 'job/%(name)s/build'
+STOP_BUILD = 'job/%(name)s/%(number)s/stop'
 BUILD_WITH_PARAMS_JOB = 'job/%(name)s/buildWithParameters'
-BUILD_INFO   = 'job/%(name)s/%(number)d/api/json?depth=0'
+BUILD_INFO = 'job/%(name)s/%(number)d/api/json?depth=0'
 
 
 CREATE_NODE = 'computer/doCreateItem?%s'
 DELETE_NODE = 'computer/%(name)s/doDelete'
-NODE_INFO   = 'computer/%(name)s/api/json?depth=0'
-NODE_TYPE   = 'hudson.slaves.DumbSlave$DescriptorImpl'
+NODE_INFO = 'computer/%(name)s/api/json?depth=0'
+NODE_TYPE = 'hudson.slaves.DumbSlave$DescriptorImpl'
 TOGGLE_OFFLINE = 'computer/%(name)s/toggleOffline?offlineMessage=%(msg)s'
 
 #for testing only
@@ -118,33 +106,41 @@
   <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
   <triggers class='vector'/>
   <concurrentBuild>false</concurrentBuild>
-<builders> 
-    <jenkins.tasks.Shell> 
-      <command>export FOO=bar</command> 
-    </jenkins.tasks.Shell> 
-  </builders> 
+<builders>
+    <jenkins.tasks.Shell>
+      <command>export FOO=bar</command>
+    </jenkins.tasks.Shell>
+  </builders>
   <publishers/>
   <buildWrappers/>
 </project>'''
 
+
 class JenkinsException(Exception):
     '''
     General exception type for jenkins-API-related failures.
     '''
     pass
 
+
 def auth_headers(username, password):
     '''
-    Simple implementation of HTTP Basic Authentication. Returns the 'Authentication' header value.
+    Simple implementation of HTTP Basic Authentication. Returns the
+    'Authentication' header value.
     '''
     return 'Basic ' + base64.encodestring('%s:%s' % (username, password))[:-1]
 
+
 class Jenkins(object):
 
     def __init__(self, url, username=None, password=None):
         '''
         Create handle to Jenkins instance.
 
+        All methods will raise :class:`JenkinsException` on failure.
+
+        :param username: Server username, ``str``
+        :param password: Server password, ``str``
         :param url: URL of Jenkins server, ``str``
         '''
         if url[-1] == '/':
@@ -164,15 +160,17 @@
         :returns: dictionary of job information
         '''
         try:
-            response = self.jenkins_open(urllib2.Request(self.server + JOB_INFO%locals()))
+            response = self.jenkins_open(urllib2.Request(
+                self.server + JOB_INFO % locals()))
             if response:
                 return json.loads(response)
             else:
-                raise JenkinsException('job[%s] does not exist'%name)
+                raise JenkinsException('job[%s] does not exist' % name)
         except urllib2.HTTPError:
-            raise JenkinsException('job[%s] does not exist'%name)
+            raise JenkinsException('job[%s] does not exist' % name)
         except ValueError:
-            raise JenkinsException("Could not parse JSON info for job[%s]"%name)
+            raise JenkinsException(
+                "Could not parse JSON info for job[%s]" % name)
 
     def debug_job_info(self, job_name):
         '''
@@ -183,17 +181,21 @@
 
     def jenkins_open(self, req):
         '''
-        Utility routine for opening an HTTP request to a Jenkins server.   This should only be used
-        to extends the :class:`Jenkins` API.
+        Utility routine for opening an HTTP request to a Jenkins server.   This
+        should only be used to extends the :class:`Jenkins` API.
         '''
         try:
             if self.auth:
                 req.add_header('Authorization', self.auth)
             return urllib2.urlopen(req).read()
         except urllib2.HTTPError, e:
-            # Jenkins's funky authentication means its nigh impossible to distinguish errors.
+            # Jenkins's funky authentication means its nigh impossible to
+            # distinguish errors.
             if e.code in [401, 403, 500]:
-                raise JenkinsException('Error in request. Possibly authentication failed [%s]'%(e.code))
+                raise JenkinsException(
+                    'Error in request.' +
+                    'Possibly authentication failed [%s]' % (e.code)
+                )
             # right now I'm getting 302 infinites on a successful delete
 
     def get_build_info(self, name, number):
@@ -214,15 +216,21 @@
             {u'building': False, u'changeSet': {u'items': [{u'date': u'2011-12-19T18:01:52.540557Z', u'msg': u'test', u'revision': 66, u'user': u'unknown', u'paths': [{u'editType': u'edit', u'file': u'/branches/demo/index.html'}]}], u'kind': u'svn', u'revisions': [{u'module': u'http://eaas-svn01.i3.level3.com/eaas', u'revision': 66}]}, u'builtOn': u'', u'description': None, u'artifacts': [{u'relativePath': u'dist/eaas-87-2011-12-19_18-01-57.war', u'displayPath': u'eaas-87-2011-12-19_18-01-57.war', u'fileName': u'eaas-87-2011-12-19_18-01-57.war'}, {u'relativePath': u'dist/eaas-87-2011-12-19_18-01-57.war.zip', u'displayPath': u'eaas-87-2011-12-19_18-01-57.war.zip', u'fileName': u'eaas-87-2011-12-19_18-01-57.war.zip'}], u'timestamp': 1324317717000, u'number': 87, u'actions': [{u'parameters': [{u'name': u'SERVICE_NAME', u'value': u'eaas'}, {u'name': u'PROJECT_NAME', u'value': u'demo'}]}, {u'causes': [{u'userName': u'anonymous', u'shortDescription': u'Started by user anonymous'}]}, {}, {}, {}], u'id': u'2011-12-19_18-01-57', u'keepLog': False, u'url': u'http://eaas-jenkins01.i3.level3.com:9080/job/build_war/87/', u'culprits': [{u'absoluteUrl': u'http://eaas-jenkins01.i3.level3.com:9080/user/unknown', u'fullName': u'unknown'}], u'result': u'SUCCESS', u'duration': 8826, u'fullDisplayName': u'build_war #87'}
         '''
         try:
-            response = self.jenkins_open(urllib2.Request(self.server + BUILD_INFO%locals()))
+            response = self.jenkins_open(urllib2.Request(
+                self.server + BUILD_INFO % locals()))
             if response:
                 return json.loads(response)
             else:
-                raise JenkinsException('job[%s] number[%d] does not exist'%(name, number))
+                raise JenkinsException('job[%s] number[%d] does not exist'
+                                       % (name, number))
         except urllib2.HTTPError:
-            raise JenkinsException('job[%s] number[%d] does not exist'%(name, number))
+            raise JenkinsException('job[%s] number[%d] does not exist'
+                                   % (name, number))
         except ValueError:
-            raise JenkinsException('Could not parse JSON info for job[%s] number[%d]'%(name, number))
+            raise JenkinsException(
+                'Could not parse JSON info for job[%s] number[%d]'
+                % (name, number)
+            )
 
     def get_queue_info(self):
         '''
@@ -233,7 +241,9 @@
             >>> print(queue_info[0])
             {u'task': {u'url': u'http://your_url/job/my_job/', u'color': u'aborted_anime', u'name': u'my_job'}, u'stuck': False, u'actions': [{u'causes': [{u'shortDescription': u'Started by timer'}]}], u'buildable': False, u'params': u'', u'buildableStartMilliseconds': 1315087293316, u'why': u'Build #2,532 is already in progress (ETA:10 min)', u'blocked': True}
         '''
-        return json.loads(self.jenkins_open(urllib2.Request(self.server + Q_INFO)))['items']
+        return json.loads(self.jenkins_open(
+            urllib2.Request(self.server + Q_INFO)
+        ))['items']
 
     def cancel_queue(self, number):
         '''
@@ -259,17 +269,22 @@
             >>> info = j.get_info()
             >>> jobs = info['jobs']
             >>> print(jobs[0])
-            {u'url': u'http://your_url_here/job/my_job/', u'color': u'blue', u'name': u'my_job'}
+            {u'url': u'http://your_url_here/job/my_job/', u'color': u'blue',
+            u'name': u'my_job'}
 
         """
         try:
-            return json.loads(self.jenkins_open(urllib2.Request(self.server + INFO)))
+            return json.loads(self.jenkins_open(
+                urllib2.Request(self.server + INFO)))
         except urllib2.HTTPError:
-            raise JenkinsException("Error communicating with server[%s]"%self.server)
+            raise JenkinsException("Error communicating with server[%s]"
+                                   % self.server)
         except httplib.BadStatusLine:
-            raise JenkinsException("Error communicating with server[%s]"%self.server)
+            raise JenkinsException("Error communicating with server[%s]"
+                                   % self.server)
         except ValueError:
-            raise JenkinsException("Could not parse JSON info for server[%s]"%self.server)
+            raise JenkinsException("Could not parse JSON info for server[%s]"
+                                   % self.server)
 
     def get_jobs(self):
         """
@@ -288,20 +303,22 @@
         :param to_name: Name of Jenkins job to copy to, ``str``
         '''
         self.get_job_info(from_name)
-        self.jenkins_open(urllib2.Request(self.server + COPY_JOB%locals(), ''))
+        self.jenkins_open(urllib2.Request(
+            self.server + COPY_JOB % locals(), ''))
         if not self.job_exists(to_name):
-            raise JenkinsException('create[%s] failed'%(to_name))
+            raise JenkinsException('create[%s] failed' % (to_name))
 
     def delete_job(self, name):
         '''
         Delete Jenkins job permanently.
-        
+
         :param name: Name of Jenkins job, ``str``
         '''
         self.get_job_info(name)
-        self.jenkins_open(urllib2.Request(self.server + DELETE_JOB%locals(), ''))
+        self.jenkins_open(urllib2.Request(
+            self.server + DELETE_JOB % locals(), ''))
         if self.job_exists(name):
-            raise JenkinsException('delete[%s] failed'%(name))
+            raise JenkinsException('delete[%s] failed' % (name))
 
     def enable_job(self, name):
         '''
@@ -310,7 +327,8 @@
         :param name: Name of Jenkins job, ``str``
         '''
         self.get_job_info(name)
-        self.jenkins_open(urllib2.Request(self.server + ENABLE_JOB%locals(), ''))
+        self.jenkins_open(urllib2.Request(
+            self.server + ENABLE_JOB % locals(), ''))
 
     def disable_job(self, name):
         '''
@@ -319,7 +337,8 @@
         :param name: Name of Jenkins job, ``str``
         '''
         self.get_job_info(name)
-        self.jenkins_open(urllib2.Request(self.server + DISABLE_JOB%locals(), ''))
+        self.jenkins_open(urllib2.Request(
+            self.server + DISABLE_JOB % locals(), ''))
 
     def job_exists(self, name):
         '''
@@ -340,12 +359,13 @@
         :param config_xml: config file text, ``str``
         '''
         if self.job_exists(name):
-            raise JenkinsException('job[%s] already exists'%(name))
+            raise JenkinsException('job[%s] already exists' % (name))
 
         headers = {'Content-Type': 'text/xml'}
-        self.jenkins_open(urllib2.Request(self.server + CREATE_JOB%locals(), config_xml, headers))
+        self.jenkins_open(urllib2.Request(
+            self.server + CREATE_JOB % locals(), config_xml, headers))
         if not self.job_exists(name):
-            raise JenkinsException('create[%s] failed'%(name))
+            raise JenkinsException('create[%s] failed' % (name))
 
     def get_job_config(self, name):
         '''
@@ -360,20 +380,22 @@
 
     def reconfig_job(self, name, config_xml):
         '''
-        Change configuration of existing Jenkins job.  To create a new job, see :meth:`Jenkins.create_job`.
+        Change configuration of existing Jenkins job.  To create a new job, see
+        :meth:`Jenkins.create_job`.
 
         :param name: Name of Jenkins job, ``str``
         :param config_xml: New XML configuration, ``str``
         '''
         self.get_job_info(name)
         headers = {'Content-Type': 'text/xml'}
-        reconfig_url = self.server + CONFIG_JOB%locals()
+        reconfig_url = self.server + CONFIG_JOB % locals()
         self.jenkins_open(urllib2.Request(reconfig_url, config_xml, headers))
 
     def build_job_url(self, name, parameters=None, token=None):
         '''
-        Get URL to trigger build job.  Authenticated setups may require configuring a token on the server side.
-        
+        Get URL to trigger build job.  Authenticated setups may require
+        configuring a token on the server side.
+
         :param parameters: parameters for job, or None., ``dict``
         :param token: (optional) token for building job, ``str``
         :returns: URL for building job
@@ -381,21 +403,26 @@
         if parameters:
             if token:
                 parameters['token'] = token
-            return self.server + BUILD_WITH_PARAMS_JOB%locals() + '?' + urllib.urlencode(parameters)
+            return (self.server + BUILD_WITH_PARAMS_JOB % locals() +
+                    '?' + urllib.urlencode(parameters))
         elif token:
-            return self.server + BUILD_JOB%locals() + '?' + urllib.urlencode({'token': token})
+            return (self.server + BUILD_JOB % locals() +
+                    '?' + urllib.urlencode({'token': token}))
         else:
-            return self.server + BUILD_JOB%locals()
+            return self.server + BUILD_JOB % locals()
 
     def build_job(self, name, parameters=None, token=None):
         '''
         Trigger build job.
-        
+
+        :param name: name of job
         :param parameters: parameters for job, or ``None``, ``dict``
+        :param token: Jenkins API token
         '''
         if not self.job_exists(name):
-            raise JenkinsException('no such job[%s]'%(name))
-        return self.jenkins_open(urllib2.Request(self.build_job_url(name, parameters, token)))
+            raise JenkinsException('no such job[%s]' % (name))
+        return self.jenkins_open(urllib2.Request(
+            self.build_job_url(name, parameters, token)))
 
     def stop_build(self, name, number):
         '''
@@ -414,15 +441,17 @@
         :returns: Dictionary of node info, ``dict``
         '''
         try:
-            response = self.jenkins_open(urllib2.Request(self.server + NODE_INFO%locals()))
+            response = self.jenkins_open(urllib2.Request(
+                self.server + NODE_INFO % locals()))
             if response:
                 return json.loads(response)
             else:
-                raise JenkinsException('node[%s] does not exist'%name)
+                raise JenkinsException('node[%s] does not exist' % name)
         except urllib2.HTTPError:
-            raise JenkinsException('node[%s] does not exist'%name)
+            raise JenkinsException('node[%s] does not exist' % name)
         except ValueError:
-            raise JenkinsException("Could not parse JSON info for node[%s]"%name)
+            raise JenkinsException("Could not parse JSON info for node[%s]"
+                                   % name)
 
     def node_exists(self, name):
         '''
@@ -438,38 +467,40 @@
     def delete_node(self, name):
         '''
         Delete Jenkins node permanently.
-        
+
         :param name: Name of Jenkins node, ``str``
         '''
         self.get_node_info(name)
-        self.jenkins_open(urllib2.Request(self.server + DELETE_NODE%locals(), ''))
+        self.jenkins_open(urllib2.Request(
+            self.server + DELETE_NODE % locals(), ''))
         if self.node_exists(name):
-            raise JenkinsException('delete[%s] failed'%(name))
-
+            raise JenkinsException('delete[%s] failed' % (name))
 
     def disable_node(self, name, msg=''):
         '''
         Disable a node
-        
+
         :param name: Jenkins node name, ``str``
         :param msg: Offline message, ``str``
         '''
         info = self.get_node_info(name)
         if info['offline']:
             return
-        self.jenkins_open(urllib2.Request(self.server + TOGGLE_OFFLINE%locals()))
+        self.jenkins_open(urllib2.Request(
+            self.server + TOGGLE_OFFLINE % locals()))
 
     def enable_node(self, name):
         '''
         Enable a node
-        
+
         :param name: Jenkins node name, ``str``
         '''
         info = self.get_node_info(name)
         if not info['offline']:
             return
         msg = ''
-        self.jenkins_open(urllib2.Request(self.server + TOGGLE_OFFLINE%locals()))
+        self.jenkins_open(urllib2.Request(
+            self.server + TOGGLE_OFFLINE % locals()))
 
     def create_node(self, name, numExecutors=2, nodeDescription=None,
                     remoteFS='/var/lib/jenkins', labels=None, exclusive=False,
@@ -485,7 +516,7 @@
         :param launcher_params: Additional parameters for the launcher, ``dict``
         '''
         if self.node_exists(name):
-            raise JenkinsException('node[%s] already exists'%(name))
+            raise JenkinsException('node[%s] already exists' % (name))
 
         mode = 'NORMAL'
         if exclusive:
@@ -494,25 +525,29 @@
         launcher_params['stapler-class'] = launcher
 
         inner_params = {
-                'name'            : name,
-                'nodeDescription' : nodeDescription,
-                'numExecutors'    : numExecutors,
-                'remoteFS'        : remoteFS,
-                'labelString'     : labels,
-                'mode'            : mode,
-                'type'            : NODE_TYPE,
-                'retentionStrategy' : { 'stapler-class'  : 'hudson.slaves.RetentionStrategy$Always' },
-                'nodeProperties'    : { 'stapler-class-bag' : 'true' },
-                'launcher'          : launcher_params
+            'name': name,
+            'nodeDescription': nodeDescription,
+            'numExecutors': numExecutors,
+            'remoteFS': remoteFS,
+            'labelString': labels,
+            'mode': mode,
+            'type': NODE_TYPE,
+            'retentionStrategy': {
+                'stapler-class':
+                'hudson.slaves.RetentionStrategy$Always'
+            },
+            'nodeProperties': {'stapler-class-bag': 'true'},
+            'launcher': launcher_params
         }
 
         params = {
-            'name' : name,
-            'type' : NODE_TYPE,
-            'json' : json.dumps(inner_params)
+            'name': name,
+            'type': NODE_TYPE,
+            'json': json.dumps(inner_params)
         }
 
-        self.jenkins_open(urllib2.Request(self.server + CREATE_NODE%urllib.urlencode(params)))
+        self.jenkins_open(urllib2.Request(
+            self.server + CREATE_NODE % urllib.urlencode(params)))
 
         if not self.node_exists(name):
-            raise JenkinsException('create[%s] failed'%(name))
+            raise JenkinsException('create[%s] failed' % (name))

=== modified file 'setup.py'
--- setup.py	2012-05-17 15:35:18 +0000
+++ setup.py	2013-04-13 22:12:24 +0000
@@ -9,4 +9,4 @@
       author_email='kwc@xxxxxxxxxxxxxxxx',
       url='http://launchpad.net/python-jenkins',
       packages=['jenkins'],
-     )
+      )


Follow ups