Posts tagged ‘opterator’

Opterator, revisited

A few years ago, I wrote a simple decorator that introspects a main() method signature and docstring to generate an option parser. I never really used it, mostly because back then, I wasn’t too keen on third party dependencies. Nowadays with distribute being maintained, well-documented, and working (plus I know how to use it properly), I no longer have this concern.

A friend recently linked me to the brilliantly designed docopt and reminded me of my interest in opterator. I revisited my code and decided it’s a pretty neat little design. So I ported it to Python 3. This took a matter of minutes, largely because I originally wrote it shortly after taking the pytest tutorial at Pycon 2009 and it was easy to find the failing code. It now supports Python 2.6, 2.7, and 3.3, according to tox.

I originally designed opterator to be a full replacement for optparse and friends. However, my main purpose for it now is to create quick and dirty command line applications, often for my personal use. These apps usually have a couple of options and it doesn’t seem worth the trouble of setting up optparse. Yet, mucking around with sys.argv is also annoying. Opterator minimizes the boilerplate. Check out this basic example:

from opterator import opterate                      


@opterate                                           
def main(filename, color='red', verbose=False):     
    print(filename, color, verbose)

main()                                              

with three lines of boilerplate code, a function can be turned into a command line program that can be called like so:

  $ python examples/basic.py this_file
  this_file red False

or so:

  python examples/basic.py this_file --color=blue
  this_file blue False

or even so:

  $ python examples/basic.py --color=purple another_file --verbose
  another_file purple True

And you even get a not totally (but somewhat) useless helpfile:

  $ python examples/basic.py -h
  Usage: basic.py [options] filename



Options:
  -h, --help            show this help message and exit
  -c COLOR, --color=COLOR
  -v, --verbose

Recent Developments

I’ve been awfully busy the past few weeks, but finally had three separate evenings to sit down and code on some of my little projects this week. I’m anticipating having more time in a couple weeks, as I gave notice on my job on Thursday. I am, however planning a move and exploring numerous job opportunities in my home province.

I managed to fix a couple bugs on WhoHasMy. As previously reported this project was originally coded in 48 hours for the Django Dash competition. We tied for fifth place and have traded the resulting bitbucket account for a github prize. I’m very happy with the placement given that my brilliant co-developers had nil django experience going into the competition and I hadn’t touched it professionally in months. I added a TOS to the page as requested in a comment on my earlier post, fixed some ordering bugs in the lists, fixed a couple broken links, and made it easier to add information about friends when you loan an item to someone not currently in the system. And here we thought it was 100% bug free when we finished our 48 hour stint and stumbled off to bed.

I have also spent a fair bit of time improving Quodroid, the Android app for controlling quod libet on my laptop from my phone. It now uses fancy icon buttons, allows you to specify the host and port you want to connect to, lists the currently playing song whenever you perform an action, allows volume control, and gives a semi-sane error message when the phone can’t connect. In short, its actually useful and usable by someone other than myself. I’ve been using it regularly the past few days. I still have to arrange it to perform the network stuff in a service instead of the main activity, which occasionally becomes unresponsive if the server is slow to respond. I’m actually becoming more comfortable with Java again as I develop this, its not as evil as I thought, but it certainly cuts into productivity.

Today, I made a few changes to opterator. I wrote my first app (a contrived example code-test for a job I’m pursuing) that actually used opterator a couple weeks back and found it was missing a few features. It now the ability to have multiple copies of a single option. Turns out this actually worked, all you had to do was use the ‘append’ action. I wrote three tests, didn’t change a line of code and poof, I had append support! I then realized that storing the action in the docstring was unnecessary as it could be introspected from the type of the keyword argument. This makes the @param docstrings a lot more readable and informative. As simple as this little module is, I feel its one of my more brilliant innovations.

I’ve also tossed around the idea of having multiple opterated main methods in a single module and allow the decorator to pick which one to call depending on the options. This seemed cool at first, but I think it may violate the ‘one best way’ policy of Python. I also realized that deriving sensible error messages and usage strings would be really painful, from the end user’s perspective, so I’m holding off on this until I’ve decided how best to do it.

Introducing Opterator

When I attended Pycon 2009, I saw a lightning talk on a neat little tool called Argparse. Its a python option parsing tool designed to be easier to use than optparse.

In the first few seconds of the talk, I thought to myself “I know where this is going. It’s brilliant.” I said so to the guy next to me. Then I took it back because while argparse is a pretty cool idea, its not the brilliant idea I thought it was.

See, I thought the tool would automatically introspect a script’s main method and use the docstring to provide any missing information. That’s what I thought would be brilliant.

Since it didn’t do that, I did it. Its certainly not full-featured yet, but it works for basic options and arguments. For 90% of scripts, that’s all you need. I called it opterator and put it on github:

http://github.com/buchuki/opterator/tree/master.

git clone git://github.com/buchuki/opterator.git

Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from opterator import opterate
@opterate
def main(filename1, filename2, recursive=False, backup=False,
        suffix='~', *other_filenames):
    '''An example copy script with some example parameters that might
    be used in a copy command.
 
    @param recursive store_true -r --recursive copy directories
        recursively
    @param backup store_true -b --backup backup any files you copy over
    @param suffix store -S --suffix override the usual backup
        suffix '''
    filenames = [filename1, filename2] + list(other_filenames)
    destination = filenames.pop()
 
    print "You asked to move %s to %s" % (filenames, destination)
    if recursive:
        print "You asked to copy directories recursively."
    if backup:
        print "You asked to backup any overwritten files."
        print "You would use the suffix %s" % suffix
 
if __name__ == '__main__':
    main()

Obviously, that only parses the options, its not doing the actual copying because implementing that would do nothing to illustrate how opterator is working. Here’s one run of it:

dusty:opterator $ python copy.pyc -b a b c d -r
You asked to move ['a', 'b', 'c'] to d
You asked to copy directories recursively.
You asked to backup any overwritten files.
You would use the suffix ~

Here’s the automatically generated help:

dusty:opterator $ python copy.py -h
Usage: copy.py [options] filename1 filename2 [other_filenames]

An example copy script with some example parameters that might
    be used in a copy command.

Options:
  -h, --help            show this help message and exit
  -r, --recursive       copy directories recursively
  -b, --backup          backup any files you copy over
  -S SUFFIX, --suffix=SUFFIX
                        override the usual backup suffix

I named it opterator because I wanted a name that didn’t have ‘parse’ in it. I was going to go for operator but I realized if you heard about this app called operator and searched google for ‘python operator’ you’d get swamped. ‘opterator’ is unique and highlights the option part of the task. Plus it allowed me to invent a new verb: ‘opterate’. Days when I get to invent new verbs are always good days.

I used py.test for testing (another gem I discovered at pycon). You don’t need to run the tests, but the tests are useful examples to see what opterator can do, so have a look.

So I hope somebody finds this useful or even awesome and contributes some patches to make it more of the above. Enjoy!