Package: coleo
ColeoColeo is a minimum-effort way to create a command-line interface in Python.Declare options where they are used.Scale easily to extensive CLIs with dozens of subcommands and options.Basic usageFirst, define a command line interface as follows:fromcoleoimportOption,auto_cli,default@auto_clidefmain():# The greetinggreeting:Option=default("Hello")# The name to greetname:Option=default("you")returnf"{greeting},{name}!"Then you may run it like this on the command line:$pythonhello.py
Hello,you!
$pythonhello.py--nameLuke
Hello,Luke!
$pythonhello.py--nameLuke--greeting"Happy birthday"Happybirthday,Luke!
$pythonhello.py-h
usage:hello.py[-h][--greetingVALUE][--nameVALUE]optionalarguments:-h,--helpshowthishelpmessageandexit--greetingVALUEThegreeting--nameVALUEThenametogreetAny variable annotated withOptionwill become an option.You can provide a default value withdefault(value), although you don't have to, if the argument is required.If there is a comment above the variable, it will be used as documentation for the option.Option typesBy default, all arguments are interpreted as strings, but you can easily give a different type to an argument:@auto_clidefmain():# This argument will be converted to an intx:Option&int# This argument will be converted to a floaty:Option&floatreturnx+yBoolean flagsIf the type is bool, the option will take no argument, for example:@auto_clidefmain():flag:Option&bool=default(False)return"yes!"ifflagelse"no!"Use it like this:$pythonscript.py--flag
yes!
$pythonscript.py
no!You can alsonegatethe flag, meaning that you want to provide an option that will store False in the variable instead of True. For example:@auto_clidefmain():# [negate]flag:Option&bool=default(True)return"yes!"ifflagelse"no!"By default, the above will create a flag called--no-<optname>:$pythonscript.py
yes!
$pythonscript.py--no-flag
no!You can write[negate: --xyz -n]if you want the option to be--xyzor-n. This overrides the default--no-flagoption.Note that using[negate]will remove--flag, because we assume that it is True by default and there is therefore no need for this option.If you wish, you can have both options that set the flag to True and others that set the flag to False, using[false-options]. You can optionally document these options with[false-options-doc](if not provided, Coleo will use a sensible default):@auto_clidefmain():# Set the flag to True# [options: -y]# [false-options: -n]# [false-options-doc: Set the flag to False]flag:Option&bool=default(None)returnflag$pythonscript.py
None
$pythonscript.py-y
True
$pythonscript.py-n
FalseFilesUsecoleo.FileType(orargparse.FileType, it's the same thing) to open a file to read from or to write to:@auto_clidefmain():grocery_list:Option&coleo.FileType("r")withgrocery_listasf:forfoodinf.readlines():print(f"Gotta buy some{food}")ConfigYou can manipulate configuration files withcoleo.configorcoleo.ConfigFile:@auto_clidefmain():# ConfigFile lets you read or write a configuration filebook:Option&ConfigFilecontents=book.read()contents["xyz"]="abc"book.write(contents)# config will read the file for you or parse the argument as JSONmagazine:Option&configprint(magazine)Use it simply like this:$pythonlibrarian.py--bookalice.json--magazinevogue.json
$pythonlibrarian.py--bookhistory.yaml--magazinegamez.toml
$pythonlibrarian.py--bookphysics.json--magazine'{"a": 1, "b": 2}'# etcSupported extensions arejson,yamlandtoml(the latter two require installing thepyyamlortomlpackages).OtherAny function can be used as a "type" for an argument. So for example, if you want to be able to provide lists and dictionaries on the command line you can simply usejson.loads(althoughcoleo.configis usually better, because it can also read files, in various formats):@auto_clidefmain():obj:Option&json.loadsreturntype(obj).__name__$pythonjson.py--obj1int
$pythonjson.py--obj'"hello"'str
$pythonjson.py--obj'{"a": 1, "b": 2}'dictIf you're feeling super feisty and care nothing about safety, you can even useeval:@auto_clidefmain():obj:Option&evalreturntype(obj).__name__$pythoneval.py--obj"1 + 2"int
$pythoneval.py--obj"lambda x: x + 1"functionCustomizationUsing comments of the form# [<instruction>: <args ...>]you can customize the option parser:@auto_clidefmain():# This argument can be given as either --greeting or -g# [alias: -g]greeting:Option=default("Hello")# This argument is positional# [positional]name:Option=default("you")# This argument can only be given as -n# [options: -n]ntimes:Option&int=default(1)foriinrange(ntimes):print(f"{greeting},{name}!")The above would be used like this:$pythonhello.pyAlice-gGreetings-n2Greetings,Alice!
Greetings,Alice!The following customizations are available:[alias: ...]defines one or several options that are aliases for the main one. Options are separated by spaces, commas or semicolons.[options: ...]defines one or several options for this argument, whichoverridethe default one. Options are separated by spaces, commas or semicolons.[positional]defines one positional argument.[positional: n]: n positional arguments (a list is returned).[positional: ?]: one optional positional argument[positional: *]: zero or more positional arguments[positional: +]: one or more positional arguments[remainder]represents all arguments that are not matched by the argument parser[nargs: n]declares that the option takes n arguments[nargs: ?]: one optional argument[nargs: *]: zero or more arguments[nargs: +]: one or more arguments[nargs: **]or[nargs: --]: all remaining arguments, including --args[action: <action>]customizes the action to perform[action: append]lets you use an option multiple times, accumulating the results in a list (e.g.python app.py -a 1 -a 2 -a 3, would put[1, 2, 3]ina)[metavar: varname]changes the variable name right after the option in the help string, e.g.--opt METAVAR[group: groupname]puts the option in a named group. Options in the same group will appear together in the help.Forbooloptions only:[negate: ...]changes the option so that it sets the variable to False instead of True when they are given. Space/comma aliases may be provided for the option, otherwise the flag will be named--no-<optname>.[false-options: ]provide a list of options that set the flag to False.[false-options-doc: ]provide a documentation for the options given using the previous statement.SubcommandsYou can create an interface with a hierarchy of subcommands by decorating a class withauto_cli:@auto_cliclassmain:classcalc:defadd():x:Option&inty:Option&intreturnx+ydefmul():x:Option&inty:Option&intreturnx*ydefpow():base:Option&intexponent:Option&intreturnbase**exponentdefgreet():greeting:Option=default("Hello")name:Option=default("you")returnf"{greeting},{name}!"The class only holds structure and will never be instantiated, so don't addselfto the argument lists for these functions.Then you may use it like this:$pythonmulti.pygreet--nameAlice--greetingHi
Hi,Alice!
$pythonmulti.pycalcadd--x=3--y=811Sharing argumentsIt is possible to share behavior and arguments between subcommands, or to split complex functionality into multiple pieces. For example, maybe multiple subcommands in your application require an API key, which can either be given on the command line or can be read from a file. This is how you would share this behavior across all subcommands:fromcoleoimportOption,auto_cli,config,default,tooled@tooleddefapikey():# The API key to usekey:Option=default(None)ifkeyisNone:# If no key parameter is given on the command line, try to read it from# some standard location.key=config("~/.config/myapp/config.json")["key"]returnkey@auto_cliclassmain:defsearch():interface=Application(apikey())query:Optionreturninterface.search(query)definstall():interface=Application(apikey())package:Optionreturninterface.install(package)If a function is decorated with@tooledand is called from one of the main functions (or from another tooled function), Coleo will search for arguments in that function too. Thus any subcommand that callsapikey()will gain a--keyoption.In addition to this, you can "share" arguments by defining the same argument with the same type in multiple functions. Coleo will set all of them to the same value.For example, in the example above you could easily let the user specify the path to the file that contains the key, simply by replacingkey=config("~/.config/myapp/config.json")["key"]# ==>config_path:Option=default("~/.config/myapp/config.json")key=config(config_path)["key"]And thatconfig_pathargument could, of course, be declared in any other function that needs to read some configuration value.run_clifromcoleoimportOption,auto_cli@auto_clidefmain():x:OptionreturnxIs equivalent to:fromcoleoimportOption,run_cli,tooled@tooleddefmain():x:Optionreturnxresult=run_cli(main)ifresultisnotNone:print(result)Non-CLI usageIt is possible to set arguments withoutauto_cliusingsetvars:fromcoleoimportOption,setvars,tooled@tooleddefgreet():greeting:Option=default("Hello")name:Option=default("you")returnf"{greeting}{name}!"withsetvars(greeting="Hi",name="Bob"):assertgreet()=="Hi bob!"Note:Withsetvars, youmustdecorate the function with@tooled(this is somethingauto_clidoes on your behalf).setvarsentirely bypasses the option parsing and the type annotations will not be used to wrap these values. In other words, if a variable is annotatedOption & intand you provide the value "1", it will remain a string.Using with PteraColeo is based onPteraand all of Ptera's functionality is de facto available on functions marked as@tooled. For example, using the example above:# Set the variables in the greet function -- it's a bit like making an objecthibob=greet.new(greeting="Hi",name="Bob")asserthibob()=="Hi Bob!"# Same as above but this would also change greeting/name in any other function# that is called by greet, and so on recursively (a bit like dynamic scoping)hibob=greet.tweaking({"greeting":"Hi","name":"Bob"})asserthibob()=="Hi Bob!"# More complex behaviorfrompteraimportoverlaywithoverlay.tweaking({"greet(greeting='Bonjour') > name":"Toto"}):assertgreet()=="Hello you!"assertgreet.new(greeting="Hi")()=="Hi you!"assertgreet.new(greeting="Bonjour")()=="Bonjour toto!"Read the documentation forPterafor more information. Note that Ptera is not limited to variables taggedOption, it can manipulateanyvariable in a tooled function.