Published

owned this note owned this note

Linked with GitHub Any changes Be notified of any changes

Mention me Be notified of mention me

# I prefer Plumbum for creating Command-Line Interfaces Having Evaluated Click and Fire. ## Example 1 import fire def greet(): print("Hello world!") if __name__ == '__main__': fire.Fire(greet) The [click code is here](https://youtu.be/kNke39OZ2k0?t=206). ## Example 2 ```python import fire def greet(greeting='World'): print(f"Hello {greeting}") if __name__ == '__main__': fire.Fire(greet) ``` The [click code is here](https://youtu.be/kNke39OZ2k0?t=245). ### Notes I can already see that click will be better at documenting the arguments to a command than Fire. ## Example 3 ```python import fire def greet(greeting='World', repeat=1): _ = f'Hello {greeting}

' * repeat print(_) if __name__ == '__main__': fire.Fire(greet) ``` The click code for this is [shown here](https://youtu.be/kNke39OZ2k0?t=386). ### Note Invalid types for arguments also are more [human-friendly](https://youtu.be/kNke39OZ2k0?t=414) in Click than in Fire: ``` c:\prg\play-python-fire\click-example>python 3.py -greeting Jona --repeat adfadsf python 3.py -greeting Jona --repeat adfadsf Traceback (most recent call last): File "3.py", line 8, in <module> fire.Fire(greet) File "C:\Development\lib\site-packages\fire-0.1.3-py3.7.egg\fire\core.py", line 130, in Fire component_trace = _Fire(component, args, context, name) File "C:\Development\lib\site-packages\fire-0.1.3-py3.7.egg\fire\core.py", line 413, in _Fire target=component.__name__) File "C:\Development\lib\site-packages\fire-0.1.3-py3.7.egg\fire\core.py", line 601, in _CallAndUpdateTrace component = fn(*varargs, **kwargs) File "3.py", line 4, in greet _ = f'Hello {greeting}

' * repeat TypeError: can't multiply sequence by non-int of type 'str' c:\prg\play-python-fire\click-example> ``` ## Example 4 ```python import fire import path def greet(out='-', greeting='World', repeat=1): _ = f'Hello {greeting}

' * repeat if out != '-': p = path.Path(out) p.write_text(_) else: print(_) if __name__ == '__main__': fire.Fire(greet) ``` After comparing the above with [the click equivalent](https://youtu.be/kNke39OZ2k0?t=568), we notice some things: * Click gives you a filehandle to write to regardless of whether it is a file or stdout. I had to manually dispatch to different output streams myself with Fire. * The number of arguments to click's `option` and `argument` is getting a bit confusing... instead of just a readable function signature, one has to supply `required=False`. You can tell I'm a former [argh](https://github.com/neithere/argh) user. * You also get to [see](https://youtu.be/kNke39OZ2k0?t=646) `click.echo` shine in all it's polymorphic grandeur here. # Sub-command example ```python import fire import path class App(object): def __init__(self, verbose=False): self.verbose = verbose if verbose: print("We are in verbose mode.") def say(self, out='-', greeting='World', repeat=1): _ = f'Hello {greeting}

' * repeat if out != '-': p = path.Path(out) p.write_text(_) else: print(_) if __name__ == '__main__': fire.Fire(App) ``` Passing arguments to sub-commands in click [appears tortuous](https://youtu.be/kNke39OZ2k0?t=931) - you have to create a decorator with a call to `make_pass_decorator` - but there is no comparison between the help strings. **You dont even know you have a `say` command with the help output from Fire**: ``` c:\prg\play-python-fire\click-example>python 5.py --help python 5.py --help INFO: Showing help with the command '5.py -- --help'. Type: type String form: <class '__main__.App'> File: 5.py Line: 5 Usage: 5.py [VERBOSE] 5.py [--verbose VERBOSE] ``` The help does not even list the subcommand # Manifest Interface We are going to take a break here and discuss a concept called "Manifest Interface". There probably is a better term for that these days but the bottom line is: *you should be able to look at something and clearly understand it's function*. With click, there are only options and arguments. If something is a flag, you do not know that immediately, [you must read further to the right for an `is_flag=True` argument](https://youtu.be/kNke39OZ2k0?t=995). The concepts of flag, argument, and option are not even needed to use Fire because all you are doing is letting fire hook you up to standard Python code... you have infinite power to drill down through your code from the command-line. As [the author says](https://www.reddit.com/r/Python/comments/5x8wdv/introducing_python_fire_a_library_for/dehs3nl/) - > You could have an object with a property that's a function that returns a module with a dict with a member whose value that ou want to access, and you can access it from the command line > So if that is what is important, nothing will touch Fire. # Enter Plumbum In Click, you create a "pass decorator" so that things are passed down to sub-commands. Plumbum works the opposite way: you simply access the `root_app` property and work your way up. Here is the subcommand example written in Plumbum. ```python from plumbum import cli def greet(greeting, repeat): _ = f'Hello {greeting}

' * repeat print(_) class MyApp(cli.Application): verbose = cli.Flag('--verbose', default=False) def main(self): pass @MyApp.subcommand('say') class Say(cli.Application): """Provide a greeting which may be verbose.""" greeting = cli.SwitchAttr('--greeting', str, default='World') repeat = cli.SwitchAttr('--repeat', int, default=1) def main(self): greeting = f"v={self.root_app.verbose}: {self.greeting}" greet(greeting, self.repeat) if __name__ == '__main__': MyApp.run() ``` Notice the call to `self.root_app.verbose` in `main()` to get access to the `verbose` flag defined in the parent command. # Conclusion I started out my life writing command-line apps using [argh](https://www.reddit.com/r/Python/comments/7t50zq/commandline_processing_why_i_made_the_move_from/) but [the lack of regular releases](https://github.com/neithere/argh/issues/124) coupled with the fact that you can only infer so much from pure Python code led me to look for something that was explicit and that is [Plumbum's CLI tools](https://plumbum.readthedocs.io/en/latest/cli.html). [Click](https://click.palletsprojects.com/en/7.x/) is very popular and [Fire](https://github.com/google/python-fire) is freaking amazing. But for the types of things I do, Plumbum CLI is most appopriate. I think Plumbum gives me a nice separation of responsibilities. I have an app class for processing the command-line which then calls functionality regardless of where it is. In Click, I would find myself addings lots of decorators to my program functionality itself.