mod2doctest

What’s New

  • 9/23/2010 – Version 0.2.0 out. Drastically changes how mod2doctest operates and fixes many bugs (mainly due to whitespace errors). Overall, a much better, more stable release.

What is mod2doctest?

mod2doctest takes a runnable python script as input and creates a nicely formatted triple quoted docstring. This docstring can then be used as:

  • A test fixture since it’s runnable within doctest
  • Source code documentation as it can be handed to sphinx with a .. automodule command (see examples below).

It’s similiar in concept to copying and pasting the script/module contents into the interactive interpreter. However, among other features, mod2doctest:

  • Provides several formatting tools. In particular, #> and #| are special mod2doctest comments that allow you to control the output format of the docstr (and what gets printed to stdout). In general, the output docstring from mod2doctest is much more nicely formatted than if you copy/paste directly.
  • Fixes problems with whitespace that don’t allow you to directly copy a module source into the interpreter (modules can have blanklines within a suite; the interpreter does not allow this).

It’s also similar to writing docstrings directly. However, among other things, by using mod2doctest you:

  • Take advantage of writing python code in your normal IDE / editor (as opposed to writing within the triple quoted string) so things like code completion, etc will still work.
  • Don’t need to worry about creating program output – mod2doctest adds this for you.

The goal of doctest itself is to greatly reduce the burden of writing test fixtures and documentation. mod2doctest attempts to build on these goals. By following a few conventions, you can create permanent test fixtures and nicely formatted documentation in as much time as you’d spend creating those quick throw away test scripts you need to develop your code against.

Quick Example

A module that looks like this:

if __name__ == '__main__':

    # All __name__ == '__main__' blocks are removed, serving as mod2doctest
    # comments

    import mod2doctest
    mod2doctest.convert('python', src=True, target='_doctest', run_doctest=False,
                        add_testmod=False, add_autogen=False)

#>Welcome to mod2doctest
#>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#|
#|Just enter in some python
#|
#|.. warning::
#|   make sure to examine your resulting docstr to make sure output is as
#|   expected!

#|The basics:
print 'Hello World!'

#>Extended Example
#>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#|A little more:
somelist = [100, 2, -20, 340, 0, 0, 10, 10, 88, -3, 100, 2, -99, -1]
sorted(set(somelist))

Will print to stdout (when run) like this:

Python 2.6.2 (r262:71605, Apr 14 2009, 22:40:02) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.


Welcome to mod2doctest
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Hello World!


Extended Example
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
[-99, -20, -3, -1, 0, 2, 10, 88, 100, 340]

Also, a docstring like this is created:

r"""
Welcome to mod2doctest
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Just enter in some python

.. warning::
  make sure to examine your resulting docstr to make sure output is as
  expected!

The basics:

>>> print 'Hello World!'
Hello World!

Extended Example
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A little more:

>>> somelist = [100, 2, -20, 340, 0, 0, 10, 10, 88, -3, 100, 2, -99, -1]
>>> sorted(set(somelist))
[-99, -20, -3, -1, 0, 2, 10, 88, 100, 340]

"""

Which, when included in sphinx documentation looks like this:

Welcome to mod2doctest

Just enter in some python

Warning

make sure to examine your resulting docstr to make sure output is as expected!

The basics:

>>> print 'Hello World!'
Hello World!

Extended Example

A little more:

>>> somelist = [100, 2, -20, 340, 0, 0, 10, 10, 88, -3, 100, 2, -99, -1]
>>> sorted(set(somelist))
[-99, -20, -3, -1, 0, 2, 10, 88, 100, 340]

Installation for Python 2.x

Try:

easy_install mod2doctest

or

pip mod2doctest

If that does not work go to http://pypi.python.org/pypi/mod2doctest/.

There you can grab the windows installer, egg, or source directly.

Also, go to http://github.com/cart0113/mod2doctest to grab the latest repo.

API

mod2doctest.convert(python_cmd, src=True, target='_doctest', add_autogen=True, add_testmod=True, ellipse_memid=True, ellipse_traceback=True, ellipse_path=True, run_doctest=False, doctest_flags=524, fn_process_input=None, fn_process_docstr=None, fn_title_docstr=None)
Summary :

Runs a module in shell, grabs output and creates a docstring.

