cappa 0.32.2


pip install cappa

  Latest version

Released: Jul 27, 2026

Project Links

Meta
Author: Dan Cardin
Requires Python: <4,>=3.8

Classifiers

Topic
  • Software Development :: Libraries :: Python Modules

Cappa

Actions Status codecov Documentation Status

Cappa is a declarative command line parsing library, which uses runtime type inspection to infer (default) CLI argument behavior, and provide automatic help text generation and dynamic completion generation.

It supports two different modes of execution:

  • parse: Argparse-like parsing of the arguments into an output structure

  • invoke: Click-like calling of functions based on the selected subcommand

    It also provides a dependency injection system for providing non-argument resources to the invoked commands.

And, a number of different styles of CLI declaration (which can be mixed and matched within a given CLI):

  • Classes: The fields of the class correspond to CLI arguments/subcommands
  • Functions: The arguments of the function correspond to CLI arguments
  • Methods: The class fields correspond to CLI arguments, and the methods correspond to subcommands
  • Imperative Construction: The CLI structure can be manually/imperitavely constructed, rather than being inferred from the input structure

Class Based, parse

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Literal
from typing_extensions import Annotated
import cappa

@dataclass
class Example:
    # Normal types are by default inferred as positional arguments
    positional: str = "optional"

    # Except boolean types, which are inferred as flags
    flag: bool = False

    # Option that only accepts the long name
    option: Annotated[int | None, cappa.Arg(long=True, help="A number")] = None

    # Option accepted many times
    many_options: Annotated[
        list[Literal["one", "two", "three"]], cappa.Arg(short=True)
    ] = field(default_factory=list)

    # Single option, accepting many values
    three_values: Annotated[tuple[str, str, int] | None, cappa.Arg(long=True)] = None

args = cappa.parse(Example, backend=cappa.backend)
print(args)

Produces the following CLI:

help text

With examples of its usage like:

$ python example.py
Example(positional='optional', flag=False, option=None, many_options=[], three_values=None)

$ python example.py foo
Example(positional='foo', flag=False, option=None, many_options=[], three_values=None)

$ python example.py --flag
Example(positional='optional', flag=True, option=None, many_options=[], three_values=None)

$ python example.py --option 4
Example(positional="optional", flag=False, option=4, many_options=[], three_values=None)

$ python example.py -m one -m three
Example(positional='optional', flag=False, option=None, many_options=['one', 'three'], three_values=None)

$ python example.py --three-values a b 4
Example(positional='optional', flag=False, option=None, many_options=[], three_values=('a', 'b', 4))

