-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
274 additions
and
262 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
|
||
.. _comparisons: | ||
|
||
Clize compared to... | ||
==================== |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
.. _history: | ||
|
||
An illustrated history | ||
---------------------- | ||
|
||
|
||
.. _before clize: | ||
|
||
Before Clize | ||
............ | ||
|
||
.. |twopt| replace:: twisted's `usage.Options` | ||
.. _twopt: http://twistedmatrix.com/documents/13.1.0/core/howto/options.html | ||
|
||
After having used `optparse` and |twopt|_ in various medium-sized projects, and | ||
seeing `argparse` as being more of the same, I wondered if I needed all this | ||
when writing a more trivial program. I realized that if I only wanted to read | ||
some positional arguments, I could just use tuple unpacking on `sys.argv`: | ||
|
||
.. code-block:: python | ||
from __future__ import print_function | ||
import sys | ||
script, first, last = sys.argv | ||
print('Hello', first, last) | ||
If you used argument packing in a functon call instead, you gain the ability to | ||
make use of default values: | ||
|
||
.. code-block:: python | ||
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. | ||
|
||
|
||
.. _first release: | ||
|
||
A first release | ||
............... | ||
|
||
.. code-block:: python | ||
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 than | ||
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. | ||
|
||
|
||
.. _history annotations: | ||
|
||
Function annotations | ||
.................... | ||
|
||
Clize 2.0 came out with two major features. :ref:`Subcommands <multiple | ||
commands>` and a new way of specifying additional information on the | ||
parameters. I'll gloss over subcommands because this is already a well | ||
established concept in argument parsing and the docs can tell you about it. | ||
|
||
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: | ||
|
||
.. code-block:: python | ||
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, and it was generally | ||
ugly, especially when more than one parameter needed adjusting and the line had | ||
to be split. | ||
|
||
The parameter annotation syntax from :pep:`3107` was fit to replace this | ||
nicely. 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. | ||
|
||
.. code-block:: python | ||
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. | ||
|
||
|
||
.. _history rewrite: | ||
|
||
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 <https://pypi.python.org/>`. This inspired a Python 3-first approach: The | ||
old interface was deprecated in favor of the one described just above. | ||
|
||
.. code-block:: python | ||
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 not 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 compability | ||
layer for keyword-only parameters and annotations: `sigtools.modifiers`. | ||
|
||
.. code-block:: python | ||
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 `~sigtools.specifiers.forwards` and | ||
the higher-level `~sigtools.wrappers.wrapper_decorator` for specifying what | ||
these parameters meant. This allowed for :ref:`creating decorators for CLI | ||
functions <function-compositing>`, in a way analogous to regular decorators, in | ||
a way that other introspection-based tools had never done up until then. 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 previously-determined parameter | ||
objects. This allows for library authors to almost completely :ref:`customize | ||
how their parameters work <extending parser>`, 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 `~clize.parser.Parameter` | ||
subclasses for bizarre stuff to be made. In return Clize gained a flexibility | ||
few other argument parsers offer. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.