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


Release history Release notifications

Download files

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

Filename, size & hash SHA256 hash help File type Python version Upload date
kickoff-0.1.0-py3.7.egg (24.9 kB) Copy SHA256 hash SHA256 Egg 3.7
kickoff-0.1.0-py3-none-any.whl (13.3 kB) Copy SHA256 hash SHA256 Wheel py3
kickoff-0.1.0.tar.gz (15.5 kB) Copy SHA256 hash SHA256 Source None

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN SignalFx SignalFx Supporter DigiCert DigiCert EV certificate StatusPage StatusPage Status page