Package: py-dispatch
dispatchA low information-redundancy CLI framework for a quick and dirty way of converting python a script to a command line tool.The idea behind this framework is that functions and classes have one-to-one correspondence to command line interfaces with functions as commands and arguments as flags. This framework allows users to define a function or a class in plain python and turn it into a CLI with one decorator.This was inspired byfireandclick.Installpip install py-dispatchDemo# example.pyimportsysfromdispatchimportcommand@commanddefhello(name:str,verbose:bool,debug:bool,file:str='stdout'):'''Run the 'hello' command line interface.:v verbose: Run the command verbosly:name: Name of the person you are saying hello to.:file: Either stdout or stderr'''ifdebug:print(f'debugging with{name}',file=getattr(sys,file))else:print(f'hello,{name}',file=getattr(sys,file))if__name__=='__main__':hello()$ python example.py --helpRun the 'hello' command line interface.

Usage:
    hello [options]

Options:
        --name      Name of the person you are saying hello to.
    -v, --verbose   Run the command verbosly
        --debug
        --file      Either stdout or stderr (default: 'stdout')
    -h, --help      Get help.ArgumentsArguments can be retrieved in two ways, either from Command.args or with positional only arguments. When a cli function is run, it is replaced with a Command object so the cli function can use the command in it's own body.@dispatch.commanddefcli(verbose:bool):print(cli.args)Running this cli withpython cli.py hello --verbose these are some argswill result in['hello', 'these', 'are', 'some', 'args'].The Other way to get arguments is to give the cli function a positional only argument at the beginning of the parameters list.@dispatch.commanddefcli(*args,verbose:bool):print(args)Running this cli as before will have the same result. However, it only works when the args tuple is the first function parameter.Properties of FlagsBecause flags are specified by function arguments, the properties of flags are a little bit weird.Boolean FlagsAll boolean flags have a default ofFalse.A positional argument with no default and no type annotation is assumed to be a boolean flag and will default to a value ofFalse.@disptch.commanddefcli(verbose):ifverbose:print('the verbose flag has been given')else:print('using default of False for verbose')Flag TypesDispatch uses type annotations to infer flag types and will use those annotations to convert the arguments given.@dispatch.commanddefcli(num:complex,decimal:float):passWhen the programcli.pyis executed it will convert each argument to its type.pythoncli.py--num=5+3j--decimal=5.9For this command, the parser internals will eventually callcomplex('5+3j')andfloat('5.9')before giving the values as function arguments.
What this means is that you can use any type as long it has an__init__function that takes one argument. If a flag is given a default value and no type annotation, the flag will inherit whatever type is given as default.Default Values@dispatch.command(hidden_defaults={'one','two'})defcli(one=1,two=2,other_stuff=None):passTo specify a default value, simply set the function argument as you would with regular python code. The default values will be displayed in the help view of the command unless asetof typestris passed to the decorator as the 'hidden_defaults' argument. This will hide any default values from the help message. Falsy defaults that are non-boolean like an empty string will also be hidden.Multiple CommandsFor more complicated command line interfaces, you need more than just flags. To do this you simply use the samecommanddecorator on a class instead of a function.# multicommand.pyfromdispatchimportcommand@commandclassmulticommand:''':v verbose: print stuff verbosly'''verbose:boolfilename='README.md'defcat(self,file:str):'''Print a file:f file: print this file'''ifself.verbose:print('doing the thing verbosly')print(open(file,'r').read())defdo(self,thing):'''Do a thing:thing: the thing you will be doing'''print('doing',thing)def_helper(self):'''functions that start with an underscore are notinterpreted as commands (not even hidden commands).'''passif__name__=="__main__":multicommand()This small program is used just like any other cli.pythonmulticommand.py--helpUsage:
    multicommand [options] [command]

Commands:
    cat   Print a file
    do    Do a thing

Options:
        --filename   default: 'README.md'
    -v, --verbose    print stuff verbosly
    -h, --help       Get help.Group FlagsYou can define flags that are used at a class level and can be accessed by all commands in the cli. To do this you can set a class variable or you can use a type annotation if you do not want the flag to have a default value. When retrieving the value of these flags you can simply use them like any class attribute withself.attr_name.@dispatch.commandclasscli:verbose:boolfilename='notes.txt'Command AliasesAliasing commands is really simple. All you need to do is assign the function you want to alias to another variable inside the class.@dispatch.commandclasscli:defcommand(self):'''This is a command.'''passcmd=commandUsage:
    cli [options] [command]

Options:
    -h, --help   Get help.

Commands:
    command, cmd   This is a command.