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

PyOhio 2016 - Let’s Make Better Command Line Applications

PyOhio 2016 - Let’s Make Better Command Line Applications

Dave Forgac

July 30, 2016
Tweet

More Decks by Dave Forgac

Other Decks in Technology

Transcript

  1. Let's Make Better
    Command Line
    Applications

    View Slide

  2. Notes, Examples,
    Slides, Feedback:
    http://daveops.com/pyohio2016

    View Slide

  3. Philosophy

    View Slide

  4. The Zen of Python
    >>> import this
    The Zen of Python, by Tim Peters
    Beautiful is better than ugly.
    Explicit is better than implicit.
    Simple is better than complex.
    Complex is better than complicated.
    Flat is better than nested.
    Sparse is better than dense.
    Readability counts.
    Special cases aren't special enough to break the rules.
    Although practicality beats purity.
    Errors should never pass silently.
    Unless explicitly silenced.
    In the face of ambiguity, refuse the temptation to guess.
    There should be one-- and preferably only one --obvious way to do it.
    Although that way may not be obvious at first unless you're Dutch.
    Now is better than never.
    Although never is often better than *right* now.
    If the implementation is hard to explain, it's a bad idea.
    If the implementation is easy to explain, it may be a good idea.
    Namespaces are one honking great idea -- let's do more of those!

    View Slide

  5. The Unix Philosophy
    Write programs that do one thing
    and do it well.
    Write programs to work together.
    Write programs to handle text streams,
    because that is a universal interface.

    View Slide

  6. Working
    Together
    POSIX

    View Slide

  7. Standard Streams
    Text terminal
    Keyboard
    Display
    Program
    ① stdin
    ② stdout
    ③ stderr
    Image by - Own work, Public Domain,
    ScotXW
    https://commons.wikimedia.org/w/index.php?curid=687268

    View Slide

  8. Redirection
    $ grep "Hello" *
    grep: example_directory: Is a directory
    example.txt:Hello, World!
    grep: protected-file.txt: Permission denied
    $ grep "Hello" * > matches.txt
    grep: example_directory: Is a directory
    grep: protected-file.txt: Permission denied
    $ cat matches.txt
    example.txt:Hello, World!
    $ grep "Hello" * > matches.txt 2> diagnostics.txt
    $ cat diagnostics.txt
    grep: example_directory: Is a directory
    grep: protected-file.txt: Permission denied

    View Slide

  9. Pipelines
    ls -l | grep example | less
    Image by - en:File:Pipeline.svg, Public Domain,
    TyIzaeL
    https://commons.wikimedia.org/w/index.php?curid=11524651

    View Slide

  10. Why CLI?

    View Slide

  11. Why Python?
    Stdlib + PyPI
    “Readability counts.”
    (The Zen of Python)
    Argument parsing

    View Slide

  12. Why not Python?

    View Slide

  13. A Good Python
    Command Line App
    ...'s design is informed by The Zen of
    Python and The Unix Philosophy.

    View Slide

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

    View Slide

  15. std(in|out|err)

    View Slide

  16. Exit code

    View Slide

  17. Ctrl-c / Signals

    View Slide

  18. Configuration
    Standard location (per-platorm)
    Overridable

    View Slide

  19. Config Specificity
    Default value
    Config value
    Env variable
    CLI option

    View Slide

  20. Colors!
    Can be helpful
    ...when used correctly
    & can be disabled

    View Slide

  21. Parsers
    stdlib PyPI
    sys.argv
    getopt
    optparse
    argparse
    Cement
    Click
    Cliff
    Clint
    Compago
    Docopt
    Plac
    ...

    View Slide

  22. Boilerplate

    View Slide

  23. #!/usr/bin/python
    #!
    #!/usr/bin/env python
    or for Python 3 only:
    #!/usr/bin/env python3
    not:

    View Slide

  24. Execute or Import
    def cli():
    pass # do stuff here
    if __name__ == '__main__':
    cli()

    View Slide

  25. Package Layout
    pyohio
    ├── __init__.py
    ├── cli.py
    └── pyohio.py

    View Slide

  26. Entry Points
    setup.py
    setup(

    entry_points={
    'console_scripts':[
    'hello-world=pyohio.cli:hello',
    'pyohio=pyohio.cli:cli',
    ],
    },

    )

    View Slide

  27. Argparse

    View Slide

  28. 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))

    View Slide

  29. 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

    View Slide

  30. hello-argparse
    $ hello-argparse
    usage: hello-argparse [-h] [-c COUNT] name
    hello-argparse: error: too few arguments
    $ hello-argparse PyOhio
    Hello, PyOhio!
    $ hello-argparse --count 3 PyOhio
    Hello, PyOhio!
    Hello, PyOhio!
    Hello, PyOhio!

    View Slide

  31. Click

    View Slide

  32. First: Decorators
    def wrapper(func):
    # wraps a function in order to add additional behavior
    @wrapper
    def my_function(example_arg):
    # does stuff

    View Slide

  33. 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))

    View Slide

  34. 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.

    View Slide

  35. hello-click
    $ hello-click
    Usage: hello-click [OPTIONS] NAME
    Error: Missing argument "name".
    $ hello-click PyOhio
    Hello, PyOhio!
    $ hello-click --count 3 PyOhio
    Hello, PyOhio!
    Hello, PyOhio!
    Hello, PyOhio!

    View Slide

  36. Docopt

    View Slide

  37. hello_docopt.py
    """Hello, Docopt.
    Usage:
    hello-docopt
    hello-docopt [--count=]
    hello-docopt -h | --help
    Options:
    -h --help Show this screen.
    -c --count= Times to print greeting [default: 1].
    """
    from docopt import docopt
    def cli():
    args = docopt(__doc__)
    for i in xrange(int(args['--count'])):
    print("Hello, {0}!".format(args['']))

    View Slide

  38. hello-docopt --help
    $ hello-docopt --help
    Hello, Docopt.
    Usage:
    hello-docopt
    hello-docopt [--count=]
    hello-docopt -h | --help
    Options:
    -h --help Show this screen.
    -c --count= Times to print greeting [default: 1].

    View Slide

  39. hello-docopt
    $ hello-docopt
    Usage:
    hello-docopt
    hello-docopt [--count=]
    hello-docopt -h | --help
    $ hello-docopt PyOhio
    Hello, PyOhio!
    $ hello-docopt --count 3 PyOhio
    Hello, PyOhio!
    Hello, PyOhio!
    Hello, PyOhio!

    View Slide

  40. Let's make 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

    View Slide

  41. Sample data
    Declaration of Independence
    [Adopted in Congress 4 July 1776]
    The Unanimous Declaration of the Thirteen United States of
    When, in the course of human events, it becomes necessary for
    dissolve the political bands which have connected them with an
    assume among the powers of the earth, the separate and equal s
    which the laws of nature and of nature's God entitle them, a d
    to the opinions of mankind requires that they should declare t
    which impel them to the separation.

    View Slide

  42. cli_click.py
    import click
    import sys
    from pyohio2016 import counter
    @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 char occurances in INFILE, output to OUTFILE. """

    View Slide

  43. cli_click.py (cont'd)

    if verbose:
    click.echo("Infile: {0}".format(infile), file=log_file)
    click.echo("Outfile: {0}".format(outfile), file=log_file)
    text = infile.read()
    char_counts = counter.count_chars(text)
    output = "\n".join(
    ["{0} {1}".format(k, v) for k, v in char_counts])
    click.echo("Counted characters...", file=log_file)
    click.secho(output, file=outfile, fg='green')
    if verbose:
    click.echo("Done.".format(outfile), file=log_file)

    View Slide

  44. pyohio-click --help
    Usage: pyohio-click [OPTIONS] [INFILE] [OUTFILE]
    Count char occurances in INFILE, output to OUTFILE.
    Options:
    -l, --log-file FILENAME
    -v, --verbose
    --help Show this message and exit.

    View Slide

  45. File input, stdout output
    $ pyohio-click data/declaration.txt
    Counted characters...
    1706
    e 875
    t 640
    o 522
    n 493
    a 479
    s 477
    i 454
    r 429

    View Slide

  46. stdin input
    $ echo -n "test" | pyohio-click
    Counted characters...
    t 2
    s 1
    e 1
    $ echo -n "test" | pyohio-click - results.txt
    Counted characters...
    $ cat results.txt
    t 2
    s 1
    e 1

    View Slide

  47. Verbose with log file
    $ echo -n "test" | pyohio-click -v --log-file log.txt - out.txt
    $ cat out.txt
    t 2
    s 1
    e 1
    $ cat log.txt
    Infile: <_io.TextIOWrapper name='' encoding='utf-8'>
    Outfile:
    Counted characters...
    Done.

    View Slide

  48. Further Reading

    View Slide

  49. Packaging

    View Slide

  50. Cookiecutter

    View Slide

  51. Logging

    View Slide

  52. The Zen of Python +
    The Unix Philosophy =
    Better Python CLI

    View Slide

  53. Thank You!
    Notes, Examples, Slides, Feedback:
    Questions?
    [email protected]
    @tylerdave
    http://daveops.com/pyohio2016

    View Slide