Phase objects: simple decent error reporting for Python programs

For a lot of the sort of utility programs I write in Python, the only thing the program can really do when it runs into an operating system level error is report whatever the problem is and exit. So programs start out as one big try:/except: block:

try:
    fp1 = open(fnameA, "r")
    ....
    fp2 = open(fnameB, "r")
    ....
except EnvironmentError, e:
    die("OS error: %s" % str(e))

The problem with this is that the error reported, while accurate, is basically without context; you don't know what the program was doing when it reported 'permission denied' or whatever. So simple programs mutate slightly to keep track of what they are doing in a variable:

try:
    phase = "opening %s" % fnameA
    fp1 = open(fnameA, "r")
    phase = "parsing %s" % fnameA
    ....
except EnvironmentError, e:
    die("%s: error: %s" % (phase, str(e)))

(Well, my simple programs do, since I have no desire to wrap each separate operation that might fail in its own try:/except: block. That's too much work.)

The problem with this approach is that it doesn't work very well when your program grows subroutines, because updating a global variable to track the phase is a pain. The solution I have adopted is to create a special class to track what phase of execution the program is in; with my typical (lack of) imagination, I call this class Phase, and it looks like this:

class Phase(object):
    def __init__(self):
        self.phase = ["starting",]
    def __call__(self, ph):
        self.phase = [ph, ]
    def __str__(self):
        return ": ".join(self.phase)
    def push(self, ph):
        self.phase.append(ph)
    def pop(self):
        self.phase.pop()

It is used as phase("reading file whatever"). It supports push() and pop() operations so that you can have nested situations (such as the ability of configuration files to include other configuration files) that report the full chain of events of how you wound up doing whatever it was that failed.

My programs just make a single global Phase instance and then use it directly as a global variable, but I suppose a more elegant and reusable method would be to create the instance at the top level and pass it down through the call chain.

These are my WanderingThoughts
(About the blog)

GettingAround
Full index of entries
Recent comments

This is part of CSpace, and is written by ChrisSiebenmann.

* * *

Atom feeds are available; see the bottom of most pages.

This is a DWiki.
(Help)

Categories: links, linux, programming, python, snark, solaris, spam, sysadmin, tech, unix, web

Search:
Written on 28 September 2007.
(Previous | Next)

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Fri Sep 28 22:58:54 2007
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.