Pro Yearly is on sale from $80 to $50! »

Click - PyCaribbean 2017 - Puerto Rico

C65d18a43152b199ee94aad2b79b70c4?s=47 Seb
February 18, 2017

Click - PyCaribbean 2017 - Puerto Rico

C65d18a43152b199ee94aad2b79b70c4?s=128

Seb

February 18, 2017
Tweet

Transcript

  1. Click A Pleasure To Write A Pleasure To Use Sebastian

    Vetter @elbaschid Slides: http://bit.ly/click-slides
  2. Seb • @elbaschid • Living in Vancouver ! • Python

    Engineer at Eventbase
  3. Why This Talk?

  4. Terminology https://www.flickr.com/photos/chris_schultz/11306328493

  5. Parameter • Argument • Option

  6. Argument • Mandatory parameter • Only values required What It

    Looks Like: $ pgcli postgresql://....
  7. Option • Optional parameter • Name and value required What

    It Looks Like: $ heroku --help $ heroku logs --app my-heroku-app
  8. (Sub-)Command • Nested commands allowed • Groups sub-commands • Has

    options & arguments What It Looks Like: $ pip install django
  9. In Plain Python https://www.flickr.com/photos/tutam/5008073062/

  10. Most Basic CLI import sys if len(sys.argv) <= 1: print('You

    need to give me an argument') sys.exit(1) args = sys.argv[1:] print('Your argument are: {}'.format(args))
  11. The Limitations • Manual parsing • No input validation •

    No help text formatting
  12. Choose A Library https://www.flickr.com/photos/14601516@N00/3778738056

  13. Standard Library • optparse • argparse

  14. There Are Others • docopt • clint • ...many more...

  15. Introducing Click https://www.flickr.com/photos/27587002@N07/7155464950

  16. click • Author: Armin Ronacher • Version 6.x • http://click.pocoo.org/6/

  17. click • Intuitive • Nestable and composable • Better handling

    of input and output • Why Click?
  18. Let's Build A CLI https://www.flickr.com/photos/31381897@N03/3967634792

  19. pip install click

  20. Setting Up A CLI https://www.flickr.com/photos/sveinhal/2416609728

  21. On Github • Click Template: https://github.com/elbaschid/cc-python-cli • Example Code: bit.ly/pycon-click-example

  22. Create A Project $ pip install cookiecutter $ cookiecutter https://github.com/elbaschid/cc-python-cli

    ... $ cd my-!-tool $ pip install -e . $ my_!_tool I am the my_!_tool CLI
  23. Decorator-based approach # my_!_tool/cli.py import click @click.command() def main(): click.secho('I

    am the my_!_tool CLI', fg='blue')
  24. Our Example https://www.flickr.com/photos/judy-van-der-velden/14687818499

  25. Ad Notifications • Parsing a given search URL • Extracting

    ads • Ignore "seen" ads • Email notifications
  26. Specify a URL

  27. Run it! $ ad_notifier "http://www.pinkbike.com/buysell/..." Processing URL: http://www.pinkbike.com/buysell/list/... Found 20

    ads!
  28. Arguments https://www.flickr.com/photos/alper/2781645509

  29. Add an argument # ad_notifier/cli.py @click.command() @click.argument('url') def main(url): click.echo('Processing

    URL:', url) ads = find_ads(url) click.echo('Found {} ads!'.format(len(ads)))
  30. Send an Notification Email

  31. Run it! $ ad_notifier "http://..." --email my@email.com Processing URL: http://...

    Found 20 ads! Sending email to my@email.com
  32. Options https://www.flickr.com/photos/photographingtravis/15427838493

  33. Add Email Option @click.command() @click.argument('url') @click.option('--email', envvar='EMAIL_ADDRESS') def main(url, email):

    ... if email and ads: send_email(email, ads)
  34. Handle New Ads Only

  35. Run it! $ ad_notifier "http://..." ... Found 0 ads! $

    ad_notifier "http://..." --reset-cache ... Found 20 ads!
  36. Flags https://www.flickr.com/photos/pennstatelive/16216912657

  37. Reset the cache @click.command() ... @click.option('--reset-cache', default=False, is_flag=True) def main(url,

    email, reset_cache): click.echo('Processing URL:', url) new_ads = find_new_ads(url, reset_cache) click.echo('Found {} ads!'.format(len(new_ads))) ...
  38. Make it a Periodic Task

  39. Run it! $ # With scheduler $ ad_notifier run_periodically <URL>

    \ --run-every 5 --email my@email.com $ # Once $ ad_notifier run_once <URL> \ --email my@email.com
  40. (Sub-)Commands (https://www.flickr.com/photos/soldiersmediacenter/5754070333)

  41. Add a run & schedule command @click.group() def main(): pass

    @main.command() ... def run_once(url, email, reset_cache): pass @main.command() ... @click.option('--run-every', default=5, type=int) def run_periodically(..., run_every): pass
  42. Not Very DRY @main.command() @click.argument('url') @click.option('--email') @click.option('--reset-cache', default=False, is_flag=True) def

    run_once(url, email, reset_cache): ... @main.command() @click.argument('url') @click.option('--email') @click.option('--reset-cache', default=False, is_flag=True) @click.option('--run-every', default=5, type=int) def run_periodically(url, email, reset_cache, run_every): ...
  43. Context https://www.flickr.com/photos/chanceprojects/16286089602

  44. Using the Context obj @click.group() @click.argument('url') @click.option('--email') @click.option('--reset-cache', ...) @click.pass_context

    def main(context, url, email, reset_cache): context.obj = { 'url': url, 'email': email, 'reset_cache': reset_cache}
  45. Updating Sub-Commands @main.command() @click.pass_context def run_once(context): process_url(**context.obj)

  46. Add schedule subcommand @main.command() @click.option('--run-every', default=5, type=int) @click.pass_context def run_periodically(context,

    run_every): schedule.every(run_every).minutes.do( process_url, **context.obj) while True: schedule.run_pending() time.sleep(1)
  47. The Final Command $ # With scheduler $ ad_notifier <URL>

    run_periodically \ --run-every 5 --email my@email.com $ # Once $ ad_notifier <URL> run_once \ --email my@email.com
  48. ! We Did It !

  49. Improve Documentation

  50. No Effort $ ad_notifier Usage: ad_notifier [OPTIONS] URL COMMAND [ARGS]...

    Options: --email TEXT --reset-cache --help Show this message and exit. Commands: run_once run_periodically
  51. Adding help text @click.option('--reset-cache', default=False, is_flag=True, help='reset the internal ads

    cache') @click.pass_context def main(context, url, email, reset_cache): """ Check pinkbike ads for URL and (optionally) send email notification. """
  52. A Little Effort $ ad_notifier Usage: ad_notifier [OPTIONS] URL COMMAND

    [ARGS]... Check pinkbike ads for URL and (optionally) send email notification. Options: --email TEXT email to send notifications to --reset-cache reset the internal ads cache --help Show this message and exit. Commands: run_once Run check for new ads once. run_periodically Run scheduler to check for new ads...
  53. Also Check Out • Environment Variables • Parameter Types •

    Testing • Bash Autocomplete
  54. Questions? https://www.flickr.com/photos/thomashawk/9058066607

  55. Click A Pleasure To Write A Pleasure To Use Sebastian

    Vetter @elbaschid Slides: http://bit.ly/click-slides