Skip to main content

Turns your Python script or module into an application with decent CLI.

Project description

Turns your Python script or module into an application with decent CLI.

Motivation

Kickoff is inspired by utilities like invoke, fire, runfile. It has similar function with this difference that it unterstands Python3 syntax, therefore doesn’t need from you to use decorators or any dedicated API. This way kickoff can be applied to pretty much any script with zero overhead.

Kickoff is built on top of stunning click module. This lets you build beautiful CLI in seconds without making any effort.

Installation

As typically:

pip install kickoff

Quick Start

Let’s say you have this simple hello.py script:

"""Very simple application"""

def greet(name="World", *, greeting="Hello"):
    """Say hello"""

    print(f"{greeting} {name}!")

All you need to do is invoking you script with kickoff command. Kickoff will execute the script and examine top level namespace to collect your functions and then turn them into commands:

$ kickoff hello.py
Usage: hello.py [OPTIONS] COMMAND [ARGS]...

  Very simple application

Options:
  --help  Show this message and exit.

Commands:
  greet  Say hello
$ kickoff hello.py greet --help
Usage: hello.py greet [OPTIONS] [NAME]

  Say hello

Options:
  --greeting TEXT  [default: Hello]
  --help           Show this message and exit.
$ kickoff hello.py greet
Hello World!
$ kickoff hello.py greet John --greeting Hi
Hi John!

The same can be achieved by using module name and location. For intstance this is how you can test findall function from re module:

$ kickoff :re findall "b\w*d" "beer bear bird bore beard"
['bird', 'beard']

Invoke kickoff without any parameters to get more explanations.

Usage

Basics

There is very simmilar logic behind how Python3 handles arguments of the callables and how typical CLI does. This fact makes kickoff able to translate signatures of your functions into corresponding CLI commands. Docstrings in the code can be used for providing descriptions. As an example, see ex01_simple. Table below summarizes relationship explained above.

Function Argument

Command Line

None

None

foobar()

foobar

Argument without default value

Required parameter

foobar(qux)

foobar <QUX>

Argument with default value

Optional parameter

foobar(qux=123)

foobar [<QUX>]

Argument with default value of boolean type

Flag

foobar(*, qux=False)

foobar [--qux]

Keyword-only argument without default value

Required option

foobar(*, qux)

foobar --qux <QUX>

Keyword-only argument with default value

Optional option

foobar(*, qux=123)

foobar [--qux <QUX>]

Non-keyworded variable-length argument list

Multi-parameter

foobar(*args)

foobar <ARGS> ...

Keyworded variable-length argument list

Ignored

foobar(**kwargs)

foobar

In addition, annotations can be used to specify details which cannot be distinguised from Python syntax. See ex02_args_and_opts. Table below shows couple of practical examples.

Function Argument

Command Line

“required” speifier & variable-length argument list

Optional multi-parameter

foobar(*qux: dict(required=False))

foobar [<ARGS> ...]

“multiple” specifier

Multi-option

foobar(*, qux: dict(multiple=True)

foobar --qux <QUX> ...

“multiple” & “required” specifiers

Optional multi-option

foobar(*, qux: dict(multiple=True, required=False))

foobar [--qux <QUX> ...]

“count” specifier

Counting flag

foobar(*, qux: dict(count=True) )

foobar --qux ...

“count” specifier & default value

Optional counting flag

foobar(*, qux: dict(count=True) =0 )

foobar [--qux ...]

Hierarhical Desigh

Kickoff recursively traverses across the module of your choice to find functions and classes. Functions become commands. Classes are interpreted as command groups. This way you can arrange your commands in a hierarhical structure as presented in ex03_command_groups.

By default only those functions which have their body defined in in given module can become commands. This prevents form exposing of utility functions that the script may import from other modules for internal use. Also functions and classes names of which start with underscore are ignored.

Despite of this fact, it is possible to create a desing which is divided into multiple files. To accept external code in the top level module, relevanot option must be explicitly set. This topic will be covered further. For now, here is an example of what we have discussed: ex04_distributed_design.

Following table explains the details on how kickoff translates elements of Python AST into components of click module.

Python AST

Click

Function or static method

Command

Function argument

Parameter or option

Class

Command group

Return value annotation

Command settings

Argument annotation

Parameter/option settings

Function docstring

Command description

Class docstring

Command group description

Module docstring

Application description

Annotations are expected to be dictionaries. Values of argument annotations, depending of the context, are used as arguments to either click.Option or click.Argument class. Additionally alias can be used to specify short name of an option.

Values of return annotation are used as arguments to click.Command class.

Customization

Kickoff provides a way of fine tuning specific settings through kickoff.config data structure. It is recommended to do this in a code which is conditionally unavailable. This is how we can keep the module reusable in environments where kickoff is not installed. For example:

if __name__ == "__kickoff__":
    import kickoff
    kickoff.config.prog_name = "demo"
    kickoff.config.version_option = dict(version='1.2.3')
    kickoff.config.help_option_names = ['-h', '--help']

Available options can be found in the table:

Option

Default Value

Description

accept_imported

False

Inspect entire content of given module, not only functions and classes defined locally.

scan_recursively

True

Inspect classes (allows for grouping commands).

result_file

sys.stderr

Where to print stringified values returned by commands. Use /dev/null if you want to suppress this.

black_list

[]

Functions and classes to be explicitely skipped in the inspection process.

error_handler

kickoff.default_error_handler

Function to be applied to the exceptions of expected_error_cls type when raised.

expected_error_cls

kickoff.ExpectedError

Type of the errors to be presented without traceback.

usage_error_cls

click.UsageError

Type of the errors to be presented with context help.

prog_name

None

Name of the application to be used in context help.

version_option

None

Dictionary to be unpacked to the arguments of click.version_option.

help_option_names

None

Dictionary to be unpacked to the arguments of click.help_option.

Corresponding example: ex05_customization.

Error Handling

By default, exceptions raised from the commands are not handled by kickoff. Typically this results in a traceback printed to stderr. In order to hide this from the eyes of the users, we may raise exceptions that inherit from kickoff.ExpectedError, or any other class registered in config.expected_error_cls. This kind of exceptions will be stringified and handled by kickoff.default_error_handler. By default this handler writes the error message to stderr. Traceback is not shown, unless KICKOFF_DEV_MODE environment variable is set to non-zero.

Even though click provides ways of validating users’ input, one may want to indicate improper input only in the code of his command. This can be achieved by raising an exceptions which inherits from click.UsageError or any other class registered in config.usage_error_cls. As a result, we get our exception stringified next to the context help produced by click.

Following example demonstrates functionality described above: ex06_error_handling.

References

Tips

  • ptrepl or similar tools can be used to provide your CLI in a form of REPL. For example: ptrepl --prompt demo "kickoff demo.py"

  • kickoff used in shebang will let the users run your script as any other executable binary. Remember to add executable attribute: chmod +x somescript.py.

  • Using .py extension of your script is not required.

  • Set KICKOFF_DEV_MODE=1 environment variable to force all the traceback appear on stderr.

  • kickoffcustomize.py file is loaded at the very beginning. Crease this file in your CWD if you need to perform any early configuration. Example can be found here.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kickoff-0.1.0.tar.gz (15.5 kB view hashes)

Uploaded Source

Built Distributions

kickoff-0.1.0-py3.7.egg (24.9 kB view hashes)

Uploaded Source

kickoff-0.1.0-py3-none-any.whl (13.3 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page