In this way, you can turn any dataclass-like object (with some additional annotations, depending on what you're looking for) into a CLI.

You'll note that cappa.parse returns an instance of the class. This API should feel very familiar to argparse, except that you get the fully typed dataclass instance back instead of a raw Namespace.

Class Based, invoke

"invoke" documentation

The "invoke" API is meant to feel more like the experience you get when using click or typer. You can take the same dataclass, but register a function to be called on successful parsing of the command.

from dataclasses import dataclass
import cappa
from typing_extensions import Annotated

def function(example: Example):
    print(example)

@cappa.command(invoke=function)
class Example:  # identical to original class
    positional_arg: str
    boolean_flag: bool
    single_option: Annotated[int | None, cappa.Arg(long=True)]
    multiple_option: Annotated[list[str], cappa.Arg(short=True)]


cappa.invoke(Example)

(Note the lack of the dataclass decorator. You can optionally omit or include it, and it will be automatically inferred).

Alternatively you can make your dataclass callable, as a shorthand for an explicit invoke function:

@dataclass
class Example:
    ...   # identical to original class

    def __call__(self):
       print(self)

Note invoke=function can either be a reference to some callable, or a string module-reference to a function (which will get lazily imported and invoked).

Subcommands

With a single top-level command, the click-like API isn't particularly valuable by comparison. Click's command-centric API is primarily useful when composing a number of nested subcommands, and dispatching to functions based on the selected subcommand.

from __future__ import annotations
from dataclasses import dataclass
import cappa

@dataclass
class Example:
    cmd: cappa.Subcommands[Print | Fail]


@dataclass
class Print:
    loudly: bool

    def __call__(self):  # again, __call__ is shorthand for the above explicit `invoke=` form.
        if self.loudly:
            print("PRINTING!")
        else:
            print("printing!")

def fail():
    raise cappa.Exit(code=self.code)

@cappa.command(invoke=fail)
class Fail:
    code: int

cappa.invoke(Example)

Functions, invoke

Purely function-based CLIs can reduce the ceremony required to define a given CLI command. Such a CLI is exactly equivalent to a CLI defined as a dataclass with the function's arguments as the dataclass's fields.

import cappa
from typing_extensions import Annotated

def function(foo: int, bar: bool, option: Annotated[str, cappa.Arg(long=True)] = "opt"):
    ...


cappa.invoke(function)

There are, however, some downsides to using functions. Namely, that function has no nameable type! As such, a free function can not be easily named as a subcommand option (Subcommand[Foo | Bar]).

You can define a root level function with class-based subcommands, but the reverse is not possible because there is no valid type you can supply in the subcommand union.

Methods, invoke

See also Methods.

from __future__ import annotations
from dataclasses import dataclass
import cappa

@cappa.command
@dataclass
class Example:
    arg: int

    @cappa.command
    def add(self, other: int) -> int:
        """Add two numbers."""
        return self.arg + some_dep

    @cappa.command(help="Subtract two numbers")
    def subtract(self, other: int) -> int:
        return self.arg - other

cappa.invoke(Example)

With methods, the enclosing class corresponds to the parent object CLI arguments, exactly like normal class based definition. Unlike with free functions, (explicitly annotated) methods are able to act as subcommands, who's arguments (similarly to free functions) act as the arguments for the subcommand.

The above example produces a CLI like:

Usage: example ARG {add,subtract} [-h] [--completion COMPLETION]

Arguments
  ARG

Subcommands
  add                        Add two numbers.
  subtract                   Subtract two numbers.

Imperative Construction, parse/invoke

See also Manual Construction.

from dataclasses import dataclass

import cappa

@dataclass
class Foo:
    bar: str
    baz: list[int]

command = cappa.Command(
    Foo,
    arguments=[
        cappa.Arg(field_name="bar"),
        cappa.Arg(field_name="baz", num_args=2),
    ],
    help="Short help.",
    description="Long description.",
)

result = cappa.parse(command, argv=["one", "2", "3"])

All other APIs of cappa amount to scanning the provided input structure, and producing a cappa.Command structure. As such, it's equally possible for users to manually construct the commands themselves.

This could also be used to extend cappa, or design even more alternative interfaces (Cleo is another, fairly different, option that comes to mind).

Inspirations

Credit where credit is due

  • The "Derive" API of the Rust library Clap directly inspired the concept of mapping a type's fields to the shape of the CLI, by inferring the default behavior from introspecting types.

  • Click's easy way of defining large graphs of subcommands and mapping them to functions, inspired the the "invoke" API of Cappa. The actual APIs dont particularly resemble one another, but subcommands directly triggering functions (in contrast to argparse/Clap) is a very nice, and natural seeming feature!

  • FastAPI's Depends system inspired Cappa's dependency injection system. This API is quite natural, and makes it very easy to define a complex system of ad-hoc dependencies without the upfront wiring cost of most DI frameworks.

0.32.2 Jul 27, 2026
0.32.1 Jun 09, 2026
0.32.0 Jun 08, 2026
0.31.0 Dec 04, 2025
0.30.4 Oct 30, 2025
0.30.2 Sep 08, 2025
0.30.1 Sep 03, 2025
0.30.0 Aug 11, 2025
0.29.1 Jul 31, 2025
0.29.0 Jul 23, 2025
0.28.1 Jul 08, 2025
0.28.0 May 30, 2025
0.27.3 May 23, 2025
0.27.2 Apr 09, 2025
0.27.1 Apr 07, 2025
0.26.6 Mar 03, 2025
0.26.5 Feb 07, 2025
0.26.4 Jan 17, 2025
0.26.3 Jan 15, 2025
0.26.2 Jan 14, 2025
0.26.1 Dec 24, 2024
0.26.0 Nov 27, 2024
0.25.1 Nov 20, 2024
0.25.0 Nov 13, 2024
0.24.3 Nov 12, 2024
0.24.2 Nov 12, 2024
0.24.1 Nov 07, 2024
0.24.0 Oct 24, 2024
0.23.0 Oct 03, 2024
0.22.5 Sep 17, 2024
0.22.4 Aug 10, 2024
0.22.2 Aug 03, 2024
0.22.1 Jun 27, 2024
0.22.0 Jun 26, 2024
0.21.2 Jun 23, 2024
0.21.1 Jun 20, 2024
0.21.0 Jun 20, 2024
0.20.1 Jun 12, 2024
0.19.1 May 10, 2024
0.19.0 Apr 29, 2024
0.18.1 Apr 11, 2024
0.18.0 Mar 27, 2024
0.17.3 Mar 15, 2024
0.17.2 Mar 11, 2024
0.17.1 Mar 07, 2024
0.17.0 Mar 03, 2024
0.16.6 Mar 01, 2024
0.16.5 Mar 01, 2024
0.16.4 Feb 28, 2024
0.16.3 Feb 27, 2024
0.16.2 Feb 22, 2024
0.16.1 Feb 10, 2024
0.16.0 Feb 08, 2024
0.15.4 Jan 17, 2024
0.15.3 Jan 17, 2024
0.15.2 Dec 21, 2023
0.15.1 Dec 19, 2023
0.15.0 Dec 19, 2023
0.14.3 Dec 15, 2023
0.14.2 Dec 11, 2023
0.14.1 Dec 08, 2023
0.14.0 Dec 08, 2023
0.13.2 Nov 22, 2023
0.13.1 Nov 07, 2023
0.13.0 Nov 07, 2023
0.12.1 Nov 06, 2023
0.12.0 Nov 05, 2023
0.11.6 Nov 03, 2023
0.11.5 Oct 31, 2023
0.11.4 Oct 30, 2023
0.11.3 Oct 29, 2023
0.11.2 Oct 28, 2023
0.11.1 Oct 27, 2023
0.11.0 Oct 25, 2023
0.10.2 Oct 24, 2023
0.10.1 Oct 24, 2023
0.10.0 Oct 24, 2023
0.9.3 Oct 23, 2023
0.9.2 Oct 23, 2023
0.9.0 Oct 23, 2023
0.8.7 Oct 21, 2023
0.8.6 Oct 19, 2023
0.8.4 Oct 19, 2023
0.8.3 Oct 19, 2023
0.8.2 Oct 19, 2023
0.8.1 Oct 18, 2023
0.8.0 Oct 18, 2023
0.7.1 Oct 18, 2023
0.7.0 Oct 09, 2023
0.6.0 Sep 29, 2023
0.5.0 Sep 28, 2023
0.4.0 Sep 25, 2023
0.3.0 Sep 25, 2023
0.2.2 Sep 24, 2023
0.2.1 Sep 22, 2023
0.2.0 Sep 22, 2023
0.1.0 Sep 20, 2023

Wheel compatibility matrix

Platform Python 3
any

Files in release

Extras:
Dependencies:
rich (>=12.1.0)
type-lens (>=0.2.5)
typing-extensions (>=4.12.0)
typing-extensions (>=4.8.0)