Skip to main content

Tests PyPI - License GitHub top language PyPI - Python Version PyPI version PyPI - Wheel

befehl

befehl (german for command) is a

  • declarative
  • modular (easily reuse definitions),
  • lightweight (no external dependencies), and
  • versatile (highly customizable behavior through custom parsers and validation)
  • zero-dependency

Python library for building CLI applications.

It features

  • a modern, declarative API,
  • QoL features like short-option grouping,
  • automatic generation of help-options,
  • generation of bash-autocomplete source files, and
  • natural integration into automated tests.

Example

from befehl import Parser, Option, Argument, Command, Cli

# define subcommand
class MySubCommand(Command):
    opt = Option("--sub-option")
    arg = Argument("subarg", nargs=-1)

    def run(self, args):
        # run business logic on parsed input
        # ...

    def validate(self, args):
        # perform custom validation on parsed input
        # ...

# define base-command
class MyCli(Cli):
    cmd = MySubCommand("subcommand")

    opt0 = Option(("-o", "--option-zero"))
    opt1 = Option(("-p", "--option-one"))

    arg0 = Argument("arg", parser=Parser.parse_as_path)

    def run(self, args):
        # run business logic on parsed input
        # ...

    def validate(self, args):
        # perform custom validation on parsed input
        # ...

# validate + build entry-point
cli = MyCli("my-cli").build()

Documentation

Command declaration

One of the few limitation imposed by this library is that all CLIs have the following structure:

[command] [subcommand1 [subcommand 2 [...]]] [options] [--] [arguments]

When defining a CLI, the command-tree is built from classes inheriting from Command. A Command-class encapsulates all (immediate) subcommands (i.e., instances of previously defined Commands), Options, and Arguments as class attributes:

class MySubCommand(Command):
    ...

class MyCli(Cli):
    cmd = MySubCommand("subcommand")

    opt0 = Option(("-o", "--option-zero"))
    opt1 = Option(("-p", "--option-one"))

    arg0 = Argument("arg", parser=Parser.parse_as_path)

    ...

(Cli is an optional alias for Command)

Business logic

A Command's business logic is defined in its run method, e.g.,

class MyCli(Cli):
    ...

    def run(self, args):
        print(args)

When being executed, this function receives a singular argument args: dict[Option | Argument, list[Any]], where values are lists of parsed values.

For example, invoking the Command from above with a call like

command -o path/to/file

results in an args-mapping of

args = {
    MyCli.opt0: [],
    MyCli.arg0: [Path(path/to/file)]
}

Multiple values to a single Option or Argument keep their order in the generated list.

Validation

Optionally, before entering the business-logic, a validation-step can be defined. To this end, the empty validate method of the Command class can be overwritten. For example, the following definition validates that, if -o is given, option -p is needed as well.

class MyCli(Cli):
    ...

    def validate(self, args):
        if self.opt0 in args and self.opt1 not in args:
            return (
                False,
                f"option {self.opt0} also requires option {self.opt1}"
            )
        return True, None

Invoking the cli with command -o returns with the above message (and exit code 1) whereas command -op continues past the validation into the run-method.

Build

In order to create a callable function that can be used as an entry-point for python packages, a build-step has to be performed.

For example, for the above CLI, one can enter

...

cli = MyCli("my-cli").build(help_=True, completion=False)

The variable cli then serves as the entry-point. Suppose, this variable is in the namespace of the module cli, then it can be used with, for example, setuptools as

setup(
    ...
    entry_points={
        "console_scripts": [
            "command = cli:cli",
        ],
    },
    ...
)

When building, it has to be decided, whether

  • a help-option (-h, --help) should be generated (enabled by default)
  • an option (--generate-autocomplete) for generating a sourcable bash-autocomplete script should be added (disabled by default; enabled if environment sets _BEFEHL_COMPLETION). See this section for details.

Parsers

Both Options and Arguments accept keyword arguments for a parser. A parser is a function that accepts a single string-value and returns a tuple of

  • boolean (whether input is valid),
  • string (message in case the input is invalid), and
  • any object (parsed data in case of success).

This library provides some basic parsers to be used in Options and Arguments. These range from parsing of primitive types like boolean or integer to more involved parsers for paths/files/directories (using pathlib.Path) or requiring values to satisfy regular expressions.

Pre-defined parsers are accessible via the abstract collection class Parser and can be accessed via its static methods like

class AnotherCommand(Command):
    opt = Option("-o", parser=Parser.parse_as_int)
    arg = Argument("arg", parser=Parser.parse_with_regex(r"[0-9]+"))

    ...

Lastly, by using the methods Parser.first or Parser.chain, multiple parsers can be applied to single values.

Other features

(Short) Option grouping

This library supports Options in short and long format:

  • short corresponds to starting with single - followed by a single character
  • long Options start with -- followed by at least one character

For convenience, Options with short name and nargs=0 can be grouped. For example, when using the Command from above, the two inputs

command -o -p

and

command -op

are equivalent.

Equal-sign syntax

In order to avoid problems with Option values starting with - (could be ambiguous regarding other Options), Options can be used with the following syntax

command --delta=-1

(instead of command --delta -1 which would fail).

Separator for options and arguments

Similar to the problem described in the previous section: In order to avoid problems with Argument values starting with - (could be ambiguous regarding Options), the separator -- can be used like

command -o -- -1

(instead of command -o -1 which would fail).

Autocomplete

This library can generate shell-script files that, when sourced in bash, enable basic autocomplete functionality. The script can be built by first setting the completion-option during build and then call the CLI with the --generate-autocomplete option (will be printed to stdout).

The auto-completion can also be sourced immediately by entering

eval "$(_BEFEHL_COMPLETION= <entry-point> --generate-autocomplete)"

(replace <entry-point> with your custom entry-point).

Easy implementation of automated tests

The callable that serves as an entry-point for the cli naturally allows for simple integration into automated test suites. To run tests on a cli, simply pass the test-input to the callable like

cli = MyCli(...).build()

cli(["subcommand", "--option", "value", ...])

Using inheritance, test-specific actions can be easily injected like so

class MyTestCli(MyCli):
    # ...

    def run(self, args):
        # test specifics like assertions etc.
        # ...

        super().run(args)

        # more test specifics
        # ...

test_cli = MyTestCli(...).build()

See also the automated tests provided in this repository.

Tests

Automated tests can be run with

python3 -m unittest discover tests/

Metadata

Release files for befehl 0.1.3

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

Source distribution (sdist)

Source distribution for befehl 0.1.3
File Size Uploaded
befehl-0.1.3.tar.gz 19.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for befehl 0.1.3
File Interpreter ABI Platform
befehl-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 34.7 kB

Release files / befehl-0.1.3.tar.gz

Download URL befehl-0.1.3.tar.gz
Size 19.1 kB
Tags Source
SHA-256 checksum
How to use checksums
95c174db194e6d7704f9b5ffffa09ba47029a1e88376393242ccedd69ada5cc6
BLAKE2b-256 checksum
How to use checksums
ebfcd032b44636150334ec3ee0a90e0e106fc460f43593492732f4dadf106382
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Release files / befehl-0.1.3-py3-none-any.whl

Download URL befehl-0.1.3-py3-none-any.whl
Size 15.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0d28964ada90cee16d28b58bf9569c8d0d3f6765789d9e4c4b4dae802684f6c9
BLAKE2b-256 checksum
How to use checksums
2f84b8e511d48bcd05ed3a10b0cc88d627cf984a119cdf901f2e42f9b304fbee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.0

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