Printing out all your documentation including a status report

Since this chapter has been about both documentation and testing, let's build a script that takes a set of modules and prints out a complete report, showing all documentation as well as running any given tests.

This is a valuable recipe, because it shows us how to use Python's APIs to harvest a code-driven runnable report. This means the documentation is accurate and up to date, reflecting the current state of our code.

How to do it...

In the following steps, we will write an application and some doctests. Then we will build a script to harvest a useful report.

  1. Create a new file called recipe21_report.py to contain the script that harvests our report.
  2. Start creating a script by importing Python's inspect library as the basis for drilling down into a module from inspect import*
  3. Add a function that focuses on either printing out an item's __doc__ string or prints out no documentation found.
    def print_doc(name, item):
        if item.__doc__:
            print "Documentation for %s" % name
            print "-------------------------------"
            print item.__doc__
            print "-------------------------------"
        else:
            print "Documentation for %s - None" % name
  4. Add a function that prints out the documentation based on a given module. Make sure this function looks for classes, methods, and functions, and prints out their docs.
    def print_docstrings(m, prefix=""):
        print_doc(prefix + "module %s" % m.__name__, m)
    
        for (name, value) in getmembers(m, isclass):
            if name == '__class__': continue
            print_docstrings(value, prefix=name + ".")
    
        for (name, value) in getmembers(m, ismethod):
            print_doc("%s%s()" % (prefix, name), value)
        for (name, value) in getmembers(m, isfunction):
            print_doc("%s%s()" % (prefix, name), value)
  5. Add a runner that parses the command-line string, and iterates over each provided module.
    if __name__ == "__main__":
        import sys
        import doctest
    
        for arg in sys.argv[1:]:
            if arg.startswith("-"): continue
            print "==============================="
            print "== Processing module %s" % arg
            print "==============================="
            m = __import__(arg)
            print_docstrings(m)
            print "Running doctests for %s" % arg
            print "-------------------------------"
            doctest.testmod(m)
  6. Create a new file called recipe21.py to contain an application with tests that we will run the earlier script against.
  7. In recipe21.py, create a shopping cart app and fill it with docstrings and doctests.
    """ This is documentation for the entire recipe.
    With it, we can demonstrate usage of the code.
    
    >>> cart = ShoppingCart().add("tuna sandwich", 15.0)
    >>> len(cart)
    1
    >>> cart.item(1)
    'tuna sandwich'
    >>> cart.price(1)
    15.0
    >>> print round(cart.total(9.25), 2)
    16.39
    """
    
    class ShoppingCart(object):
        """
        This object is used to store the goods.
        It conveniently calculates total cost including
        tax.
        """
    
        def __init__(self):
            self.items = []
    
        def add(self, item, price):
            "Add an item to the internal list."
            self.items.append(Item(item, price))
            return self
    
        def item(self, index):
            "Look up the item. The cart is a 1-based index."
            return self.items[index-1].item
    
        def price(self, index):
            "Look up the price. The cart is a 1-based index."
            return self.items[index-1].price
    
        def total(self, sales_tax):
            "Add up all costs, and then apply a sales tax."
            sum_price = sum([item.price for item in self.items])
            return sum_price*(1.0 + sales_tax/100.0)
        def __len__(self):
            "Support len(cart) operation."
            return len(self.items)
    
    class Item(object):
        def __init__(self, item, price):
            self.item = item
            self.price = price
  8. Run the report script against this module using -v, and look at the screen's output.
    ===============================
    == Processing module recipe21
    ===============================
    Documentation for module recipe21
    -------------------------------
    
    This is documentation for the this entire recipe.
    With it, we can demonstrate usage of the code.
    
    >>> cart = ShoppingCart().add("tuna sandwich", 15.0)
    >>> len(cart)
    1
    >>> cart.item(1)
    'tuna sandwich'
    >>> cart.price(1)
    15.0
    >>> print round(cart.total(9.25), 2)
    16.39
    
    -------------------------------
    Documentation for Item.module Item - None
    Documentation for Item.__init__() - None
    Documentation for ShoppingCart.module ShoppingCart
    -------------------------------
        This object is used to store the goods.
        It conveniently calculates total cost including
        tax.
    
    Running doctests for recipe21
    -------------------------------
    Trying:
        cart = ShoppingCart().add("tuna sandwich", 15.0)
    Expecting nothing
    ok
    Trying:
        len(cart)
    Expecting:
        1
    ok
    5 tests in 10 items.
    5 passed and 0 failed.
    Test passed.
    

How it works...

This script is tiny, yet harvests a lot of useful information.

By using Python's standard inspect module, we are able to drill down starting at the module level. The reflective way to look up a docstring is by accessing the __doc__ property of an object. This is contained in modules, classes, methods, and functions. They exist in other places, but we limited our focus for this recipe.

We ran it in verbose mode, to show that the tests were actually executed. We hand parsed the command-line options, but doctest automatically looks for -v to decide whether or not to turn on verbose output. To prevent our module processor from catching this and trying to process it as another module, we added a line to skip any -xyz style flags.

        if arg.startswith("-"): continue

There's more...

We could spend more time enhancing this script. For example, we could dump this out with an HTML markup, making it viewable in a web browser. We could also find third party libraries to export it in other ways.

We could also work on refining where it looks for docstrings and how it handles them. In our case, we just printed them to the screen. A more reusable approach would be to return some type of structure containing all the information. Then the caller can decide whether to print to screen, encode it in HTML, or generate a PDF document.

This isn't necessary, however, because this recipe's focus is on seeing how to mix these powerful out-of-the-box options which Python provides into a quick and useful tool.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.236.70