Package: cappa
CappaFull documentation here.Comparison vs existing libraries..Annotation inference details"invoke" (click-like) detailsCappa is a declarative command line parsing library, taking much of its
inspiration from the "Derive" API from theClapwritten in Rust.fromdataclassesimportdataclass,fieldimportcappafromtypingimportLiteralfromtyping_extensionsimportAnnotated@dataclassclassExample:positional_arg:str="optional"boolean_flag:bool=Falsesingle_option:Annotated[int|None,cappa.Arg(short=True,help="A number")]=Nonemultiple_option:Annotated[list[Literal["one","two","three"]],cappa.Arg(long=True,help="Pick one!"),]=field(default_factory=list)args:Example=cappa.parse(Example,backend=cappa.backend)print(args)Produces the following CLI: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 thatcappa.parsereturns an instance of the class. This API should
feel very familiar toargparse, except that you get the fully typed dataclass
instance back instead of a rawNamespace.Invoke"invoke" documentationThe "invoke" API is meant to feel more like the experience you get when usingclickortyper. You can take the same dataclass, but register a function to
be called on successful parsing of the command.fromdataclassesimportdataclassimportcappafromtyping_extensionsimportAnnotateddeffunction(example:Example):print(example)@cappa.command(invoke=function)classExample:# identical to original classpositional_arg:strboolean_flag:boolsingle_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
explcit invoke function:@dataclassclassExample:...# identical to original classdef__call__(self):print(self)Noteinvoke=functioncan either be a reference to some callable, or a string
module-reference to a function (which will get lazily imported and invoked).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.SubcommandsThe useful aspect of click's functional composability is that you can define
some number of subcommands functions under a parent command, whichever
subcommand the function targets will be invoked.importclick@click.group('example')defexample():...@example.command("print")@click.option('--loudly',is_flag=True)defprint_cmd(loudly):ifloudly:print("PRINTING!")else:print("printing!")@example.command("fail")@click.option('--code',type:int)deffail_cmd(code):raiseclick.Exit(code=code)# Called like:# /example.py print# /example.py failWhereas with argparse, you'd have had to manually match and call the funcitons
yourself. This API does all of the hard parts of deciding which function to
call.Similarly, you can achieve the same thing with cappa.from__future__importannotationsfromdataclassesimportdataclassimportcappa@dataclassclassExample:cmd:cappa.Subcommands[Print|Fail]defprint_cmd(print:Print):ifprint.loudly:print("PRINTING!")else:print("printing!")@cappa.command(invoke=print_cmd)classPrint:loudly:bool@dataclassclassFail:code:intdef__call__(self):# again, __call__ is shorthand for the above explicit `invoke=` form.raisecappa.Exit(code=code)cappa.invoke(Example)Function-based CommandsPurely functions-based can only be used for certain kinds of CLI interfaces.
However, theycanreduce the ceremony required to define a given CLI
command.importcappafromtyping_extensionsimportAnnotateddeffunction(foo:int,bar:bool,option:Annotated[str,cappa.Arg(long=True)]="opt"):...cappa.invoke(function)Such a CLI is exactly equivalent to a CLI defined as a dataclass with the
function's arguments as the dataclass's fields.There are various downsides to using functions. Given that there is no class to
reference, any feature which relies on being able to name the type will be
impossible to use. For example, subcommands cannot be naturally defined as
functions (since there is no type with which to reference the subcommand).