Upgrade to Pro — share decks privately, control downloads, hide ads and more …

FOSSCON 2015 - Writing Better Command Line Applications with Python

Ec6de98b75fa5831bc2f13564c5242fa?s=47 Dave Forgac
August 22, 2015

FOSSCON 2015 - Writing Better Command Line Applications with Python

Often the first step in automating a repetitive task is to write a command line script. Python is a great language for this because of the number of modules and packages available -- but all of the options can be overwhelming or seem like overkill. This talk will cover many of the useful Python tools available for writing command line applications and when and why you might want to use each.

Ec6de98b75fa5831bc2f13564c5242fa?s=128

Dave Forgac

August 22, 2015
Tweet

Transcript

  1. Writing (Better) CLI Applications with Python

  2. Dave Forgac dave@forgac.com @tylerdave Slides, Notes, Examples: http://daveops.com/fosscon2015

  3. Why CLI?

  4. Why Python? Stdlib + PyPI “Readability counts.” (The Zen of

    Python) Argument parsing
  5. When not Python?

  6. Good CLI? Behaves as expected

  7. Arguments & Options example ARGUMENT --long-option or -l --help --verbose

  8. std(in|out|err)

  9. Exit code

  10. Ctrl-c / Signals

  11. Configuration

  12. Colors!

  13. Options stdlib PyPI sys.argv getopt optparse argparse Click Cliff Clint

    Compago Docopt Plac ...
  14. Boilerplate

  15. #! #!/usr/bin/env python or #!/usr/bin/env python2 or #!/usr/bin/env python3

  16. Python 2 & 3 from __future__ import print_function

  17. Execute or Import def main(): pass # do stuff here

    if __name__ == '__main__': main()
  18. Python Package fosscon2015 ├── __init__.py ├── cli.py └── fosscon_stuff.py

  19. Entry Points setup.py setup( … entry_points={ 'console_scripts':[ 'fosscon-argparse=fosscon2015.cli_argparse:cli', 'fosscon-click=fosscon2015.cli_click:cli', 'hello-argparse=fosscon2015.hello_argparse:hello',

    'hello-click=fosscon2015.hello_click:hello', ], }, … )
  20. argparse

  21. hello_argparse.py import argparse def hello(): """ Argparse CLI to greet

    the provided NAME. """ parser = argparse.ArgumentParser() parser.add_argument('name', help="name of person to greet") parser.add_argument('-c', '--count', type=int, default=1, help="number of times to print the greeting") args = parser.parse_args() for i in xrange(args.count): print("Hello, {0}!".format(args.name))
  22. hello-argparse --help $ hello-argparse --help usage: hello-argparse [-h] [-c COUNT]

    name positional arguments: name name of person to greet optional arguments: -h, --help show this help message and exit -c COUNT, --count COUNT number of times to print the greeting
  23. hello-argparse $ hello-argparse usage: hello-argparse [-h] [-c COUNT] name hello-argparse:

    error: too few arguments $ hello-argparse FOSSCON Hello, FOSSCON! $ hello-argparse --count 3 FOSSCON Hello, FOSSCON! Hello, FOSSCON! Hello, FOSSCON!
  24. Click

  25. First: Decorators def wrapper(func): # wraps a function in order

    to add additional behavior @wrapper def my_function(example_arg): # does stuff
  26. hello_click.py import click @click.command() @click.argument('name') @click.option('--count', '-c', default=1, help="Number of

    times to print the greeting") def hello(name, count): """ Click CLI to greet the provided NAME. """ for i in xrange(count): print("Hello, {0}!".format(name))
  27. hello-click --help $ hello-click --help Usage: hello-click [OPTIONS] NAME Click

    CLI to greet the provided NAME. Options: -c, --count INTEGER Number of times to print the greeting --help Show this message and exit.
  28. hello-click $ hello-click Usage: hello-click [OPTIONS] NAME Error: Missing argument

    "name". $ hello-click FOSSCON Hello, FOSSCON! $ hello-click --count 3 FOSSCON Hello, FOSSCON! Hello, FOSSCON! Hello, FOSSCON!
  29. A better CLI app! Read from a file or stdin

    Write to a file or stdout Update to log or stderr Has an option or two Has colors! Does something
  30. cli_click.py @click.command() @click.argument('infile', type=click.File('r'), default='-') @click.argument('outfile', type=click.File('w'), default='-') @click.option('--log-file', '-l',

    type=click.File('w'), default=sys.stderr) @click.option('--verbose', '-v', is_flag=True) def cli(infile, outfile, log_file, verbose): """ Count the occurances of characters in INFILE and output to OUTFILE. """ …
  31. cli_click.py (cont'd) … if verbose: click.secho("Infile: {0}".format(infile), file=log_file) click.secho("Outfile: {0}".format(outfile),

    file=log_file) text = infile.read() char_counts = Counter(text) output = json.dumps(dict(char_counts.most_common()), indent=2) click.secho("Counted characters.", bold=True, file=log_file) click.secho(output, file=outfile, fg='green')
  32. fosscon-click --help $ fosscon-click --help Usage: fosscon-click [OPTIONS] [INFILE] [OUTFILE]

    Count the occurances of characters in INFILE and output to OUTFILE. Options: -l, --log-file FILENAME -v, --verbose --help Show this message and exit.
  33. File input, stdout output $ fosscon-click data/declaration.txt Counted characters... {

    "\n": 176, " ": 1706, "'": 1, "-": 3, ",": 104, ".": 37, "1": 1, "4": 1, …
  34. stdin input, file output $ echo "testing" | fosscon-click -

    outfile.txt Counted characters... $ cat outfile.txt { "e": 1, "g": 1, "i": 1, "\n": 1, "n": 1, "s": 1, "t": 2 }
  35. Verbose with log file $ echo "testing" | fosscon-click --verbose

    --log-file log.txt - out.txt $ cat log.txt Infile: <_io.TextIOWrapper name='<stdin>' encoding='utf-8'> Outfile: <unopened file 'outfile.txt' w> Counted characters...
  36. Further Reading

  37. Virtualenv (I use virtualenvwrapper)

  38. Cookiecutter

  39. Logging

  40. Packaging

  41. Recap No Surprises DRY Explicit Decisions

  42. Thank You! Talk & Contact Info: http://daveops.com/fosscon2015 dave@forgac.com @tylerdave