Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Double Click - Continue Building Better CLIs
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Seb
November 18, 2017
Technology
510
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Double Click - Continue Building Better CLIs
Seb
November 18, 2017
More Decks by Seb
See All by Seb
I Can Be A Speaker, So Can You
elbaschid
0
350
Click - PyCaribbean 2017 - Puerto Rico
elbaschid
0
480
Conferencing - Engineering Meeting
elbaschid
1
57
Show & Tell - PyCon US 2016 Summary
elbaschid
1
120
Click: A Pleasure To Write, A Pleasure To Use
elbaschid
0
710
Hunting for Treasure in Django
elbaschid
1
760
Moby & The Beanstalk
elbaschid
1
560
Docker In Production - A War Story
elbaschid
1
330
Hunting For Treasure In Django
elbaschid
0
190
Other Decks in Technology
See All in Technology
Patterns in AI Driven Software Development
akosma
PRO
0
440
SLOを阻む不安
sraku2159
0
210
ドメインエキスパートは いかにして AI を使って プロダクト開発をするか
tatsuyashiraishi
1
260
AIがGISを代替する日:〜すべてをAIが創る時代に、私たちがFOSS4Gに関わる意味〜
lehupa
0
200
自律的なチームを作るためにTechPMがやらないこと
ny7760
0
490
新米を「売り越さない」、Aurora DSQLで作る在庫ゼロ死守の限定ドロップEC
yama3133
0
140
Don't write the architecture only in the prompt.
neohsu
0
300
AI時代のエンジニアの情報発信〜個人の発信を組織の力に変える〜
ryo_kawamata
0
6.9k
20261031_IoTLT_vol140_kitazaki_v1.pdf
kitazaki
0
150
FinOpsを導入してマルチプロダクトのコスト最適化を実現する
kworkdev
PRO
0
110
Lint の層を組み直した話
bengo4com
2
300
Jev の中身は50行で再現できる。でも本物との差はどこにあるのか
hisuzuya
0
280
Featured
See All Featured
Building Adaptive Systems
keathley
44
3.3k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
390
Become a Pro
speakerdeck
PRO
31
6.3k
Producing Creativity
orderedlist
PRO
348
41k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
1.1k
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
550
Code Review Best Practice
trishagee
74
20k
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
350
Embracing the Ebb and Flow
colly
88
5.2k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
590
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
[email protected]
| 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() ...