Parameters:
  • python_cmd (str) – The python command that starts the shell (e.g. python or /bin/python2.4, etc).
  • src (True, module or file path) – The python module to be converted. If True is given, the current module is used. Otherwise, you need to provide either 1) a valid python module object or 2) a path (string) to the module to be run.
  • target (None, True, str file path, or str starting with ‘_’) –

    Where you want the output docstring to be placed:

    • None, the docstring is not saved anywhere (but it is returned by this function and convert will not exit).
    • True is given, the src module is used (the docstring is prepended to the file).
    • A path (of type str) is provided, the docstr is saved to that file.
    • And finally, a simple convention: if a string is given that starts with ‘_’ (e.g. ‘_doctest’), the output is saved to a file with the same name as the input, but with that string inserted right before the ‘.py’ of the file name. For example, if the src filename is ‘mytest.py’ and the target is ‘_doctest’ the docstring output will be saved to a file called ‘mytest_doctest.py’
  • add_autogen (True or False) – If True adds boilerplate python version / timestamp of current run to top of docstr.
  • add_testmod (True or False) – If True a if __name__ == '__main__' block is added to the output file IF the target parameter is an external file (str path).
  • ellipse_memid (True or False) – Add ellipse for memory ids.
  • ellipse_paths (True or False) – Add ellipse for front path of path (up to final rel path)
  • ellipse_traceback (True or False) – Ellipse middle part of traceback.
  • run_doctest (True or False) – If True doctest is run on the resulting docstring.
  • doctest_flags (doctest flags) – Valid OR’d together doctest flags. The default flags are (doctest.ELLIPSIS | doctest.REPORT_ONLY_FIRST_FAILURE | doctest.NORMALIZE_WHITESPACE)
  • fn_process_input (callable) – A function that is called and is passed the module input. Used for preprocessing.
  • fn_process_docstr (callable) – A function that is called and is passed the final docstring before saving. Used for post processing. You can use this function to perform your own custom regular expressions replacements and remove temporal / local data from your output before doctest is run.
  • fn_title_docstr (callable) – A function that is called and should return a string that will be used for the title.
Returns:

None or, if target=None a docstring of type str.

Examples

One great thing about doctest is that your tests can easily be converted to webpages using sphinx. Even for large test programs the linear webpage output is a great tool to understand the test setup and overall test structure.

By using the special #> and #| special mod2doctest comments, it is easy to create documentation at the same time as you are constructing your test.

The following tests below were generated using these techniques.

Note

In this case, to best understand what’s going on, look in mod2doctest.tests package. And, if you want, go to http://github.com/cart0113/mod2doctest, clone the repo, and check out how those modules are used in the Sphinx documentation.

How Does mod2doctest Work?

  • Basically, mod2doctest takes your input, fixes any whitespace problems, and then pipes it to an interpreter using the subprocess module.
  • Then, the output is collected and some processing is done to line up the original module code with the output from subprocess (basically lining up the ‘>>>’ and ‘...’ which is tricker than it sounds).
  • Then, a bunch of post processing is done to process the special mod2doctest comments and nicely format the final docstring.

This is why the output from mod2doctest is more formatted and readable than if you were to just paste a module into the intrepreter yourself.

Some Notes

A Word Of Warning

Here’s the warning: make sure to carefully inspect the output docstring or final sphinx webpage generated by mod2doctest.

mod2doctest basically provides a ‘snapshot’ of the current module run. Since it automatically copies the output to the docstring, it can be easy to skip the step of actually checking the output and have wrong output in the docstring. That is, just because the test ran does not mean it is what you really want.

To be a useful test fixture that can be used for, say regression testing you need to make sure the ‘snapshot’ contains the intended results.

mod2doctest normally exits at the end of convert()

If a target is given, convert() calls exit. This is to stop your code being run again since it’s already been piped to an interactive interpreter once (and that output printed to stdout/stderr for you).

mod2doctest can run your script up to two times

Just a quick note – mod2doctest can run your script up to two times if the run_doctest parameter is set to True.

For the example script:

if __name__ == '__main__':
        import mod2doctest
        mod2doctest.convert(src=True, target='_doctest', run_doctest=True)

print 'Foobar'

will be execute two times: once when the script is piped to a shell and once by doctest itself (to check if the doctest does in fact pass)

The script:

if __name__ == '__main__':
        import mod2doctest
        mod2doctest.convert(src=True, target='_doctest', run_doctest=False)

print 'Foobar'

will run only once (this one is not run in doctest).

will run once – just when the module code is piped to the shell.

Note

You may notice this if you have sleep / delay / blocking in your test and your test is slow to run. If you run mod2doctest like the first example it takes two times longer to run than you might have been expecting.

Indices and Tables