Tab completion in python, pdb and zopectl

published Oct 17, 2006, last modified May 18, 2007

Two speed improvements for those who spend their time in python the pdb (python debugger) or the zopectl debugger.

First I noted this blog entry by Tom Lazar. Two simple lines in a .pdbrc file in your home directory are enough to give you tab completion in your pdb session:

    import rlcompleter
    pdb.Pdb.complete = rlcompleter.Completer(locals()).complete

Be sure to read the comments at Toms site. Also see the original recipe at ASPN.

I tried this and it worked perfectly. That should give me a productivity boost. Since I work with Zope I started a bin/zopectl debug session and tried tab completion there, but that didn't work. That's logical as the zope debug is not the same as a pdb session. Daniel Nouri came with the obvious answer: just do import pdb; pdb.set_trace() in your debug session! Works fine then.

But then I thought: if this is possible in the pdb, then it should surely be possible in a normal python session. So I searched and found an article by Jacob on ParTecs Weblog. This shows that you can add a .pythonrc.py file in your home directory which contains:

    import rlcompleter, readline
    readline.parse_and_bind('tab: complete')

Set an environment variable:

    export PYTHONSTARTUP=~/.pythonrc.py

Now startup python and you have tab completion. Grand! Jacob's article shows another handy function that you can put in that file, so be sure to read it. While you are at it, check out some more pdbrc ideas. I added some lines there as well.

This still doesn't give you tab completion in a zopectl session though, unless you do the pdb trick. But I prefer not to type/paste that everytime I do some zopectl debugging. I guess zopectl doesn't even try loading that .pythonrc.py file, probably for safety reasons, so noone can sneak in some code. Let's see if we can find a workaround.

Hm, the $PYTHONSTARTUP environment variable is only read for interactive python shells. Apparently a debug session is not considered interactive.

Okay, for starters I can shave off a few key strokes. Run bin/zopectl debug and then:

    import user

This will run your ~/.pythonrc.py file. Two notes:

  • This does not use the $PYTHONSTART environment variable. It is hardcoded to load only the .pythonrc.py file in your home directory. So if you can't or don't want to set that environment variable, you can still just do an import user in an interactive python session and your .pythonrc.py file will get loaded.
  • If you have defined a function in your file, it is not available for use directly like in a really interactive python session, but only with the module name attached; so with the mentioned example file, you can need to type user.source(user), which shows the source of the user module.

Now my reasoning goes like this. The .pythonrc.py file is meant for interactive python sessions. A zopectl debug session sure feels like an interactive python session. In fact, it is an interactive python session, as zopectl debug uses the -i option to let python execute some statements and then drop into in interactive mode; but that is a special case, so your user file does not get loaded. But the general idea remains: it would be handy to load those user defaults. Of course that would only be handy in debug mode, not in other zopectl modes.

How do we do this? We change the $SOFTWARE_HOME/Zope2/Startup/zopectl.py file:

    def do_debug(self, arg):
        cmdline = self.get_startup_cmd(self.options.python + ' -i',
                                       'import user;' +
                                       'import Zope2; app=Zope2.app()')
        print ('Starting debugger (the name "app" is bound to the top-level '
               'Zope object)')
        os.system(cmdline)

The addition of import user is the only change there. For reference, the command line then becomes (split into a few lines here):

    /usr/bin/python2.3 -i -c "from Zope2 import configure;\
    configure(\'/home/maurits/instances/eXtremeManagement/etc/zope.conf\');\
    import user;import Zope2; app=Zope2.app()"

Since we do this in the do_debug function, this only gets used when you do a zopectl debug, not in a zopectl start or fg. So it should be safe, as far as I can tell.

Actually, after rereading I see that Jacob put something very similar into his above linked article.

So how about it? Can this go into Zope?

Keywords
plone