Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Double Click - Continue Building Better CLIs
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Seb
November 18, 2017
Technology
500
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
330
Click - PyCaribbean 2017 - Puerto Rico
elbaschid
0
470
Conferencing - Engineering Meeting
elbaschid
1
51
Show & Tell - PyCon US 2016 Summary
elbaschid
1
110
Click: A Pleasure To Write, A Pleasure To Use
elbaschid
0
690
Hunting for Treasure in Django
elbaschid
1
740
Moby & The Beanstalk
elbaschid
1
540
Docker In Production - A War Story
elbaschid
1
320
Hunting For Treasure In Django
elbaschid
0
190
Other Decks in Technology
See All in Technology
「顧客の声を聞かなければ何も始まらない」 ── 顧客の声から生まれた『AI返信補助機能』の開発プロセス / AICon2026_shikata_imai
rakus_dev
1
270
20260720_クラウド女子会×PyLadiesTokyoコラボ Amazon Bedrock ハンズオン用資料
yuuka51
2
110
AI時代におけるエンジニアの新たな役割──FDEとクオリアの探求/登壇資料(戸井田 裕貴)
hacobu
PRO
0
330
AIが当たり前の組織で エンジニアはどう育つか
nishihira
1
990
アップデートで何が変わった?デモで学んで使いこなすIBM Bob2.0
muehara
0
230
全社でのソフトウェアサプライチェーン攻撃対策をやってみた with Takumi Guard
z63d
0
280
[Droidcon Orlando '26] The Android Lens: Applying Mobile Forensics to AI Performance
amanda_hinchman
1
110
壊して学ぶAWS CDK: そのcdk deployで消えるもの、残るもの
k_adachi_01
1
480
AI工学特論: MLOps・継続的評価
asei
6
1.3k
AmplifyHostingConstructからSSRフレームワークのためのホスティング設計を考察する/amplify-hosting-construct
fossamagna
1
310
伝票作成AIエージェントを支える、LLMOpsとインフラの選択肢 / AICon2026_takeda
rakus_dev
0
260
キャリアLT会#3
beli68
2
250
Featured
See All Featured
What the history of the web can teach us about the future of AI
inesmontani
PRO
1
640
Amusing Abliteration
ianozsvald
1
240
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.2k
How to Think Like a Performance Engineer
csswizardry
28
2.7k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
210
Building an army of robots
kneath
306
46k
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
370
The Cult of Friendly URLs
andyhume
79
7k
Bootstrapping a Software Product
garrettdimon
PRO
307
120k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
410
Everyday Curiosity
cassininazir
0
260
Darren the Foodie - Storyboard
khoart
PRO
3
3.5k
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() ...