Package: cmdi
cmdi - Command InterfaceDescriptionA decorator@commandthat applies a special interface called theCommand Interfaceto its decorated function. Initially written for thebuildlib.TheCommand Interfaceallows you to control the execution of a function via theCommand Interface:It allows you to save/redirect/mute output streams (stdout/stderr) for its decorated function. This works on file descriptor level. It's possible to redirect output of subprocesses and C code as well.It allows you to catch exceptions for its decorated function and return them with theCmdResult(), includingreturn codes,error messagesand coloredstatus messages.It allows you to print status messages and summaries for a command at runtime.And more...A function that is decorated with@commandcan receive a set of special keyword arguments (_verbose=...,_stdout=...,_stderr=...,catch_err=..., etc.) and it returns aCmdResult()object.RequirementsPython>= 3.9Installpip install cmdiUsageThe@commanddecoratorUse the@commanddecorator to apply thecommand interfaceto a function.fromcmdiimportcommand@commanddeffoo_cmd(x,**cmdargs)->int:print(x)returnx*2Now you can usefoo_cmdas acommand:result=foo_cmd(10)Which will print the following output (in color):Cmd: foo_cmd
------------
10
foo_cmd: Okand return aCmdResultobject:CmdResult(val=20,code=0,name='foo_cmd',status=Status.ok,color=StatusColor.green,stdout=None,stderr=None)Command Function ArgumentsYou can define the behavior of a command function using a set of special keyword arguments that are applied to the decorated function.In this example we redirect the output offoo_cmdto an in-memory file writer and catch exceptions. The output and information of the exception are then returned with theCmdResult()object:fromcmdiimportCmdResult,Piperesult=foo_cmd(10,_stdout=Pipe(),_catch_err=True)isinstance(result,CmdResult)# Trueprint(result.stdout)# prints caught output.More about special keyword arguments can be found in the API documentation below.Customizing the Result of a command functionA command always returns aCmdResultobject, for which the@commandwrapper function automatically sets the values for the fields (code,status,color, ...), which is good enough in many situations. But sometimes you need fine grained control over the return values, e.g. to create function specific return codes. In these situations you can explicitly return aCmdResultobject with some or all of the fields set by yourself:@commanddeffoo_cmd(x:str,**cmdargs)->CmdResult:print(x)somestr="foo"+xifx=="bar":code=0else:code=42# Return a customized Command Result:returnCmdResult(val=somestr,code=code,)Note:In the example above, we return a customizedCmdResultfor which we only customize the fieldsvalandcode. You can customize every field of theCmdResultobject (optionally). The fields you leave out are set automatically.Command Interface Function WrappersSometimes you want to use theCommand Interfacefor an existing function, without touching the function definition. You can do so by creating aCommand Interface Function Wrapper:fromcmdiimportcommand,strip_cmdargs,CmdResult# This function wraps the Command Interface around an existing function:@commanddeffoo_cmd(x,**cmdargs)->CmdResult:returnfoo(**strip_cmdargs(loclas()))# The original function that is being wrapped:deffoo(x)->int:print(x)returnx*2Command Interface Function Wrappers andsubprocessreturn codes.If you need to create aCommand Interface Function Wrapperfor an existing function that runs asubprocessand your command depends on thereturncodeof that, you can use thesubprocess.CalledProcessErrorexception to compose something. E.g.:importsubprocessasspfromcmdiimportcommand,CmdResult,Status@commanddeffoo_cmd(x,**cmdargs)->CmdResult:try:returnfoo(**strip_cmdargs(locals()))exceptsp.CalledProcessErrorase:ife.returncode==13:returnCmdResult(code=e.returncode,status=Status.ok,)elife.returncode==42:returnCmdResult(code=e.returncode,status=Status.skip,)else:raisesp.CalledProcessError(e.returncode,e.args)deffoo(x)->int:returnsp.run(["my_arg"],check=True,...)APIdecorator@commandThis decorator allows you to apply thecommand interfaceto a function.A function decorated with@commandcan take the following keyword arguments:_verbose: bool = TrueEnable/Disable printing of command header and status information during runtime.Example:result=my_command_func("some_arg",_verbose=False)_color: bool = TrueEnable/Disable color for command header and status information.Example:result=my_command_func("some_arg",_color=False)_stdout: Optional[Pipe] = NoneRedirect stdout of the child function. More onPipe()below.Example:fromcmdiimportPipepipe=Pipe(text=False,tty=True,...)# See Pipe doc for arguments...result=my_command_func('foo',_stdout=pipe)print(result.stdout)# Prints the caught ouput._stderr: Union[Optional[Pipe], STDOUT] = NoneRedirect stderr of the child function. More onPipe()below.Example:fromcmdiimportPipepipe=Pipe(text=False,tty=True,...)# See Pipe doc for arguments...result=my_command_func('foo',_stderr=pipe))print(result.stderr)# Prints the caught ouput.If you want to redirectstderrtostdout, you can use this:fromcmdiimportSTDOUTresult=my_command_func('foo',_stderr=STDOUT))_catch_err: bool = TrueCatch errors from child function.This will let the runtime continue, even if a child function throws an exception. If an exception occurs theCmdResultobject will provide information about the error atresult.stderr,result.codeandresult.status. The status message will appear in red.Example:fromcmdiimportPiper=my_command_func("some_arg",_catch_err=True,_stderr=Pipe())r.status# Errorr.code# 1r.stdout# The stderr output from the function call.classCmdResult()The command result object.A function decorated with@commandreturns aCmdResultobject:classCmdResult:val:T,# The return value of the wrapped function.code:int,# Return codename:str,# Command name. By default the name of the wrapped function.status:Optional[Status],color:Optional[StatusColor],stdout:Optional[Union[str,bytes]]=None,stderr:Optional[Union[str,bytes]]=None,dataclassPipe()Use this type to configurestdout/stderrfor a command call.Parameterssave: bool = True- Save the function output ifTrue.text: bool = True- Save function output as text ifTrueelse save as bytes.dup: bool = False- Redirect output at file descriptor level ifTrue. This allows you to redirect output of subprocesses and C code. It'sdupbecause we're duping file descriptors.tty: bool = False- Keep ANSI sequences for saved output ifTrue, else strip ANSI sequences.mute: bool = False- Mute output of function call in terminal ifTrue. NOTE: You can still save and return the output if this is enabled.Example:fromcmdiimportCmdResult,Pipeout_pipe=Pipe(text=False,dup=True,mute=True)err_pipe=Pipe(text=False,dup=True,mute=False)result=foo_cmd(10,_stdout=out_pipe,_stderr=err_pipe,_catch_err=True)print(result.stdout)# prints caught output.print(result.stderr)# prints caught output.Redirect output of functions that run subprocesses or external/foreign/C code.If you want to redirect the output of a function that runs a subprocess or calls foreign code, you have to use aPipewith the argumentdup=True. This will catch the output of stdout/stderr at a lower level (by duping file descriptors):importsubprocessfromcmdiimportcommand,Pipe@commanddeffoo(x,**cmdargs)->CmdResult:subprocess.run("my_script")# Catch stdout of the function via low level redirect:foo(_stdout=Pipe(dup=True))functionstrip_cmdargs(locals_)Parameterslocals_: Dict[str, Any]ReturnsDict[str, Any]Remove cmdargs from dictionary.
This function helps us to easily createCommand Interface Function Wrappers.Example usage:deffoo(x):# Do a lot of stuffreturnx*2@commanddeffoo_cmd(x,**cmdargs):returnfoo(strip_cmdargs(locals()))functionprint_title(result, color, file)Parameterresult: CmdResultcolor: bool = Truefile: Optional[IO[str]] = NoneReturnsNonePrint the title for a command result.Example usage:result=my_cmd('foo')print_title(result)Output:Cmd: my_cmd
-----------functionprint_status(result, color, file)Parameterresult: CmdResultcolor: bool = Truefile: Optional[IO[str]] = NoneReturnsNonePrint the status of a command result.Example usage:result=my_cmd('foo')print_status(result)Output:my_cmd: Okfunctionprint_result(result, color, file)Parameterresult: CmdResultcolor: bool = Truefile: Optional[IO[str]] = NoneReturnsNonePrint out the CmdResult object.Example usage:result=my_cmd('foo')print_result(result)Output:Cmd: my_cmd
-----------
Stdout:
Runtime output of my_cmd...
Stderr:
Some err
foo_cmd3: Okfunctionprint_summary(results, color, headline, file)Parameterresults: CmdResultcolor: bool = Trueheadline: bool = Truefile: Optional[IO[str]] = NoneReturnsNonePrint out a summary of one or more command results.Example usage:fromcmdiimportprint_summaryresults=[]results.append(my_foo_cmd())results.append(my_bar_cmd())results.append(my_baz_cmd())print_summary(results)Output:Cmd: my_foo_cmd
---------------
stdout of foo function...
my_foo_cmd: Ok

Cmd: my_bar_cmd
---------------
stdout of bar function...
my_bar_cmd: Ok

Cmd: my_baz_cmd
---------------
stdout of baz function...
my_baz_cmd: Okfunctionread_popen_pipes(p, interval)Parameterp: subprocess.Popeninterval: int = 10- The interval which the output streams are read and written with.ReturnsIterator[Tuple[str, str]]This creates an iterator which returns Popen pipes line by line for bothstdoutandstderrseparately in realtime.Example usage:fromcmdiimportPOPEN_DEFAULTS,read_popen_pipesp=subprocess.Popen(mycmd,**POPEN_DEFAULTS)forout_line,err_lineinread_popen_pipes:print(out_line,end='')print(err_line,end='')code=p.poll()Run Tests for this libraryNOTE: Some test results must be read visually by the human user.poetry run pytest --capture=no tests