Friday 28 September 2012

Quick Sphinx documentation for Python

Intro

Hi everyone, it's been far too long since my last post. I've been busy doing lots of things, including finding a new place to live and packing. I've also been listening to a lot of music while programming and tinkering. (If you're interested: rock music by Rush, industrial electronica by Skinny Puppy, and folk by a German band named Faun. All three of these bands are fantastic for coding!)

The Riddle of the Sphinx

Sphinx is a great documentation creation module for Python, which uses reStructuredText files to generate docs in whatever format you like (usually HTML). reStructuredText (.rst) files look weird, but they're quite easy to write once you learn how. Sphinx uses the DocUtils module, which has a handy program "rest2html.py" that can convert a .rst file straight to an HTML file. Sphinx also has an autodoc/automodule feature, which can read the docstrings in your source code as reStructuredText and turn them into attractive HTML docs too. And my favourite feature, the "sphinx-apidoc" script, can walk through all your project's subdirectories and source files , and suck any docstrings from every single Python script it finds, automatically.

So here are a few tips about getting going quickly with Sphinx documentation for Python. I need to install Sphinx first:

$ pip install sphinx

I have a little project named "sphinxy" which contains a structure like this:

$ tree sphinxy
sphinxy
├── docs
└── sphinxy
    ├── big.py
    └── package1
        ├── __init__.py
        ├── small1.py
        └── small2.py


The file "big.py" looks like this (and the other .py files are similar):

#!/usr/bin/env python
#-*- coding: utf-8 -*-


"""
    big
    ~~~
    This module contains functions that are big.
   
    :copyright: (c) 2012 by Scott Davies for Web Drive Ltd.
"""


import package1.small1 as small1
from package1.small2 import Smallish, small2_cool, small2_rebel


def big_func1(abra, cadabra, sesame=None):
    """This is about big_func1.
   
    :param abra: abra number.
    :param cadabra: cadabra number.
    :param sesame: sesame information.
    :return ret_val: result information.
    :rtype: int.
    """
    ret_val = abra + cadabra
    print(sesame["street"])
    return ret_val



if __name__ == "__main__":
    ret_val = big_func1(4, 5, sesame={"street": "adding up"})

...

Notice some strange "~" and ":" characters in the docstrings? That's reStructuredText. Now what I want to do is just get Sphinx to read all the .py files in my project, and spin together some HTML documentation for me. (Since I can't be bothered typing custom .rst files and setting "automodule" everywhere.) So I run the "sphinx-apidoc" script, and want to put the new documentation generated in the "docs" directory.

$ cd ~/code/py/sphinxy
 $ sphinx-apidoc -F -o docs sphinxy
Creating file docs/big.rst.
Creating file docs/package1.rst.
Creating file docs/conf.py.
Creating file docs/index.rst.
Creating file docs/Makefile.
Creating file docs/make.bat.


If I go into my "docs" directory now, I can see a bunch of .rst files (and other things to help them) in there.

$ cd docs; ls
big.rst  conf.py    make.bat  package1.rst  _templates
_build   index.rst  Makefile  _static


The index page, i.e. the root/start page of my new documentation, is called "index.rst". If I have a look at it, the important parts are like this (a table of contents tree):

...
Welcome to sphinxy's documentation!
===================================

Contents:

.. toctree::
   :maxdepth: 4

   big
   package1

...

That's what I wanted! Yahoo. What I need to do now is convert those .rst files into my desired format, HTML. For that, I'm going to have to edit a setting in the "conf.py" file, so that Sphinx can find the directory I want it to start running from. I'll uncomment the line that adds to the sys.path for Sphinx autodoc to work:

$ vim conf.py

 I change this line, and save:

sys.path.insert(0, os.path.abspath('../../sphinxy'))

Then I create my HTML docs!

$ make html
sphinx-build -b html -d _build/doctrees   . _build/html
Making output directory...
Running Sphinx v1.1.3
loading pickled environment... not yet created
building [html]: targets for 3 source files that are out of date
updating environment: 3 added, 0 changed, 0 removed
reading sources... [100%] package1                                                     
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
writing output... [100%] package1                                                      
writing additional files... (3 module code pages) _modules/index
 genindex py-modindex search
copying static files... done
dumping search index... done
dumping object inventory... done
build succeeded.


Build finished. The HTML pages are in _build/html.



I will find my lovely new pages in the _build/html directory:

$ cd _build/html; ls
big.html       index.html  objects.inv    py-modindex.html  searchindex.js  _static
genindex.html  _modules    package1.html  search.html       _sources


Here is the index.html file viewed in a browser:


Here is the file big.html in a browser:




Make sure you read the online structuredText tutorials available, to help you in future. Here's a quick example of editing a .rst file manually, which you'll understand if you've read a few tutorials. This is part of a file I wrote to document a web services API, for calling a URL "init":

:init:

Initialises a workflow by attempting to save a non-template workflow to the database.
It will retrieve workflow template information for the workflow name and populate the new workflow with the information. It will also populate these workflow fields with initial default values: current_step_task, template_id, status, state.

**Parameters:**   
    * **name** - the name of a workflow as a string.
   
    i.e. send a JSON request in this format:

    ::

        {
            "name": unicode(50)
        }
   
**Returns:**
    Inside the JSON response dict:
        * **errors** - a list of any errors (strings) that may have occurred.
        * **confirmations** - a list of any confirmations that may have occurred.
        * **workflow_id** - the ObjectId string of the new workflow.
    e.g.
   
    ::
   
        {
            "errors": [],
            "confirmations: ["Successfully initialised workflow. "]
            "workflow_id": "4ea5e2afcd8bbf1491000005"
        }
       
**Usage example:**

::

    base_url = "http://asapweb3.local.org.nz:9003/approvals/"
    request_dic = {"name": "Purchase Order Workflow}
    req = urllib2.Request(url=base_url + "init",
                          data=json.dumps(request_dic),
                          headers={"Content-Type": "application/json"}
                        )
    opener = urllib2.build_opener()
    resp = opener.open(req)
    json_resp_strg = resp.read()
    # Get the info from the json string
    json_resp = json.loads(json_resp_strg)  
     



And here is the HTML page result via Sphinx (yay!):


Till next time, fellow Sphinxians. :)

2 comments:

  1. This has been incredibly helpful. I will be visiting your post on MongoDB in the coming days as well. Thank you for making this!

    ReplyDelete
  2. Nice blog, here I had an opportunity to learn something new in my field. I have an expectation about your future post so please keep updates...Thanks..



    Python Online Training

    ReplyDelete