Lunchtime Python #3: Click¶
Presenter: Dominic Kempf, Scientific Software Center
Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary.
Command Line Interfaces: Why?¶
Natural evolution of a piece of research software in Python:
- Starts on a Jupyter notebook playground
- At some point freezes into a script for long term use
- (Automated) Application to a wider range of usage scenarios
For the last step, good Command Line Interface (CLI) is very helpful. Important aspects:
- Easy addition into existing code
- Few lines of code to ease maintenance and focus on the scientific part
- Good help text generation
How not to do it¶
We get access to command line arguments similar to C through sys.argv
:
# DON'T DO THIS!
import sys
inputfile = sys.argv[1]
print(f"Calculating statistics from {inputfile}")
Why is this bad?
- No help text generation
- No validation of arguments
- Prone to errors in argument indexing
- Logic for non-string and optional arguments becomes quickly unwieldy
The standard libraries argparse
and optparse
are better options, but there is even better.
Click: A beautiful, opinionated approach¶
import click
@click.command()
@click.argument("inputfile", type=click.Path(exists=True))
def stats(inputfile):
"""Read data from the given INPUTFILE and calculate useful statistics"""
click.echo(f"Calculating statistics from {inputfile}")
# This allows use of this Python file both for imports and for the CLI
if __name__ == "__main__":
stats()
Arguments vs. Options¶
Arguments are positional and only too a small extent optional or defaultable. Use them only for absolute essential, self-explanatory input. To customize your script's behavious options are the better choice:
import click
@click.command()
@click.option(
"--input",
type=click.Path(exists=True),
default="input.txt",
help="The data file to read from",
)
@click.option(
"--verbose/--no-verbose", type=bool, help="Whether to output intermediate results"
)
def stats(verbose, input):
"""Read data and calculate useful statistics"""
if verbose:
click.echo("Started the CLI script")
click.echo(f"Calculating statistics from {input}")
if __name__ == "__main__":
stats()
Composability of commands¶
Add subcommand structure by reusing previously defined commands:
@click.group()
def main():
pass
@click.command()
def preprocess():
click.echo("Apply preprocessing")
main.add_command(stats)
main.add_command(preprocess)
if __name__ == "__main__":
main()
This mechanism is very powerful: arbitrary nesting, runtime extension e.g. through plugins etc.
Setuptools integration¶
As software becomes more mature, it is also advisable to package and distribute it as a Python package. Click easily integrates with setuptools
as well using the entrypoints mechanism:
# In setup.py
setup(entry_points={"console_scripts": ["myscript = mypackage.mymodule:myclifunction"]})
# In setup.cfg
[options.entry_points]
console_scripts =
myscript = mypackage.mymodule:myclifunction
Further information about click¶
Today's presentation can be found on the Lunch Time Python website: https://ssciwr.github.io/lunch-time-python/
For further information, see also the (very good) Click documentation
For questions to the Scientific Software Center, please write us to ssc@iwr.uni-heidelberg.de
Lunch Time Python Session #4¶
Library options, please vote now:
- itertools is a standard library that implements a number of iterator building blocks inspired by functional programming languages.
- pytest The pytest framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries.
- matplotlib Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python