Slide 1

Slide 1 text

Command line programs for busy developers

Slide 2

Slide 2 text

busy, busy, busy …

Slide 3

Slide 3 text

… too busy

Slide 4

Slide 4 text

Creating command line programs

Slide 5

Slide 5 text

Aaron Iles http://www.aaroniles.net

Slide 6

Slide 6 text

One: survey existing tools Two: deep dive into begins

Slide 7

Slide 7 text

Part One: Survey

Slide 8

Slide 8 text

Review Process • Minimal Twitter Client • 3 sub-commands • API tokens from command line options • API tokens from environment variables

Slide 9

Slide 9 text

Review Criteria • Small API • Minimise disruption to workflow • Minimise boilerplate code • Feature rich command lines

Slide 10

Slide 10 text

Review Code https://github.com/aliles/cmdline_examples https://speakerdeck.com/aliles

Slide 11

Slide 11 text

Python Standard Library

Slide 12

Slide 12 text

getopt

Slide 13

Slide 13 text

getopt • Original command line parsing module • Thin wrapper over C library • Function based API

Slide 14

Slide 14 text

getopt Minimal functionality

Slide 15

Slide 15 text

getopt >>> opts, args = getopt.getopt(args,’abc:d:’)

Slide 16

Slide 16 text

optparse

Slide 17

Slide 17 text

optparse • High level command line parser • Support for common option behaviours • Object based API

Slide 18

Slide 18 text

optparse Verbose API Large API No sub-commands Positional arguments ignored

Slide 19

Slide 19 text

optparse >>> parser = OptionParser() >>> parser.add_option("-f", “—file”, ... dest=“filename”, ... help=“write report to FILE”, ... metavar=“FILE”) >>> parser.add_option("-q", “--quiet", ... action=“store_false”, ... dest=“verbose", ... default=True, ... help="don't print status messages") >>> (options, args) = parser.parse_args()

Slide 20

Slide 20 text

argparse

Slide 21

Slide 21 text

argparse • High level command line parser • Support for common option behaviours • Support for sub-commands • Object base API

Slide 22

Slide 22 text

argparse Python 2.7+ or PyPI backport Verbose API Large API

Slide 23

Slide 23 text

