Python CLI with click

2023-05-22 python click CLI arguments help generation

In previous article I showed simple python script to extract data from ispell dictionaries. It worked well for first pass on the problem, but as the Hangman implementation continues, I needed to generate the list of words with different parameters. Coincidentally, I read an article about reading the docs and usage of click that pointed me to quite nice extensions of regular python function.

Here is the example. I started with main function implemented like this

import click

@click.command()
@click.argument('input', type=click.File('r', encoding='utf-8'))
@click.option('-o', '--output', type=click.File('w', lazy=False, encoding='utf-8'), default='-', help='Write to file instead of stdout.')
def main(input, output):
    for entry in filter(longer_noun, ispell_entries(input)):
        output.write(entry[0] + '\n')

if __name__ == '__main__':
    main()

The example shows input and output parameters that are automatically opened as a file and supplied by click. The interface is pretty standard, allows to read stdin and output to stdout or specify actual files. Click also does quite some checks to make sure the unicode would work both on console and the files.

This was good start, but lend itself easily to add more parameters and drive the filtering other using the them

@click.command()
@click.argument('input', type=click.File('r', encoding='utf-8'))
@click.option('-o', '--output', type=click.File('w', lazy=False, encoding='utf-8'), default='-', 
                                                         help='Write words to file instead of stdout.')
@click.option('--min-len', default=5, show_default=True, help='Minimum characters for the word')
@click.option('--max-len', default=8, show_default=True, help='Maximum characters for the word')
@click.option('--pattern', default='HP', show_default=True, help='Pattern for selected words')
def main(input, output, min_len, max_len, pattern):
    """
    Extracts noun words of specified length from ispell dictionary.
    Files this was designed to work with are from https://github.com/tvondra/ispell_czech

    INPUT is the ispell .cat file like hlavni.cat from distribution above
    """
    for entry in filter(create_longer_noun_filter(pattern, min_len, max_len), ispell_entries(input)):
        output.write(entry[0] + "\n")

click gives you automatically generated help, also. When run with --help parameter it provides description that utilizes the docstring of the main method, help parameters of click.option and default values

Usage: ispell-words.py [OPTIONS] INPUT

  Extracts noun words of specified length from ispell dictionary. Files this
  was designed to work with are from https://github.com/tvondra/ispell_czech

  INPUT is the ispell .cat file like hlavni.cat from distribution above

Options:
  -o, --output FILENAME  Write words to file instead of stdout.
  --min-len INTEGER      Minimum characters for the word  [default: 5]
  --max-len INTEGER      Maximum characters for the word  [default: 8]
  --pattern TEXT         Pattern for selected words  [default: HP]
  --help                 Show this message and exit.

Last thing of interest is creation of filter function using a closure that wraps around parameters of the create_longer_noun_filter function in inner definition, that is returned and used for filtering of the words

def create_longer_noun_filter(pattern, min_len, max_len):
    def longer_noun(entry):
        word, flags = entry
        if not re.search(f'[{pattern}]', flags):
            return False
        return min_len <= len(word) <= max_len
    return longer_noun