Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
Double Click - Continue Building Better CLIs
Seb
November 18, 2017
Technology
0
180
Double Click - Continue Building Better CLIs
Seb
November 18, 2017
Tweet
Share
More Decks by Seb
See All by Seb
I Can Be A Speaker, So Can You
elbaschid
0
170
Click - PyCaribbean 2017 - Puerto Rico
elbaschid
0
180
Conferencing - Engineering Meeting
elbaschid
1
28
Show & Tell - PyCon US 2016 Summary
elbaschid
1
32
Click: A Pleasure To Write, A Pleasure To Use
elbaschid
0
340
Hunting for Treasure in Django
elbaschid
1
360
Moby & The Beanstalk
elbaschid
1
280
Docker In Production - A War Story
elbaschid
1
260
Hunting For Treasure In Django
elbaschid
0
120
Other Decks in Technology
See All in Technology
CityGMLとFBXの連携で地理空間のエンタメ化
soh_mitian
0
730
ECS on EC2 で Auto Scaling やってみる!
sayjoy
1
240
Learning to Solve Hard Minimal Problems
takmin
1
440
年700万円損するサーバレスの 認可システムをご紹介します!!
higuuu
3
340
漫画で使えそうな背景画像をblenderを使って作ってみた!
nokonoko1203
1
290
今 SLI/SLO の監視をするなら Sloth が良さそうという話
shotakitazawa
1
280
ログラスを支える技術的投資の仕組み / loglass-technical-investment
urmot
9
1.9k
LINSTOR — это как Kubernetes, но для блочных устройств
flant
0
3.4k
Micro frontends and micro services
kashif98
0
150
開発環境のセキュリティおよびCI/CDパイプラインのセキュア化
rung
PRO
11
4.4k
インフラのテストに VPC Reachability Analyzer は外せないという話
nulabinc
PRO
2
720
AWSを使う上で意識しておきたい、クラウドセキュリティ超入門(駆け足版)
kkmory
0
200
Featured
See All Featured
Teambox: Starting and Learning
jrom
123
7.7k
Build your cross-platform service in a week with App Engine
jlugia
219
17k
Fontdeck: Realign not Redesign
paulrobertlloyd
73
4.1k
jQuery: Nuts, Bolts and Bling
dougneiner
56
6.4k
Writing Fast Ruby
sferik
612
57k
Adopting Sorbet at Scale
ufuk
63
7.6k
Why You Should Never Use an ORM
jnunemaker
PRO
47
7.7k
The Brand Is Dead. Long Live the Brand.
mthomps
46
2.7k
Atom: Resistance is Futile
akmur
255
20k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
14
3.8k
Bash Introduction
62gerente
598
210k
What's in a price? How to price your products and services
michaelherold
229
9.4k
Transcript
Double Click Continue Building Better CLIs Sebastian Vetter @elbaschid Slides:
bit.ly/pyconca-double-click
Check Out Click - Part I Click - A Pleasure
To Write, A Pleasure To Use (PyCon US 2016)
Seb • @elbaschid • Freelance Coder at Roadside Software •
Outdoor Tour Guide • Vancouver ➡ Rocky Mountains
Why This Talk?
Terminology
Parameter • Argument • Option
Argument • Mandatory parameter • Only values required What It
Looks Like: $ pgcli postgresql://....
Option • Optional parameter • Name and value required •
Flags as special options What It Looks Like: $ heroku --help $ heroku logs --app my-heroku-app
(Sub-)Command • Nested commands allowed • Groups sub-commands • Has
options & arguments What It Looks Like: $ pip install django
Introducing Click
click • Author: Armin Ronacher • Version 6.x • http://click.pocoo.org/6/
• Why Click?
Let's Use It
pip install click
Basic Example # cli.py import click @click.command() @click.option('--times', '-t', type=int)
@click.argument('symbol') def main(times, symbol): print(f'I {symbol * times} Click!') if __name__ == '__main__': main()
Running It $ python test.py -t 5 ❤ I ❤❤❤❤❤
Click!
Breaking It $ python test.py -t five ❤ Usage: test.py
[OPTIONS] SYMBOL Error: Invalid value for "--times" / "-t": five is not a valid integer
Predict The Weather
Using A Web API
Github Repos • Click Template: bit.ly/click-template • Example Code: bit.ly/double-click-example
We Are Building • weather config • weather forecast LOCATION
weather config $ weather config Please enter your API key
[]: mysecretapikey $ cat ~/.weather.cfg mysecretapikey
weather forecast $ weather forecast Montreal Time Description Min Temp
Max Temp ============================================================ Tue, Nov 14 @ 00h clear sky -2.3 2.1 Tue, Nov 14 @ 03h clear sky -2.8 0.1 Tue, Nov 14 @ 06h few clouds -2.7 -1.2 Tue, Nov 14 @ 09h few clouds -2.9 -2.9 ... ... ... ...
Multiple Commands
Group import click @click.command() def main(): pass
Group import click @click.group() def main(): pass
config Command @click.command() def config(): pass
config Command @main.command() def config(): pass
forecast Command @main.command() @click.argument('location') def forecast(location): pass
Store The API Key
Running It $ weather config Please enter your API key
[]: mysecretapikey $ cat ~/.weather.cfg mysecretapikey
Parameter Types
Basic Types Click Types int UUID float File str Path
bool Choice IntRange
Using The Path Type @main.option( '--config-file', type=click.Path(), default=os.path.expanduser('~/.weather.cfg')) @click.option('--api-key', envvar='API_KEY',
default='') def config(config_file, api_key): ...
Using The Path Type @main.option( '--config-file', type=click.Path(exists=True, writable=True), default=os.path.expanduser('~/.weather.cfg')) @click.option('--api-key',
envvar='API_KEY', default='') def config(config_file, api_key): ...
Save The Key def config(config_file, api_key): api_key = click.prompt( 'Please
enter your API key', default=api_key ) with open(config_file, 'w') as cfg: cfg.write(api_key)
Look At The Weather
weather forecast <name> $ weather forecast Montreal Time Description Min
Temp Max Temp ============================================================ Tue, Nov 14 @ 00h clear sky -2.3 2.1 Tue, Nov 14 @ 03h clear sky -2.8 0.1 Tue, Nov 14 @ 06h few clouds -2.7 -1.2 Tue, Nov 14 @ 09h few clouds -2.9 -2.9 ... ... ... ... API Call https://api.openweathermap.org/data/2.5/forecast?q=Montreal
weather forecast <city ID> $ weather forecast 6077243 Time Description
Min Temp Max Temp ============================================================ Tue, Nov 14 @ 00h clear sky -2.3 2.1 Tue, Nov 14 @ 03h clear sky -2.8 0.1 Tue, Nov 14 @ 06h few clouds -2.7 -1.2 Tue, Nov 14 @ 09h few clouds -2.9 -2.9 ... ... ... ... API Call https://api.openweathermap.org/data/2.5/forecast?id=6077243
Custom Parameter Type
The Return Type from collections import namedtuple Location = namedtuple('Location',
['query', 'value'])
Our Location Type class LocationType(click.ParamType): name = 'location' def convert(self,
value, param, ctx): try: value = int(value) except ValueError: query = 'q' else: query = 'id' return Location(query, value)
Use It @main.command() @click.argument('location', type=LocationType()) def forecast(location): ... url =
'https://api.openweathermap.org/data/2.5/forecast' params = { location.query: location.value, } response = session.get(url, params=params) ...
weather forecast $ weather forecast Montreal Time Description Min Temp
Max Temp ============================================================ Tue, Nov 14 @ 00h clear sky -2.3 2.1 Tue, Nov 14 @ 03h clear sky -2.8 0.1 Tue, Nov 14 @ 06h few clouds -2.7 -1.2 Tue, Nov 14 @ 09h few clouds -2.9 -2.9 ... ... ... ...
Don't Break It
The Test Runner from click.testing import CliRunner def test_writing_config_file(): runner
= CliRunner() runner.invoke( main, ['config'], input='mysecretapikey', ) ...
Filesystem Tests with runner.isolated_filesystem() as env: api_key = '00ee52f44f3350c73f2684a0f23f2805' filename
= f'{env}/weather.cfg' result = runner.invoke( main, ['--config-file', filename, 'config'], input=api_key, )
The Result assert result.exit_code == 0 assert api_key in result.output
with open(filename) as cfg_file: assert cfg_file.read() == api_key
Packaging Your CLI
Learn More • PyPA Packaging User Guide • Grug make
fire! Grug make wheel! - Russell Keith-Magee • Shipping Software To Users With Python - Glyph
Choose A License It's a minefield, people. All I'm saying
is this: the next time you release code into the wild, do your fellow developers a favor and pick a license – any license. — Coding Horror
Start With Here # setup.py setup( name='double-click-weather', version='0.0.1', license='MIT', ...
)
Create The Executable # setup.py setup( ... entry_points={'console_scripts': [ 'weather
= forecast.simple:main']}, ... )
pip install double-click-weather
File Location ${VENV}/bin/weather.py
Generated Code #!${VENV}/bin/python3 # EASY-INSTALL-ENTRY-SCRIPT: 'double-click-weather','console_scripts','weather' __requires__ = 'double-click-weather' import
re import sys from pkg_resources import load_entry_point if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) sys.exit( load_entry_point('double-click-weather', 'console_scripts', 'weather')() )
You Have Many New Tools
PyCascades 2018 Vancouver, BC ! 22-23 January, 2018 ! Get
Your Ticket ! http://www.pycascades.com
None
Need Help Building Cool Let's Talk seb@roadsi.de | www.roadsi.de Slides:
bit.ly/pyconca-double-click Code: bit.ly/double-click-example
Chains & Pipes
Unix-style Pipeline $ weather find Montreal | weather forecast Time
Description Min Temp Max Temp ============================================================ Tue, Nov 14 @ 00h clear sky -2.3 2.1 Tue, Nov 14 @ 03h clear sky -2.8 0.1 Tue, Nov 14 @ 06h few clouds -2.7 -1.2 ... ... ... ...
Chainable Group @click.group(chain=True) def main(): pass
Write To stdout @main.command() @click.argument('location', type=LocationType()) @click.argument('output', type=click.File('w'), default='-') def
find(location, output): ... output.write(f"{location.value}")
Read From stdin @main.command() @click.argument('location', type=click.File('r')) def forecast(location): value =
location.read() ...