Skip to main content

Tests Codecov PyPI PyPI pyversions

click-inspect

Add options to click commands based on inspecting functions

Installation

pip install click-inspect

Usage

Suppose an application containing an API function for which we would like to expose a command line interface. That function expects one or two arguments with internal data types and a bunch of configuration options. For example:

def display_data(data: List[Tuple[int, int]],
                 *,
                 size: int, symbol: str = 'x', empty: str = ' ') -> str:
    """Display the given data points in a 2D ASCII grid.

    Args:
        data (list of (int, int)): The data points as x- and y-tuples.
        size (int): Size of the grid in both dimensions.
        symbol (str): Symbol for displaying data points.
        empty (str): Symbol for displaying empty space.

    Returns:
        str: The string containing the grid.
    """
    grid = [[empty]*size for _ in range(size)]
    for x, y in data:
        grid[y][x] = symbol
    top = bottom = ('+', *'-'*size, '+')
    grid = (top, *(('|', *row, '|') for row in grid), bottom)
    return '\n'.join(map(''.join, grid))

Here the type of the first argument, data, is an internal aspect of the application, but the remaining arguments are generic options.

Now we want to create a click interface for using this function from the command line. Specifically we want it to work on JSON files of the following format:

{"data": [[1, 1], [2, 4], [3, 3]]}

So the only thing our command needs to do is to read the JSON file and convert the content in a way that it is compatible with what display_data expects:

import json
import click


@click.command()
@click.argument('file')
def display(file):
    with open(file) as fh:
        data = json.load(fh)['data']
    data = [[int(x) for x in row] for row in data]
    print(display_data(data))


if __name__ == '__main__':
    display()

Then we can run the program in the following way:

$ python example.py test.json
+-----+
|     |
| x   |
|     |
|   x |
|  x  |
+-----+

Now this only uses the default configuration of the display_data function and we also want to expose these optional parameters to the command line interface. We can do so by adding a few options:

@click.command()
@click.argument('file')
@click.option('--size', default=5, help='Size of the grid in both dimensions.')
@click.option('--symbol', default='x', help='Symbol for displaying data points.')
@click.option('--empty', default=' ', help='Symbol for displaying empty space.')
def display(file, size, symbol, empty):
    with open(file) as fh:
        data = json.load(fh)['data']
    data = [[int(x) for x in row] for row in data]
    print(display_data(data, size=size, symbol=symbol, empty=empty))

But that's a lot of code duplication. We duplicated the parameter names, the default values and the help text from the docstring. Also if we decide to add a new parameter to display_data we need to update the command as well.

This is where click-inspect comes in handy. Using the add_options_from decorator we can simply add all optional parameters from display_data to the display command without code duplication:

from click_inspect import add_options_from

@click.command()
@click.argument('file')
@add_options_from(display_data)
def display(file, **kwargs):
    with open(file) as fh:
        data = json.load(fh)['data']
    data = [[int(x) for x in row] for row in data]
    print(display_data(data, **kwargs))

Customization

The add_options_from decorator supports various keyword parameters which can be used for customizing the way options are created from parameters. Please refer to the docstring of add_options_from for more information. In the following some possibilities are shown:

# This adds only the `size` and `empty` parameters as options:
@add_options_from(display_data, include={'size', 'empty'})

# This has a similar effect by excluding the `symbol` parameter:
@add_options_from(display_data, exclude={'symbol'})

# This specifies custom option-names for some of the parameters:
@add_options_from(display_data, names={'size': ['-s', '--size'], 'empty': ['-e', '--empty']})

# This overrides the default value for the `symbol` parameter:
@add_options_from(display_data, custom={'symbol': {'default': '#'}})

Boolean flags

Boolean flags are supported via the bool type hint. The default behavior is to create an on-/off-option as described in the click docs. If this is undesired, it can be customized by using the names keyword parameter of add_options_from:

foo: bool = True
# translates to
click.option('--foo/--no-foo', default=True)

# Use the following to create an on-/off-option:
add_options_from(my_func, names={'foo': ['--foo']})
# translates to
click.option('--foo', is_flag=True, default=True)

Lists and tuples

click-inspect also supports sequences as type hints (e.g. list[int] or tuple[int, str]). Specifically those type hints are translated as follows:

foo: Sequence[int]
foo: List[int]
# translates to
click.option('--foo', multiple=True, type=int)

# -------------------------------------------------- #

foo: Tuple[int, str]
# translates to
click.option('--foo', type=(int, str))

These type hints are also supported as part of the docstring:

"""
Args:
    foo (list of int): Equivalent to List[int].
    foo ((int, str)): Equivalent to Tuple[int, str].
"""

Unions

click-inspect also supports typing.Union by simply selecting the first option as the type. So Union[int, str] is equivalent to int.(1) Unions are also supported as part of the docstring via int or str.

Docstring styles

click-inspect supports inspecting reST-style docstrings, as well as Google- and Numpy-style docstrings via sphinx.ext.napoleon.


(1) If the Union is part of a generic type, it is not guaranteed that the first option is the same one that is displayed in the Union literal. This is because generic types cache their __getitem__ methods. For that reason List[Union[int, str]] is List[Union[str, int]] and the selected type would be int in both cases since that's the one that got cached.

Metadata

Release files for click-inspect 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for click-inspect 0.3.2
File Size Uploaded
click-inspect-0.3.2.tar.gz 8.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for click-inspect 0.3.2
File Interpreter ABI Platform
click_inspect-0.3.2-py3-none-any.whl Python 3 none any Details

Total release size: 17.1 kB

Release files / click-inspect-0.3.2.tar.gz

Download URL click-inspect-0.3.2.tar.gz
Size 8.9 kB
Tags Source
SHA-256 checksum
How to use checksums
b8045196b1227c5c730e877f336155b6fa2e35e40183e87fd5f2310e31104e78
BLAKE2b-256 checksum
How to use checksums
15dc9717d4c3030f8fd10948efe6ddbddd22b65ba093612ec80a29c697500b8e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.0.8 CPython/3.8.6 Linux/5.4.0-1031-azure

Release files / click_inspect-0.3.2-py3-none-any.whl

Download URL click_inspect-0.3.2-py3-none-any.whl
Size 8.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
621580d980da24cbb3f83fac0da6475f8eaca11632054b7c2b277ea452dcae47
BLAKE2b-256 checksum
How to use checksums
12db8a99c671cfcb54857bbc20dd928db13634b0ec50c5024640a9514b77017e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.0.8 CPython/3.8.6 Linux/5.4.0-1031-azure

Release history Release notifications | RSS feed

This release

0.3.2 This release

2 release files

0.3.1

2 release files

0.3

2 release files

0.2

2 release files

0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page