# Python CLI Bandersnatch
By Paul Bailey
---
## Choose Your Own Adventure
Python Command Line Libraries:
- [argparse](https://docs.python.org/3/library/argparse.html)
- [click](https://click.palletsprojects.com/en/7.x/)
- [fire](https://google.github.io/python-fire/)
- Maybe a bonus!!
**Vote at: [ppp.wtf/cli](https://ppp.wtf/cli)**
---
<iframe width="100%" height="500" src="https://www.ppp.wtf/embed/cli/1/" frameborder="0" style="border: 0"></iframe>
<div style="font-size: 40%;">
<a href="#/3">Cart Wheel</a> - <a href="#/5">About Me</a>
</div>
---
<iframe width="100%" height="500" src="https://www.ppp.wtf/embed/cli/2/" frameborder="0" style="border: 0"></iframe>
<div style="font-size: 40%;">
<a href="#/4">Yes</a> - <a href="#/5">No</a>
</div>
---
### This isn't Black Mirror
<iframe src="https://giphy.com/embed/3oriNZGGY2eNlwj2sE" width="480" height="267" frameBorder="0" class="giphy-embed" allowFullScreen></iframe>
<div style="font-size: 40%;">
<a href="#/6">Next »</a>
</div>
---
## About Me
- @PizzaPanther (Twitter, Github, etc)
- System Architect @ CognitiveSpace.com
---
<iframe width="100%" height="500" src="https://www.ppp.wtf/embed/cli/3/" frameborder="0" style="border: 0"></iframe>
<div style="font-size: 40%;">
<a href="#/7">argparse</a> - <a href="#/9">click</a> - <a href="#/11">fire</a>
</div>
---
## Intro to Argparse
https://docs.python.org/3/library/argparse.html
<div style="text-align: left">
Pros:
- Easier to use
- Included in the standard library
- Used very often
Cons:
- Not so elegant
</div>
---
### Sample Argparse
```python
import argparse
def hello(args):
greeting = 'Hello'
if args.enable_texas:
greeting = 'Howdy'
for name in args.names:
for i in range(0, args.times):
print(f'{greeting} {name}')
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--times', help='number of hellos', type=int, default=1)
parser.add_argument('--enable-texas', action='store_true')
parser.add_argument('names', nargs='+', help='names to say hello')
args = parser.parse_args()
hello(args)
```
<div style="font-size: 40%;">
<a href="#/13">argparse done</a> - <a href="#/9">click left</a> - <a href="#/11">fire left</a> - <a href="#/16">all done</a>
</div>
---
## Intro to Click
https://click.palletsprojects.com/en/7.x/
<div style="text-align: left">
Pros:
- Easy to use decorators
- Loads of tools!
- Very flexible for many uses cases
Cons:
- Lots of decorators
- Loads of tools to learn
</div>
---
### Sample Click
```python
import click
@click.command()
@click.argument('names', nargs=-1)
@click.option('--times', default=1, help='number of hellos')
@click.option('--enable-texas', is_flag=True)
def hello(names, times, enable_texas):
greeting = 'Hello'
if enable_texas:
greeting = 'Howdy'
for name in names:
for i in range(0, times):
print(f'{greeting} {name}')
if __name__ == "__main__":
hello()
```
<div style="font-size: 40%;">
<a href="#/14">click done</a> - <a href="#/7">argparse left</a> - <a href="#/11">fire left</a> - <a href="#/16">all done</a>
</div>
---
### Intro to Fire
https://google.github.io/python-fire/
<div style="text-align: left">
Pros:
- Easiest to use
- Make a CLI out of any Python code
Cons:
- Non-standard CLI usage
- Fewer standard features
</div>
---
### Fire Example
```python
import fire
def hello(names, times=1, enable_texas=False):
greeting = 'Hello'
if enable_texas:
greeting = 'Howdy'
for name in names:
for i in range(0, times):
print(f'{greeting} {name}')
if __name__ == "__main__":
fire.Fire(hello)
```
<div style="font-size: 40%;">
<a href="#/15">fire done</a> - <a href="#/7">argparse left</a> - <a href="#/9">click left</a> - <a href="#/16">all done</a>
</div>
---
<iframe width="100%" height="500" src="https://www.ppp.wtf/embed/cli/4/" frameborder="0" style="border: 0"></iframe>
<div style="font-size: 40%;">
<a href="#/9">click</a> - <a href="#/11">fire</a>
</div>
---
<iframe width="100%" height="500" src="https://www.ppp.wtf/embed/cli/5/" frameborder="0" style="border: 0"></iframe>
<div style="font-size: 40%;">
<a href="#/7">argparse</a> - <a href="#/11">fire</a>
</div>
---
<iframe width="100%" height="500" src="https://www.ppp.wtf/embed/cli/6/" frameborder="0" style="border: 0"></iframe>
<div style="font-size: 40%;">
<a href="#/7">argparse</a> - <a href="#/9">click</a>
</div>
---
<iframe width="100%" height="500" src="https://www.ppp.wtf/embed/cli/7/" frameborder="0" style="border: 0"></iframe>
<div style="font-size: 40%;">
<a href="#/23">sub-commands</a> - <a href="#/17">click</a> - <a href="#/25">fire</a>
</div>
---
### Click Subcommands
```python
import click
@click.group()
@click.option('--times', default=1, help='number of hellos')
@click.pass_context
def cli(context, times):
context.obj['times'] = times
def greet(context, greeting, names):
for name in names:
for i in range(0, context.obj['times']):
print(f'{greeting} {name}')
@cli.command()
@click.pass_context
@click.argument('names', nargs=-1)
def hello(context, names):
greet(context, 'Hello', names)
@cli.command()
@click.pass_context
@click.argument('names', nargs=-1)
def howdy(context, names):
greet(context, 'Howdy', names)
@cli.command()
@click.pass_context
@click.argument('names', nargs=-1)
def all(context, names):
context.forward(hello)
context.forward(howdy)
if __name__ == "__main__":
cli(obj={})
```
---
### Click Types
<div style="font-size: 70%">
https://click.palletsprojects.com/en/7.x/parameters/#parameter-types
</div>
```python
@click.option(
'--cert',
type=click.Path(exists=True, dir_okay=False),
default=None
)
```
---
### Click ENVs
<div style="font-size: 70%">
https://click.palletsprojects.com/en/7.x/arguments/#environment-variables
</div>
```python
@click.argument(
'src',
envvar='SRC',
type=click.File('r')
)
```
---
### Click Colors
```python
click.echo(
click.style('Hello World!', fg='green')
)
```
---

---
### More Click
https://click.palletsprojects.com/en/7.x/utils/
- Clear screen
- Getting characters
- Waiting for keypress
- Launching editors, applications
- Progress bars
- and More!
<div style="font-size: 40%;">
<a href="#/30">Questions/Comments »</a>
</div>
---
### Argparse subcommands
```python=
import argparse
def howdy(args):
_greet('Howdy', args.names, times=args.times)
def hello(args):
_greet('Hello', args.names, times=args.times)
def all(args):
hello(args)
howdy(args)
def _greet(greeting, names, times=1):
for name in names:
for i in range(0, times):
print(f'{greeting} {name}')
if __name__ == "__main__":
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('--times', help='number of hellos', type=int, default=1)
subparsers = parser.add_subparsers(help='sub-command help', dest='command')
parser_hello = subparsers.add_parser('hello', help='say hello')
parser_howdy = subparsers.add_parser('howdy', help='say howdy')
parser_all = subparsers.add_parser('all', help='say hello and howdy')
for p in (parser_hello, parser_howdy, parser_all):
p.add_argument('names', nargs='+', help='names to say hello')
args = parser.parse_args()
globals()[args.command](args)
```
---
### Click Subcommands
```python
import click
@click.group()
@click.option('--times', default=1, help='number of hellos')
@click.pass_context
def cli(context, times):
context.obj['times'] = times
def greet(context, greeting, names):
for name in names:
for i in range(0, context.obj['times']):
print(f'{greeting} {name}')
@cli.command()
@click.pass_context
@click.argument('names', nargs=-1)
def hello(context, names):
greet(context, 'Hello', names)
@cli.command()
@click.pass_context
@click.argument('names', nargs=-1)
def howdy(context, names):
greet(context, 'Howdy', names)
@cli.command()
@click.pass_context
@click.argument('names', nargs=-1)
def all(context, names):
context.forward(hello)
context.forward(howdy)
if __name__ == "__main__":
cli(obj={})
```
---
### Fire subcommands (Functional)
```python=
import fire
def howdy(names, times=1):
_greet('Howdy', names, times=times)
def hello(names, times=1):
_greet('Hello', names, times=times)
def all(names, times=1):
hello(names, times=times)
howdy(names, times=times)
def _greet(greeting, names, times=1):
for name in names:
for i in range(0, times):
print(f'{greeting} {name}')
if __name__ == "__main__":
fire.Fire()
```
---
### Fire subcommands (Class)
```python=
import fire
class Greet:
def howdy(self, names, times=1):
self._greet('Howdy', names, times=times)
def hello(self, names, times=1):
self._greet('Hello', names, times=times)
def all(self, names, times=1):
self.hello(names, times=times)
self.howdy(names, times=times)
def _greet(self, greeting, names, times=1):
for name in names:
for i in range(0, times):
print(f'{greeting} {name}')
if __name__ == "__main__":
fire.Fire(Greet)
```
---
<iframe width="100%" height="500" src="https://www.ppp.wtf/embed/cli/8/" frameborder="0" style="border: 0"></iframe>
<div style="font-size: 40%;">
<a href="#/28">Argparse</a> - <a href="#/29">Docopt</a>
</div>
---
### More Argparse
<div style="text-align: left; font-size: 90%;">
Number of Args: https://docs.python.org/3/library/argparse.html#nargs
Actions: https://docs.python.org/3/library/argparse.html#action
File Types:
```python
parser.add_argument('out', type=argparse.FileType('w'))
```
</div>
<br>
<div style="font-size: 40%;">
<a href="#/30">Questions/Comments »</a>
</div>
---
### Bonus: Docopt
http://docopt.org/
```python=
"""Naval Fate.
Usage:
naval_fate.py ship new <name>...
naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
naval_fate.py ship shoot <x> <y>
naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
naval_fate.py (-h | --help)
naval_fate.py --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.
"""
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__, version='Naval Fate 2.0')
print(arguments)
```
---
## Comments and Questions
<iframe src="https://giphy.com/embed/lz67zZWfWPsWnuGH0s" width="480" height="360" frameBorder="0" class="giphy-embed" allowFullScreen></iframe>
Examples on [Github](https://github.com/pizzapanther/pizzapanther.github.io/tree/master/talks/cli-examples)
{"metaMigratedAt":"2023-06-15T02:01:19.938Z","metaMigratedFrom":"YAML","title":"Python CLI Bandersnatch","breaks":false,"description":"Presentation about Python CLI libraries","slideOptions":"{\"theme\":\"night\"}","contributors":"[{\"id\":\"7cc70866-a83e-46ea-9638-b8fbc2e11e7a\",\"add\":11397,\"del\":2975}]"}