An illustrated history¶
This document recounts the story of each clize version. Here’s the short version first:
Version |
Release date |
Major changes |
---|---|---|
1.0b |
April 4th 2011 |
First release |
2.0 |
October 7th 2012 |
Subcommands, Python 3 syntax support |
2.2 |
August 31st 2013 |
Minor additions |
2.4 |
October 2nd 2013 |
Bugfixes |
3.0 |
May 13th 2015 |
Extensibility, decorators, focus on py3 |
3.1 |
October 3rd 2016 |
Better decorators, py3-first docs |
You can also browse the release notes.
And here’s the story:
Before Clize¶
After having used optparse
and twisted’s usage.Options
in various medium-sized projects, and
viewing argparse
as being more of the same, I wondered if I needed all this
when writing a trivial program. I realized that if I only wanted to read some
positional arguments, I could just use tuple unpacking on sys.argv
:
from __future__ import print_function
import sys
script, first, last = sys.argv
print('Hello', first, last)
If you used argument packing in a function call instead, you gain the ability to make use of default values:
from __future__ import print_function
import sys
def greet(script, first, last, greeting="Hello"):
print(greeting, first, last)
greet(*sys.argv)
It works nicely save for the fact that you can’t request a help page from it or have named options. So I set out to add those capabilities while doing my best to keep it as simple as possible, like the above example.
1.0: A first release¶
from __future__ import print_function
from clize import clize, run
@clize
def greet(first, last, greeting="Hello"):
print(greeting, first, last)
run(greet)
Thanks to the ability in Python to look at a function’s signature, you gained a
--help
page, and greeting
was available as --greeting
on the
command line, while adding just one line of code. This was very different from
what argparse
had to offer. It allowed you to almost completely ignore
argument parsing and just write your program’s logic as a function, with your
parameters’ documented in the docstring.
In a way, Clize had opinions about what a CLI should and shouldn’t be like. For instance, it was impossible for named parameters to be required. It was generally very rigid, which was fine given its purpose of serving smaller programs.
It hadn’t visibly garnered much interest. I was still a user myself, and no other argument parser had really interested me, so I kept using it and watched out for possible improvements. Aside from the subcommand dispatcher, there was little user feedback so the inspiration ended up coming from somewhere else.
2.0: Function annotations¶
Clize 2.0 came out with two major features. Subcommands and a new way of specifying additional information on the parameters. I’ll skip over subcommands because they are already a well established concept in argument parsing. See Multiple commands for their documentation.
Through now forgotten circumstances, I came across PEP 3107 implemented since Python 3.0, which proposed a syntax for adding information about parameters.
Up until then, if you wanted to add an alias to a named parameter, it looked a bit like this:
from __future__ import print_function
from clize import clize, run
@clize(require_excess=True, aliases={'reverse': ['r']})
def echo(reverse=False, *args):
text = ' '.join(args)
if reverse:
text = text[::-1]
print(text)
run(echo)
Many things involved passing parameters in the decorator. It was generally quite ugly, especially when more than one parameter needed adjusting, at which point the decorator call grew to the point of needing to be split over multiple lines.
The parameter annotation syntax from PEP 3107 was fit to replace this. You could tag the parameter directly with the alias or conversion function or whatever. It involved looking at the type of each annotation, but it was a lot more practical than spelling alias, converter and the parameter’s name all over the place.
It also allowed for keyword-only parameters from PEP 3102 to map directly to named parameters while others would always be positional parameters.
from __future__ import print_function
from clize import clize, run
@clize(require_excess=True)
def echo(*args, reverse:'r'=False):
text = ' '.join(args)
if reverse:
text = text[::-1]
print(text)
run(echo)
Python 3 wasn’t quite there yet, so these were just features on the side at the time. I liked it a lot however and used it whenever I could, but had to use the older interface whenever I had to use Python 2.
3.0: The rewrite¶
Python 3.3 introduced inspect.signature
, an alternative to the rough
inspect.getfullargspec
. This provided an opportunity to start again from
scratch to build something on a solid yet flexible base.
For versions of Python below 3.3, a backport of inspect.signature
existed on
PyPI. This inspired a Python 3-first approach: The
old interface was deprecated in favor of the one described just above.
from clize import run, parameter
def echo(*args: parameter.required, reverse:'r'=False):
text = ' '.join(args)
if reverse:
text = text[::-1]
print(text)
run(echo)
Since the @clize
decorator is gone, echo
is now just a regular function
that could theoretically be used in non-cli code or tests.
Users looking to keep Python 2 compatibility would have to use a compatibility
layer for keyword-only parameters and annotations: sigtools.modifiers
.
from __future__ import print_function
from sigtools import modifiers
from clize import run, parameter
@modifiers.autokwoargs
@modifiers.annotate(args=parameter.REQUIRED, reverse='r')
def echo(reverse=False, *args):
text = ' '.join(args)
if reverse:
text = text[::-1]
print(text)
run(echo)
sigtools
was created specifically because of Clize, but it aims to be a
generic library for manipulating function signatures. Because of Clize’s
reliance on accurate introspection data on functions and callables in general,
sigtools
also provided tools to fill the gap when inspect.signature
stumbles.
For instance, when a decorator replaces a function and complements its
parameters, inspect.signature
would only produce something like (spam,
*args, ham, **kwargs)
when Clize would need more information about what
*args
and **kwargs
mean.
sigtools
thus provided decorators such as forwards
and
the higher-level wrapper_decorator
for specifying what
these parameters meant. This allowed for creating decorators for CLI
functions in a way analogous to regular decorators,
which was up until then something other introspection-based tools had never
done. It greatly improved Clize’s usefulness with multiple commands.
With the parser being completely rewritten, a large part of the argument
parsing was moved away from the monolithic “iterate over sys.argv
” loop to
one that deferred much of the behaviour to parameter objects determined from
the function signature. This allows for library and application authors to
almost completely customize how their parameters work, including things like replicating --help
’s behavior of working
even if there are errors beforehand, or other completely bizarre stuff.
This is a departure from Clize’s opiniated beginnings, but the defaults remain
sane and it usually takes someone to create new Parameter
subclasses for bizarre stuff to be made. In return Clize gained a flexibility
few other argument parsers offer.