28 Dec 2017

Parsing arguments in Python isn’t hard. There are already many libraries for command line argument parsing in Python, not least argparse in the standard library. However, just because it’s not hard doesn’t mean it’s entirely without effort, and when you’re working on specific problem it’s probably not top of your mind. Creating a proper command line syntax for your quick script or runnable module requires making declarations or tagging things, so you just end up writing something like this and moving on:

if __name__ == '__main__':
    infile, outfile = sys.argv[1:]
    do_stuff(infile, outfile)

It’s rough, but it gets the job done. However, throw away scripts have a tendency not to get thrown away, and this kind of overly spartan, undocumented CLI can end up hanging around for longer than first thought. Adding features is a pain, and a rewrite using a more structured framework is a lot of effort and has to maintain compatibility with the ad hoc version.

Quarg was written to address this problem directly, allowing a proper command line interface to be built up incrementally, starting from nothing. The first and most important design goal was that it worked on unmodified Python code, and produced sensible results. This is done by taking the information that’s already there — top level functions, parameters names, and docstrings — and building an interface based on some broad assumptions and conventions.

The purely automatic interface (which you can get by running your script with quarg as the interpreter, or adding a call to quarg.main() at the end) is useful, and often sufficient, but you can refine it by adding annotations in the form of function decorators and specially formatted docstrings. This is what allows and interface to be built incrementally, rather than requiring a fixed amount of effort up front.

Under the hood, the actual argument parsing is done with argparse, and much of the functionality of that library is available via the arg decorator. While there are fancier libraries out there, I’ve always found the parsing of argparse to be good enough, and it avoids adding an external dependency. Quarg only depends on the standard library, and works in Python 2 and 3, so it should be easy to integrate into most contexts. It’s available on PyPI (pip install quarg).

(On a personal note, I’ve been using Python for 18 years, and while that’s mostly been ad hoc and proprietary stuff, I’ve shared numerous projects on GitHub (and, earlier, SourceForge). However, this is the first package I’ve uploaded to PyPI. The process was pretty straightforward and painless thanks to the Python Packaging User Guide, and I’ll definitely be doing it again. The spread of unified package indices such as PyPI, NPM and CPAN are one of the biggest practical improvements to the programming experience that I’ve seen over the last two decades, and it’s taken me too long to pitch in.)

There are numerous ways in which Quarg could be improved — for example, by making use of type annotations if they’re present. There are also no doubt numerous bugs still to be fixed. Nevertheless, we’ve been using it internally at Cydar for a while now, and found it pretty useful. Hopefully you will too.

While I’m the main author and maintainer of the library, Quarg began life at Cydar, and the company retains the copyright. Thanks to my colleagues at Cydar, particularly Ian Glover, for useful feedback and code contributions. Unfortunately the latter are not attributed in the Git history, as I had to excise it from a larger private repository.

This site is maintained by me, Rob Hague. The opinions here are my own, and not those of my employer or anyone else. You can mail me at rob@rho.org.uk, and I'm @robhague@mas.to on Mastodon and robhague on Twitter. The site has a full-text RSS feed if you're so inclined.

Body text is set in Georgia or the nearest equivalent. Headings and other non-body text is set in Cooper Hewitt Light. The latter is © 2014 Cooper Hewitt Smithsonian Design Museum, and used under the SIL Open Font License.

All content © Rob Hague 2002-2024, except where otherwise noted.