argparse >>> parser = argparse.ArgumentParser( ... description=‘Process some integers.') >>> parser.add_argument('integers', metavar='N', ... type=int, nargs=‘+', ... help=‘an integer for the accumulator') >>> parser.add_argument(‘--sum', ... dest=‘accumulate’, ... action=‘store_const’, ... const=sum, default=max, ... help=‘sum the integers') >>> args = parser.parse_args()

Slide 24

Slide 24 text

Python Package Index

Slide 25

Slide 25 text

cliff

Slide 26

Slide 26 text

cliff • Framework over argparse • Intended to use setuptools’ entry points • Class based API

Slide 27

Slide 27 text

cliff ✓ Many hook points during application lifecycle No abstraction of argparse Requires use of sub-commands Expect use of entry points

Slide 28

Slide 28 text

cliff >>> class DemoApp(cliff.app.App): ... pass ! >>> class Simple(cliff.command.Command): ... pass ! >>> DemoApp().run(argv)

Slide 29

Slide 29 text

docopt

Slide 30

Slide 30 text

docopt • Command line interface description language • Docstring based API

Slide 31

Slide 31 text

docopt ✓ Easily create complex command line logic Multiple points of control New syntax language to understand Fewer option behaviours

Slide 32

Slide 32 text

docopt """Minimal Twitter client. ! Usage: tw_docopt.py [options] (timeline|retweets) tw_docopt.py -h | --help ! Options: -h --help Show this screen. --version Show version. """ ! >>> args = docopt(__doc__, version="PyConAU")

Slide 33

Slide 33 text

click

Slide 34

Slide 34 text

click • High level abstraction for optparse • Decorator based API

Slide 35

Slide 35 text

click ✓ Simple but powerful API ✓ Robust unicode support Multiple points of control

Slide 36

Slide 36 text

click >>> @click.command() ... @click.option(‘--count’, default=1, ... help=‘Number of greetings.') ... @click.option(‘--name’, prompt='Your name', ... help=‘The person to greet.') ... def hello(count, name): ... pass

Slide 37

Slide 37 text

Part Two: Begins

Slide 38

Slide 38 text

begins

Slide 39

Slide 39 text

begins • High level abstraction for argparse • Decorator based API

Slide 40

Slide 40 text

begins ✓ Simple but powerful API ✓ Reflection on function signature ✓ Replaces the use of __name__ Beta

Slide 41

Slide 41 text

begins >>> @begin.start ... def run(): ... “Does nothing!”

Slide 42

Slide 42 text

begins usage: app.py [-h] ! Does nothing! ! optional arguments: -h, --help show this help message and exit

Slide 43

Slide 43 text

not begins >>> if __name__ == “__main__”: ... run()

Slide 44

Slide 44 text

More Begins

Slide 45

Slide 45 text

Default Values >>> @begin.start ... def run(name=‘Arther', ... quest=‘Holy Grail’, ... colour=‘blue’, ... *knights): ... "tis but a scratch!"

Slide 46

Slide 46 text

Default Values usage: app.py [-h] [--name NAME] [--quest QUEST] [--colour COLOUR] [knights [knights ...]] ! tis but a scratch! ! positional arguments: knights ! optional arguments: -h, --help show this help message and exit --name NAME, -n NAME (default: Arthur) --quest QUEST, -q QUEST (default: Holy Grail) --colour COLOUR, -c COLOUR (default: blue)

Slide 47

Slide 47 text

Annotations for help >>> @begin.start ... def run(name: 'What, is your name?', ... quest: 'What, is your quest?', ... colour: 'What, is your favourite colour?'): ... pass

Slide 48

Slide 48 text

Annotations for help usage: app.py [-h] NAME QUEST COLOUR ! positional arguments: NAME What, is your name? QUEST What, is your quest? COLOUR What, is your favourite colour? ! optional arguments: -h, --help show this help message and exit

Slide 49

Slide 49 text

Boolean Flags >>> @begin.start ... def main(enable=False, disable=True): ... pass

Slide 50

Slide 50 text

Boolean Flags usage: app.py [-h] [--enable] [--no-enable] [--disable] [--no-disable] ! optional arguments: -h, --help show this help message and exit --enable (default: False) --no-enable --disable --no-disable (default: True)

Slide 51

Slide 51 text

Sub-Commands >>> import begin >>> @begin.subcommand ... def name(answer): ... "What is your name?” ... >>> @begin.subcommand ... def quest(answer): ... "What is your quest?” ... >>> @begin.start ... def main(): ... pass

Slide 52

Slide 52 text

Sub-Commands usage: app.py [-h] {name,quest} ... ! optional arguments: -h, --help show this help message and exit ! Available subcommands: {name,quest} name What is your name? quest What is your quest?

Slide 53

Slide 53 text

Even More Begins

Slide 54

Slide 54 text

Entry Points ! >>> @begin.start(plugins='begins.plugin.demo') ... def main(): ... pass

Slide 55

Slide 55 text

Multiple Commands >>> @begin.subcommand ... def subcmd(): ... pass ... >>> @begin.start(cmd_delim='--') ... def main(): ... pass

Slide 56

Slide 56 text

Environment Variables >>> @begin.start(env_prefix='MP_') ... def run(name='Arther', quest='Holy Grail’, colour='blue', *knights): ... "tis but a scratch!"

Slide 57

Slide 57 text

Configuration Files >>> import begin >>> @begin.start(config_file='.camelot.cfg') ... def run(name='Arther', quest='Holy Grail’, ... colour='blue', *knights): ... "tis but a scratch!"

Slide 58

Slide 58 text

Type Casting >>> @begin.start ... @begin.convert(port=int, ... debug=begin.utils.tobool) ... def main(host=‘127.0.0.1', ... port=8080, ... debug=False): ... "Run web application"

Slide 59

Slide 59 text

Automatic Type Casting >>> @begin.start ... @begin.convert(_automatic=True) ... def main(host=‘127.0.0.1', ... port=8080, ... debug=False): ... "Run web application"

Slide 60

Slide 60 text

Yet More Begins

Slide 61

Slide 61 text

Logging >>> @begin.start ... @begin.logging ... def main(*message): ... for msg in message: ... logging.info(msg)

Slide 62

Slide 62 text

Logging usage: app.py [-h] [-v | -q] [--loglvl {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [--logfile LOGFILE] [--logfmt LOGFMT] [messages [messages ...]] ! positional arguments: messages ! optional arguments: -h, --help show this help message and exit -v, --verbose Increse logging output -q, --quiet Decrease logging output ! logging: Detailed control of logging output ! --loglvl {DEBUG,INFO,WARNING,ERROR,CRITICAL} Set explicit log level --logfile LOGFILE Ouput log messages to file --logfmt LOGFMT Log message format

Slide 63

Slide 63 text

Tracebacks >>> @begin.start ... @begin.tracebacks ... def main(*message): ... pass

Slide 64

Slide 64 text

Tracebacks usage: app.py [-h] [--tracebacks] [--tbdir TBDIR] [messages [messages ...]] ! positional arguments: messages ! optional arguments: -h, --help show this help message and exit ! tracebacks: Extended traceback reports on failure ! --tracebacks Enable extended traceback reports --tbdir TBDIR Write tracebacks to directory

Slide 65

Slide 65 text

Enough Already

Slide 66

Slide 66 text

Closing Thoughts

Slide 67

Slide 67 text

Pragmatism over ideology

Slide 68

Slide 68 text

Your application is not its command toolkit

Slide 69

Slide 69 text

Good Luck

Slide 70

Slide 70 text

Thank You